22 KiB
Igneum protocol specification, section 4: epoch and era seeds through the class-group VDF
Spec version 0.1, 3 October 2026. Status of this section: Measured for the VDF primitive on one machine (proto-vdf/, docs/bench-log.md entry "proto-vdf, Wesolowski VDF"); the ERA path Implemented in the node on 7 October 2026 (era VDF lane, fork branch era-vdf-node on the 0.3.19 line: consensus/core/src/era_vdf/, consensus/src/processes/era_vdf.rs; record docs/analysis/era-vdf-2026-10-07.md), behind era_vdf_activation_daa (never on every network until the project lead sets it per network), with the scheme byte of 4.2 and the measured reference rates of 4.6; the EPOCH path (4.3) still Designed. The class-group code has not been reviewed by a second cryptographer (O-4.1; the node's port is a second implementation of the same algorithms on a fixed-width integer, checked against num-bigint and against the textbook composition, not a review).
4.1 Why a delay
The hourly program is generated from a seed, the seed comes from a checkpoint, and the checkpoint commits to the blocks before it. The miner who finds the last block before a checkpoint can compute the program that block implies, benchmark it on its own fleet, and withhold the block if the program is bad for it. The review priced this at roughly 130 to 1 for a 30% miner; the prototype's own model gives 6 to 15 to 1 on the burned block for 10% to 40% miners (proto-vdf/README.md, grinding table, Measured by Monte Carlo over 2,000,000 epochs against the analytic model, agreement 0.03 blocks). Either way the sign is positive: with no delay, grinding pays and favours the largest miner.
A miner has about 2 s to decide whether to publish a block under a 1 block/s DAG (approximate, from the block rate). The delay only has to exceed that by a margin no hardware advantage can close. With the delay, withholding has the same expected program as publishing and only burns the block: gain 0 in every row of the grinding table.
4.2 The primitive
Wesolowski VDF (Efficient Verifiable Delay Functions, EUROCRYPT 2019) in the class group of an imaginary quadratic field with a prime discriminant derived from the input. Chia's construction (vendor/chiavdf, commit 7e62ce14, 29 Sep 2026): no trusted setup, group order unknown to everyone, a fresh discriminant per input so nothing can be precomputed. NUDUPL and NUCOMP ported from chiavdf's qfb_nudupl and qfb_nucomp with the Lehmer partial xgcd; the textbook Cohen 5.4.7 composition and plain duplication are kept as oracles and agree on 15,000 random cases (Measured, proto-vdf/README.md).
input (32 bytes)
D = -HashPrime(tag_D || input), 1024 bits, |D| prime, D = 1 mod 8
x = (2, 1, (1 - D) / 8), the generator form of Cl(D)
y = x^(2^T) T sequential squarings
l = HashPrime(tag_l || x || y || T), 256 bits Fiat-Shamir challenge
pi = x^floor(2^T / l)
output = SHA-256(tag_out || input || T || y)
proof = (T, y, pi)
verify: rederive D and x, recompute l, check pi^l * x^(2^T mod l) == y, recompute output
Tags (Implemented in proto-vdf/src/seed.rs for the epoch path): tag_D = "igneum-epoch-discriminant", tag_l = "igneum-vdf-challenge", tag_out = "igneum-program-seed". The era path (Implemented, kaspa_consensus_core::era_vdf) uses "igneum-era-discriminant" and "igneum-era-seed", with the scheme byte in the seed preimage: E_n = SHA-256("igneum-era-seed" || scheme || input || T_be64 || y). Prime |D| kills the 2-torsion, the low-order element Wesolowski must exclude. The node derives D itself once per era (the search is a per-era one-off, measured in 4.6) and verifies with the group held; HashPrime's byte layout is this specification's (a 64-bit big-endian counter suffix, not chiavdf's in-place increment), which O-4.5 keeps open only for tooling compatibility.
The scheme byte and the fallback (7 October 2026, era VDF lane; mission item 8, docs/analysis/mission/future.md 7.4 item 5). The era VDF is one of two schemes behind a genesis byte vdf_scheme (Params::vdf_scheme, in the digest with the activation and T once the activation is set), the same shape as the vote keys' sig_scheme: a flip is a class change under the 95 percent signal with a floor height, never a fork. The bytes are coordinated with the genesis-forward lane's sig_scheme (0 = BLS12-381 there; the two bytes are separate fields with the same mechanism, era_vdf::scheme_known and igneum::sig_scheme_of_class the two tables a class change fills).
| Byte | Scheme | Delay | Proof | Verify | Why it exists |
|---|---|---|---|---|---|
| 0 | Wesolowski over the class group of a 1,024-bit prime discriminant derived from the input (this section) | T squarings | (y, pi), 516 bytes (13 bytes of framing on the wire, EraVdfProof::to_bytes) |
two short exponentiations, milliseconds (4.6) | the production choice: no trusted setup, Chia precedent |
| 1 | SHA-256 hash chain: `s_0 = SHA-256("igneum-era-hash-chain" | input), s_{i+1} = SHA-256(s_i), y = s_T` |
T hashes | the end state, 32 bytes |
The fallback costs every verifier the full delay, which 4.5 already asks of every mining node (one core for an hour per era); what it loses is the 5 ms check for light clients and syncing nodes, who then take E_n from the chain's own work (a block mined under the wrong E_n fails its proof of work under the right program) or recompute. The flip is sized, not scheduled: T for scheme 1 at the reference core is in 4.6.
| Parameter | Value | Label |
|---|---|---|
| Group | Class group, 1024-bit prime discriminant from the input | Designed (production choice) |
| Fiat-Shamir prime | 256 bits | Designed (Chia uses 264; 2x the 128-bit level) |
| Proof | (T, y, pi), 516 bytes with the prototype serialisation (sign byte plus fixed-width a and b) | Measured. The serialisation should become chiavdf's compact encoding before any wire format is frozen (Open, O-4.5) |
| Proof plan | 12-bit digits, at most 65,536 checkpoints (17 MB) during evaluation | Implemented; proving costs 12 to 13% of evaluation single-threaded and parallelises over residue classes |
Measured on the Apple M5 Max, one core, rustc 1.69.0, GMP 6.3.0, 3 October 2026 (docs/bench-log.md, proto-vdf entry):
| Group | Squarings/s | T for 600 s | T for 3,600 s | Verify | Proof bytes |
|---|---|---|---|---|---|
| Class group, 1024-bit D | 163,000 | 98.0 million | 588 million | 4.5 ms (12.6 ms including deriving D) | 516 |
| Class group, 2048-bit D | 83,500 | 50.1 million | 301 million | 8.0 ms | 1,028 |
| RSA-2048 (trusted-setup stand-in, timing only, never for production) | 1,257,000 | 754 million | 4.53 billion | 1.4 ms | 512 |
Full-length run, class 1024: T = 97,126,043, evaluation 585.4 s at 165,900 sq/s, proof 9.1 s on 12 threads (56.8 s on one), verify 4.47 ms. Determinism: the same checkpoint hash gave the same seed and identical proof bytes in two processes at T = 1,000,000; a wrong checkpoint, a flipped seed bit and T + 1 are all rejected.
4.3 The epoch seed pipeline
Designed. Epoch e is the DAA-score interval [3,600 e, 3,600 (e + 1)) (section 1.12).
- Seed checkpoint.
C(e)is the highest-index checkpoint (section 3, C1) whose checkpoint block has DAA score at most3,600 e - 1,200: the latest checkpoint at least 20 minutes of DAA time before the epoch starts. The checkpoint block hash is Kaspa's full header hash, which covers the nonce, as the grinding defence requires (proto-vdf/README.md: a hash that covers only the body would let a miner start the VDF while still searching nonces). - Evaluation.
input = hash(C(e)),T = T_epoch, run 4.2.program_seed_eis the 32-byte output;proof_eis (T, y, pi). - Program.
S_e = seed_words_from_bytes(program_seed_e)(section 1.3.1), program =generate_from_words(S_e)(section 1.4). The 1,200-s lead is 2x the reference evaluation time, so a core half as fast as the reference still finishes before the epoch (4.6). The lead andT_epochare fixed whatever the epoch length of section 1.12: at the floor of 600 DAA s the checkpoint is two epochs back and the program is known one full epoch ahead; at the base it is known for the last sixth of the previous epoch (docs/plans/epoch-length.md, section 3). - Header. Every header carries
seed_source = hash(C(e))for its own epoch (section 2.4). A header is valid under the lottery only if itsseed_sourceis a block on its own selected chain at the blue score of checkpoint indexi(C(e)), and its PoW verifies under the program derived from that block. The proof is not in the header: a node verifiesproof_eonce per epoch (4.5 ms) and cachesS_e.
Determinism of step 1 is the point of the rule: which block is "the checkpoint at blue score 30 i" is a function of the header's own past, so two nodes validating the same header derive the same program, and a header mined under a reorged-away checkpoint names a block that is not on its chain and is invalid. Whether C(e) must be certified (section 3) or merely be the selected-chain block at that blue score in the header's past is Open (O-4.3): requiring certification couples mining to finality liveness (a stall longer than the lead would stop the program from being derivable), which the design document accepts ("an epoch cannot start without a valid proof") and this specification argues against, because the chain is meant to keep running on plain GHOSTDAG through a finality pause (section 3.7 item 2). The proposal: the selected-chain block at that blue score, certified or not, deep enough (1,200 DAA s plus d) that a reorg across it is a merge-depth-scale event.
4.4 The era seed pipeline
Implemented in the node on 7 October 2026 (era VDF lane; design document: "The era draw applies a one-hour delay function to the hash of all blue blocks in the day ending at the last certified checkpoint one epoch before the boundary"; consensus/src/processes/era_vdf.rs, consensus/core/src/era_vdf/). Era n is the DAA-score interval [15,552,000 n, 15,552,000 (n + 1)) (section 1.12; igneum::pow_era_blocks, a constant on every network that a private test network's override file may shorten with pow_era_blocks and pow_era_lead, in the digest when it does). The pipeline applies to every era whose start is at or above Params::era_vdf_activation_daa and never to era 0 (genesis seeds it); below the activation the devnet stand-in of docs/plans/era-layout.md section 2 stands (the cut block's hash itself).
- The cut block. The cut of era n is the DAA score
15,552,000 n - 7,200(2 hours of lead, 2x the 1-hour evaluation;igneum::pow_era_seed_score).C_era(n)is the last selected-chain block below the cut on the chain of the header being validated (class_signal::seed_below, the block the stand-in used asE_n). This is the checkpoint block the cut rule names under the O-4.3 decision of 3 October 2026 (3.11 item 6: the seed checkpoint is the chain block the lead rule names, certified or not, so a finality pause never stops the program): a function of the header's own past, so two nodes validating one header derive oneC_era(n), and a header on a chain that reorged across the cut names another block and is validated under that block's seed. BindingC_era(n)to the certified checkpoint instead (the certificate carried by a block in the header's past, the design document's wording) is a change to one function,EraVdfManager::cut_block; it would couple the era seed to finality liveness (a pause across the cut leaves the era without a seed) and needs a deterministic rule for a certificate that lands late, which the record (docs/analysis/era-vdf-2026-10-07.md, section 4) spells out for the finality lane; the grinding defence does not depend on it, since the delay is what makes any candidate's draw unknowable. - The input.
input = Hash(chain_id || n_le64 || h_1 || h_2 || ... || h_m)whereh_1..h_mare the hashes of the blue blocks inC_era(n)'s past with DAA score in(daa(C_era(n)) - day, daa(C_era(n))],daybeing the dataset day (pow_day_ms / 1,000, 86,400 DAA s on the devnet) at the network's block rate, in ascending (blue score, hash) order, andHashthe chain's BLAKE2b-256 keyed"IgneumEraVdfInput"(era_vdf::era_vdf_input;chain_idthe prefixed network name the finality votes use). Walked as the class signal walks a window: every chain block's mergeset blues once each, down to the merge depth below the day's start. A function ofC_era(n)'s past, memoised per cut block. - The delay.
era_vdf::evaluate(scheme, input, T_era)withscheme = Params::vdf_schemeandT_era = Params::era_vdf_t(4.6): under scheme 0, 4.2 with the era tags; under scheme 1, the hash chain. Every node runs it itself on one thread (the prover's residue classes on up to 8), started by the virtual processor once the sink is a quarter of the lead past the cut (the epoch seed's confirm margin: right at the cut the selected chain still flips between sibling tips), and persisted (DbEraVdfStore, one row per era: input, proof,E_n) when it ends, an hour before any header of the era can exist under the 2x lead. A header that arrives before the node holds the record (a node syncing across an era boundary with no peer's proof, 4.5) waits for the evaluation on the header processor's thread. - The seed.
E_n = SHA-256("igneum-era-seed" || scheme || input || T_be64 || y), which the era draw of section 1.13.1 consumes as its only randomness (O-4.8: the draw's preimage is"igneum-era/" || E_n, nothing else; the node hands the generatorE_nalone throughEpochSeeds::era). Every block template reportsE_nasera_seed, the input, the scheme, T and the evaluation state (PowEpochInfo), and reports noera_seedwhile the node is still evaluating, on which the miner holds (igneum-miner: "era VDF: the node is still evaluating").
The gate the attack pass set (F7 sub-row a, docs/analysis/attack-pass/f7-era.md): the re-roll harness fires with the VDF off and is silent with it on. Measured 7 October 2026 on the box, tools/era-vdf/reroll.mjs against the real era cut on a fast-time network (era 120 DAA, lead 20): the record's section 3 carries both runs.
4.5 Who evaluates, who verifies
Every mining node evaluates both VDFs itself: one CPU core for 10 minutes each hour (17% of one core) and once for an hour each era, plus 17 MB of RAM if it also proves. The GPU is untouched. The proof exists for nodes that did not evaluate (light clients, syncing nodes, late starters): 516 bytes and 4.5 ms per epoch. Proofs gossip as their own message type and are also retrievable by seed_source from any peer.
There is no timelord role and no race: unlike Chia, the chain does not wait for the VDF; it uses a VDF output that was fixed 20 minutes (epoch) or 2 hours (era) earlier. "No node evaluates" means no miner is running a CPU, which means nobody is mining.
Implemented for the era (7 October 2026): every node evaluates (4.4 step 3) and holds the record; EraVdfManager::submit accepts a record from outside when its input is the one this node derives for the era, its scheme and T are the network's, its proof verifies and its output is the seed of the proof. What is still owed before era 1 of any network with the switch set (180 days after that network's genesis at the earliest): the P2P message type that gossips the record and serves it to a syncing peer, and the RPC that imports one; until then a node syncing across an era boundary evaluates the delay itself on its header processor's thread (4.4 step 3).
4.6 T from a reference core rate, fixed at genesis
Designed (proto-vdf/README.md, parameter recommendation). Before genesis, run vdf bench (or chiavdf's vdf_bench) on every devnet node type that will mine, take the fastest honest single-core NUDUPL rate observed as r_ref (squarings per second), and fix at genesis:
| Constant | Definition | On the M5 Max core (r_ref = 163,000) |
|---|---|---|
T_epoch |
600 x r_ref | 98.0 million |
T_era |
3,600 x r_ref | 588 million |
Choosing the fastest honest core, not the median, keeps the stated 10 minutes an upper bound for honest nodes and leaves the attacker margin intact. T MUST NOT be derived from on-chain timing, which is manipulable. T is a genesis constant; hardware will get faster over the years and the margin will erode slowly from 300x, which a fixed T covers for decades, and the upgrade path of section 5.7 exists if it is ever needed.
The era constants as set (7 October 2026, era VDF lane; docs/analysis/era-vdf-2026-10-07.md section 2). The reference rate is the NODE's own evaluator (the fixed-width-integer class group of kaspa_consensus_core::era_vdf), not chiavdf's, because an honest node runs the node's code and must finish inside the lead: the two rules are "T from the honest evaluator's rate" and "the margin against the fastest prover anyone can run". Measured on one core of igneum-build-2 (AMD EPYC 9454P, nice 10, the box under load):
| Constant | Value | Basis |
|---|---|---|
ERA_VDF_REFERENCE_SQUARINGS_PER_S |
30,000 | the node's evaluator at 30,589 and 40,117 squarings/s in two runs; the lower run is the reference, so the hour is an upper bound at it |
T_era, scheme 0 (ERA_VDF_T_CLASS_GROUP, Params::era_vdf_t) |
108,000,000 squarings | 3,600 x 30,000; 45 to 60 min on the box core, about 72 min on a 2019-class core (the F6 calibration), inside the 2-hour lead |
ERA_VDF_REFERENCE_HASHES_PER_S |
16,000,000 | SHA-256 chain at 16.2 and 17.0 million/s (SHA-NI) |
T_era, scheme 1 (ERA_VDF_T_HASH_CHAIN) |
57,600,000,000 hashes | 3,600 x 16,000,000 |
| Verify, scheme 0 | 21.9 to 22.6 ms with the group held; the discriminant derivation 161 to 167 ms once per era | the 10-ms gate is missed by 2.2x on the box core; the record's section 5 says what closes it (O-4.6's reducer, a limb-level NUDUPL, or GMP behind a feature on x86-64) |
| Prove, scheme 0 | about 14 percent of the evaluation single-threaded, parallel over residue classes | at T 401,167: eval 10.0 s, prove 1.6 s |
| The fastest prover measured | chiavdf's NUDUPL over GMP at 208.8 K squarings/s on the same core (its AVX-512 IFMA path was not established in this build) | the delay at that prover: 517 s, 517x the 1-s block interval and 259x the 2-s window; at a hardware prover 10x faster, 52 s, still 26x the window |
The epoch constants (T_epoch) stay as this section designs them until the epoch path is implemented; a measured T_epoch would be 600 x the same reference rate, 18,000,000 squarings.
| Attacker evaluator speed vs reference | Epoch delay | Era delay | Beats the 2-s window |
|---|---|---|---|
| 1x | 600 s | 3,600 s | no, margin 300x |
| 2x | 300 s | 1,800 s | no, 150x |
| 10x | 60 s | 360 s | no, 30x |
| 100x | 6 s | 36 s | no, 3x |
| 300x | 2 s | 12 s | epoch yes, era no |
Chia's and the Ethereum Foundation's VDF hardware efforts targeted single to low double-digit speedups over CPUs (approximate, from memory, proto-vdf/README.md). chiavdf's AVX-512 IFMA assembly path is faster than the prototype's 163,000 sq/s and the x86 devnet nodes will set r_ref higher than this Mac (open item 2 there).
4.7 Fallback for a node without the seed at an epoch boundary
Open (O-4.4). A node that has not finished evaluating program_seed_e when epoch e starts cannot build the program, so it cannot mine the new program or validate new blocks' PoW until it has y. It can still receive and relay. Two options:
| Option | Rule | For | Against |
|---|---|---|---|
A (proposed by proto-vdf/README.md) |
The previous epoch's program stays valid for the first N blocks of epoch e, and each header names the program seed it mined under | A slow node keeps mining through the boundary | Two programs are valid at once for N blocks; a miner may pick the better of the two, which reintroduces a small choice the VDF was meant to remove; N is a new parameter |
| B (this specification's preference) | No consensus fallback. The 1,200-s lead is 2x the reference evaluation; a node slower than that takes (y, pi) from any peer and verifies in 4.5 ms; seed_source in every header tells it which proof to ask for |
No second valid program, no new parameter; the proof is 516 bytes | A node isolated from all peers and slower than 2x the reference loses up to the remainder of its own evaluation. Honest nodes evaluate at r_ref or take the proof from peers, so the case is a slow node that is also isolated |
Decision at gate 3 with the devnet, where the time to first block after an epoch boundary is measured on the slowest node type.
4.8 Open items from the prototype
Flagged, not sized (genesis forward-compatibility, 7 October 2026, docs/analysis/mission/future.md 7.2 and docs/design/genesis-forward.md section 4): a cryptographically relevant quantum computer computes the class-group order by Shor, which removes the sequentiality assumption of the Wesolowski VDF, so an attacker with one grinds the hourly program seed; the fallback is a hash-chain delay behind the same version byte that moves the signature scheme (a class change by the 95 percent signal), designed and sized when the scheme flip is scheduled, not before. Nothing in the pipeline of 4.3 and 4.4 changes today.
Carried into section 6: external review of classgroup.rs against chiavdf (O-4.1); reference core choice (O-4.2); whether C(e) must be certified and the header field (O-4.3); the fallback (O-4.4); HashPrime layout, carrying D in the proof, compact form encoding (O-4.5); reduction only when a exceeds 8 limbs as chiavdf does, a further speedup to port (O-4.6); no fuzzing of deserialize on hostile bytes beyond validity checks, no measurement on NVIDIA or AMD hosts' CPUs (O-4.7).