Standard-library Rust port of the Swift prototype for the rusty-kaspa fork. 23 tests tie it to proto-cuda/packs: program.json instruction by instruction for three packs, cache FNV 48c4f5bf24166b2e, dataset head/last/64 samples, 96/96 hash vectors per pack, and kernel.cu, program.metal, kernel.cl, program.h, memhard.h, memhard.metal byte-identical. CPU verify 0.41 to 0.58 ms per warp (Swift 0.63 to 1.21), cache fill 175 to 181 ms one core. CLI: bench, export, hash. program.json is written as valid JSON (the Swift quotes the cache line mask inside the "item" string; fix pending in main.swift). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| rustfmt.toml | ||
igneum-pow
The Igneum lottery hash in Rust, bit-exact with the Swift prototype in proto-metal/main.swift. This is the
crate the rusty-kaspa fork will call (docs/fork-map.md, rows a1 to a3) so a node written in Rust can verify any
block and hand miners the kernel source for the epoch. No dependency outside the standard library; serde_json
is a dev-dependency for reading the packs in the tests.
Date: 3 October 2026. Toolchain: rustc 1.99.0 via rustup (the Homebrew 1.69 on PATH is too old; use
~/.cargo/bin/cargo).
Modules
| Module | What it is | Swift namesake |
|---|---|---|
seed |
32-byte seed words from a string (FNV-1a 64, four salts, finalised); seed_words_from_bytes is the boundary where the chain will feed the VDF output; SplitMix64 |
seedWords, SplitMix64 |
generator |
the 64-instruction program for a seed (op, dst, src, src2, imm, imm2, rot, bit, mask); levers load_weight and wide_frac |
generateProgram, GeneratorConfig |
memhard |
256 MiB cache (2^16 chains of 64 ChaCha12 blocks), mixer parameters, 8-round item derivation with the 32 lanes interleaved, MemhardCpu::fetch |
cpuFillCache, MixParams, deriveItems, MemhardCPU |
verify |
the 32-lane warp interpreter, DatasetMode::{ClosedForm, MemoryHard}, Epoch, hash_warp, verify_block |
cpuWarp, DatasetSource |
emit |
Metal, CUDA and OpenCL source, program.h, memhard.h, vectors.h, program.json, vectors.json, export_pack |
generateMSL, memhardMSL, emitMemhardCore, generateCUDA, generateOpenCL, exportPack |
The API the fork calls
use igneum_pow::{Epoch, DatasetMode};
// Once per epoch and day: generates the program and fills the 256 MiB cache (about 0.18 s on one core).
let epoch = Epoch::memory_hard("igneum-genesis", "2026-10-03");
let h: u64 = epoch.hash(nonce); // one nonce (computes its aligned 32-nonce warp)
let w: [u64; 32] = epoch.hash_warp(base_nonce); // one warp
let ok: bool = epoch.verify_block(nonce, target_u64);
// Miner programs for the epoch, byte-identical to the Swift exporter.
let pack = igneum_pow::emit::export_pack(&epoch, "2026-10-03", "igneum node");
pack.write_to(std::path::Path::new("out"))?; // kernel.cu, kernel.cl, program.metal, memhard.h, ...
Epoch is Send + Sync; build one and share it. DatasetMode::ClosedForm reproduces the two old packs
(igneum-genesis, igneum-hourly) and is not memory-hard. The hash is 64 bits; the fork maps it into its
256-bit target space in consensus/pow/src/lib.rs.
CLI
cargo build --release
./target/release/igneum-pow bench --seed igneum-genesis [--warps 20] [--closed-form] [--day 2026-10-03]
./target/release/igneum-pow export --seed igneum-genesis --out <dir> [--closed-form]
./target/release/igneum-pow hash --seed igneum-genesis --nonce 4103
Tests
cargo test (23 tests, 0.7 s after compile; the dev profile is optimised so the cache fill is quick):
| Check | Pack | Result |
|---|---|---|
| program.json instruction by instruction, op mix, loads per hash | igneum-genesis, igneum-genesis-mh, igneum-hourly | 3 x 64 match |
| Mixer parameters (key, rot, mul, rc) | igneum-genesis-mh | match |
Cache head, last line, FNV-1a 64 48c4f5bf24166b2e |
igneum-genesis-mh | match |
Dataset head (16), [MASK], 64 sampled words |
all three | match |
| 96 hash vectors (3 warps x 32 lanes) | igneum-genesis-mh | 96/96 |
| 96 hash vectors | igneum-genesis, igneum-hourly | 96/96 each |
| kernel.cu, program.metal, kernel.cl, program.h byte-identical | all three | identical |
| memhard.h, memhard.metal byte-identical | igneum-genesis-mh | identical |
| program.json byte-identical (after the fix below) | all three | identical |
| vectors.json, vectors.h byte-identical apart from the provenance string | all three | identical |
An independent diff -r of igneum-pow export output against the checked-in packs shows the same two lines
only: the provenance string and the "item" line.
One deliberate difference: proto-cuda/packs/igneum-genesis-mh/program.json as written by the Swift is not
valid JSON (main.swift line 1291 uses jhex inside the "item" string, so the cache line mask is quoted inside a
quoted string). The Rust emitter writes 0x003fffff bare; the test normalises that one line before comparing.
A node must hand miners valid JSON, so the Rust side does not reproduce the defect.
Measured, 3 October 2026, Apple M5 Max, one core, release build
| Step | Rust | Swift (MEMHARD.md) |
|---|---|---|
| Cache fill, 256 MiB, 65,536 chains x 64 ChaCha12 blocks | 175 to 181 ms (5 quiet runs; 200 ms once with another build running) | 184.5 to 190.6 ms (C++ host reference 161.5) |
| CPU verify per warp, igneum-genesis, 104 loads, 3,328 items, avg of 20 | 0.441 ms | 0.649 ms |
| igneum-genesis/epoch1, 104 loads | 0.411 ms | 0.631 ms |
| igneum-genesis/epoch2, 112 loads | 0.488 ms | 0.701 ms |
| igneum-second-seed, 104 loads | 0.482 ms | 0.801 ms |
| igneum-second-seed/epoch1, 144 loads, 4,608 items | 0.579 ms | 1.205 ms |
| Cold single warps across the five seeds | 0.41 to 0.87 ms | 1.16 to 2.11 ms |
| Closed form, igneum-genesis | 0.002 ms | 0.017 ms |
The Rust verifier is 1.4x to 2.1x faster than the Swift one per warp; the registers are kept register-major
(r[reg][lane]) so the lane loops vectorise, and the item derivation interleaves the 32 lanes round by round as
the Swift does. The 10 ms gate holds with a margin of about 17x on the steady figure and 11x on the worst cold warp.
Not done here
- No GPU. The vectors tie this crate to the Metal and CUDA results through the packs; nothing here runs a kernel.
- The epoch seed is still a string.
seed::seed_words_from_bytesis where the VDF output will enter. - The 256-bit target mapping and the
kaspa_pow::Stateshape belong to the fork, not to this crate.