Class group (1024-bit prime discriminant from the checkpoint hash, chiavdf construction, NUDUPL/NUCOMP/Lehmer xgcd ported from vendor/chiavdf) and an RSA-2048 trusted-setup stand-in for timing. eval, block prover, verify, epoch_seed/verify_epoch_seed, grinding model, README with measurements and the parameter recommendation, bench-log entry. M5 Max: class 163k sq/s (T 98 M for 10 min, 588 M for 1 h), verify 4.5 ms, proof 516 bytes; full 10-min runs for both groups; grinding gain for a 30% miner +3.62 blocks/epoch with no delay, 0 with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| .cargo | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
proto-vdf: a verifiable delay between the checkpoint and the mining program
Prototype for Igneum finality rule v2: "epoch seed = 10-min class-group VDF of a certified
checkpoint, era draw = 1-h VDF". Rust, GMP through rug, 3 October 2026, Apple M5 Max.
The problem
The hourly mining program is generated from a seed. The seed comes from a certified checkpoint, and the checkpoint commits to the blocks merged 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 a bad one for it. The review priced this at roughly 130 to 1 for a 30 percent miner (the review's figure, model-dependent; this prototype's own model gives 13.5 to 1, see the grinding table). The sign is what matters: with no delay, grinding pays.
The fix is to make the program unknowable for far longer than the time a miner has to decide whether to publish a block (about 2 s at 1 block/s), while keeping it cheap for everyone to check the answer. That is a verifiable delay function.
Construction
checkpoint_hash (32 bytes, certified by the finality vote)
-> D = -HashPrime("igneum-epoch-discriminant" || checkpoint_hash), 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, nobody can parallelise this
-> pi = x^floor(2^T / l), l = HashPrime("igneum-vdf-challenge" || x || y || T), 256 bits
-> program_seed = SHA256("igneum-program-seed" || checkpoint_hash || T || y)
epoch_seed(checkpoint_hash) -> (program_seed, proof) does all of it. proof = (T, y, pi),
524 bytes. verify_epoch_seed(checkpoint_hash, program_seed, proof) -> bool rederives D and x,
recomputes l, checks pi^l * x^(2^T mod l) == y, and recomputes the seed. Two exponentiations
with 256-bit exponents, about 700 group operations, single-digit milliseconds.
Why these parts:
- Wesolowski (Efficient Verifiable Delay Functions, EUROCRYPT 2019). Proof is one group
element. Verification is independent of T. The proof is built from checkpoints kept during
evaluation in about T/12 group multiplications (12-bit digits of the quotient, bucketed per
residue class, the same digit formula as chiavdf
Prover::GetBlock), so proving costs 13 percent of evaluating and parallelises over residue classes. - Class group of an imaginary quadratic field with a prime discriminant derived from the checkpoint. No trusted setup. The group order is unknown to everyone, including whoever wrote the code. Chia Network runs its timelords on exactly this construction (vendor/chiavdf/src/create_discriminant.h, vdf_new.h, nucomp.h, proof_common.h, prover_impl.hpp, verifier.h). Prime |D| also kills the 2-torsion, which is the low-order element Wesolowski needs to exclude. A fresh D per checkpoint means nothing can be precomputed before the checkpoint is certified.
- Squaring is NUDUPL and multiplication is NUCOMP, ported line by line from chiavdf's
qfb_nuduplandqfb_nucomp(William Hart's FLINT code), with the Lehmer-accelerated partial extended gcd fromxgcd_partial.c. The textbook Cohen 5.4.7 composition and the plain duplication formula are kept as test oracles;vdf selftestchecks the fast paths against them on thousands of random cases and checks the block prover against the naive O(T) prover. - An RSA-2048 group is included as a TRUSTED-SETUP STAND-IN for timing only. Its factors come from a public seed, so the trapdoor is public by construction. Anyone holding the factors skips the delay. Not for production.
Build and run
Homebrew cargo 1.69.0 on this Mac predates edition 2024, so rug, az and gmp-mpfr-sys
are pinned to older versions and link against Homebrew GMP 6.3.0 (.cargo/config.toml sets
the library path).
cd proto-vdf
cargo build --release
./target/release/vdf selftest
./target/release/vdf bench --seconds 5
./target/release/vdf eval --group class --minutes 10 --threads 12
./target/release/vdf demo --checkpoint <64 hex> --t 1000000
./target/release/vdf grind --epochs 2000000
Measurements (Apple M5 Max, macOS Darwin 25.6.0, rustc 1.69.0, GMP 6.3.0, 3 Oct 2026)
Single core, one squaring after another, 4 to 5 s samples. "T(10 min)" is the squaring count that takes 600 s of wall time at that rate.
| Group | Squarings/s | T(10 min) | T(60 min) | Verify | Proof bytes |
|---|---|---|---|---|---|
| Class group, 1024-bit prime D (production choice) | 163,000 | 98.0 million | 588 million | 4.5 ms | 516 |
| Class group, 2048-bit prime D | 83,500 | 50.1 million | 301 million | 8.0 ms | 1,028 |
| RSA-2048 stand-in (trusted setup) | 1,257,000 | 754 million | 4.53 billion | 1.4 ms | 512 |
Prover and verifier at short T, 12-bit digits, checkpoints capped at 65,536:
| Group | T | Eval | Prove 1 thread | Prove 12 threads | Prove / eval | Verify |
|---|---|---|---|---|---|---|
| Class 1024 | 131,072 | 0.80 s | 0.10 s | 0.10 s (gamma = 1, nothing to split) | 0.13 | 4.5 ms |
| Class 2048 | 32,768 | 0.39 s | 0.05 s | 0.05 s | 0.14 | 8.0 ms |
| RSA-2048 | 1,048,576 | 0.83 s | 0.10 s | 0.07 s | 0.12 / 0.09 | 1.4 ms |
Full-length runs (T chosen from a 3 s rate sample, then evaluated end to end, proved, verified):
| Group | T | Eval wall | Rate during eval | Prove (12 threads) | Verify |
|---|---|---|---|---|---|
| RSA-2048 | 756,516,411 | 607.1 s | 1,246,000 sq/s | 9.0 s (71.2 s on 1 thread, 1.5 percent of eval) | 0.88 ms |
| Class 1024 | 97,126,043 | 585.4 s | 165,900 sq/s | 9.1 s (56.8 s on 1 thread, 1.6 percent of eval) | 4.47 ms |
The class run finished in 585 s rather than 600 because the RSA run that shared the chip for its first half ended, and the single-core rate rose by about 2 percent. Rates in the first table were sampled with one process alone.
Seed pipeline determinism (vdf demo, T = 1,000,000, two separate processes):
checkpoint 7a007ef8...869c gave program_seed 3a5f8921...46a7 in both processes, identical
proof bytes, 6.9 s eval plus prove, verify 12.6 ms including the 1024-bit prime search for D.
A different checkpoint gave 5bdc8386...4eed. Wrong checkpoint, flipped seed bit and T+1 are
all rejected.
Rate history inside this session, for the record: Cohen composition with a textbook reducer
45,000 sq/s; plus NUDUPL with plain-division partial gcd 59,000; plus Lehmer partial gcd
163,000. chiavdf's assembly path (asm_*.h, AVX-512 IFMA on x86) is faster still; Chia
mainnet timelords are commonly quoted in the low hundreds of thousands of iterations per
second, approximate, from memory, not measured here.
Does a faster attacker matter
| Attacker evaluator | Epoch delay (T set for 600 s on the reference core) | Era delay (3,600 s) | Beats the 2 s window |
|---|---|---|---|
| 1x (reference) | 600 s | 3,600 s | no, margin 300x |
| 2x | 300 s | 1,800 s | no, margin 150x |
| 10x | 60 s | 360 s | no, margin 30x |
| 100x | 6 s | 36 s | no, margin 3x |
| 300x | 2 s | 12 s | epoch yes, era no |
A 2x faster evaluator does not change the defence. The delay has one job: exceed the time a miner has before an unpublished block is dead, which is about 2 s under a 1 block/s DAG. It does so by 300x at the epoch and 1,800x at the era. The margin is there so that no plausible hardware advantage (Chia's and the Ethereum Foundation's VDF ASIC efforts targeted single to low double digit speedups over CPUs, approximate, from memory) ever gets close. The delay also does not have to be exact: a node that finishes in 5 min or 20 min gets the same y.
One requirement on the rest of the design: the checkpoint hash the VDF is seeded from must commit to the full block hash (header plus nonce), not only to the block body. Otherwise a miner could start the VDF while still searching nonces.
Grinding table (vdf grind --epochs 2000000)
Model: 3,600 blocks per hourly epoch; a miner's hash-rate advantage on a program is uniform on [0, 15 percent] (the review's measured range); the grinder keeps a candidate only if the advantage is in the top quartile (at least 11.25 percent), pays one block reward per withheld block, and with probability 1 - s someone else's block becomes the seed first. Revenue in an epoch with advantage a is 3600 s(1+a)/(1+sa).
| Share | Honest revenue (blocks/epoch) | P(grind lands) | Withheld blocks | Gain, no delay | Gain, no delay (Monte Carlo) | Gain | Gain, with delay | Gain to cost |
|---|---|---|---|---|---|---|---|---|
| 0.1 | 384.1 | 0.027 | 0.081 | +0.40 | +0.41 | +0.105% | 0 | 6.0 : 1 |
| 0.2 | 762.4 | 0.059 | 0.176 | +1.66 | +1.63 | +0.218% | 0 | 10.4 : 1 |
| 0.3 | 1135.1 | 0.097 | 0.290 | +3.62 | +3.62 | +0.319% | 0 | 13.5 : 1 |
| 0.4 | 1502.3 | 0.143 | 0.429 | +6.06 | +6.05 | +0.403% | 0 | 15.1 : 1 |
With the delay the grinder learns nothing about the candidate's program inside the window, so withholding has the same expected program as publishing and only burns the block. Gain is 0 and a rational miner publishes. The absolute gains without the delay look small per epoch, but they are free money at a 6 to 15 to 1 return on the burned block, they compound over 8,760 epochs a year, and they favour the largest miner. The review's 130 to 1 used a different cost model; both say the same thing about the sign.
Parameter recommendation
| Parameter | Value | Why |
|---|---|---|
| Group | Class group, 1024-bit prime discriminant derived from the checkpoint hash | No trusted setup, Chia precedent, 4.5 ms verify, 516-byte proof |
| Epoch T | 98 million squarings | 600 s on this M5 Max core at 163k sq/s. Reference core to be fixed on the devnet, see below |
| Era T | 588 million squarings | 3,600 s on the same core |
| Fiat-Shamir prime | 256 bits | Chia uses 264; 2x the 128-bit security level |
| Proof plan | 12-bit digits, at most 65,536 checkpoints (17 MB) | Prove in 13 percent of eval time, parallel over residue classes |
| Lead time | Seed epoch n from the checkpoint certified 20 min before epoch n starts | 2x the reference evaluation time, so a core half as fast still finishes before the epoch |
| Era lead time | 2 h before the era boundary | Same 2x margin on the 1 h delay |
How to set T from the devnet: 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 the reference
rate r_ref, and set epoch T = 600 r_ref, era T = 3,600 r_ref, fixed at genesis. Choosing the
fastest honest core, not the median, keeps the stated 10 minutes an upper bound for honest
nodes and leaves the 300x margin intact against attackers. Nodes slower than the reference
either finish later (the lead time covers 2x) or take y and pi from a peer and verify in 5 ms.
Hardware will get faster over the years and the margin will erode slowly from 300x; a fixed T
covers decades, and the 90 percent miner-signalled upgrade path exists if it is ever needed.
Do not derive T from on-chain timing, which is manipulable.
Cost to a miner: one CPU core for 10 min each hour (17 percent of one core) and 17 MB of RAM if it also proves. The GPU is untouched.
What happens if no node evaluates in time
Every miner evaluates the VDF itself; the proof exists for nodes that did not (light clients, syncing nodes, 516 bytes and 5 ms per epoch). "No node evaluates" means no miner is running a CPU, which means nobody is mining. There is no race and no timelord role: unlike Chia, the chain does not wait for the VDF, it only uses the VDF output as a seed that was fixed 20 minutes earlier. A node that is late to compute the seed cannot mine the new program until it has y, but it can still receive, verify and relay blocks once it has y from any peer. If a fallback is wanted anyway, the clean one is: the previous epoch's program stays valid for the first N blocks of the new epoch and each header names the program seed it mined under. This is an open design item, not needed for the grinding defence.
Open items
- Class group implementation review. The NUDUPL, NUCOMP and Lehmer partial-gcd ports agree
with the textbook algorithms on 15,000 random cases and with the naive prover on three
sizes, but a cryptographer other than the author should read
classgroup.rsagainst vendor/chiavdf/src/nucomp.h and xgcd_partial.c. Reduction runs every squaring; chiavdf reduces only whenaexceeds 8 limbs, which is a further speedup to port. The form serialization (sign byte plus fixed width a and b) should become the chiavdf compact encoding before any wire format is frozen. - Reference evaluator speed. 163k sq/s is this Mac with this code. chiavdf's assembly path and any x86 AVX-512 IFMA machine will differ. Measure on the devnet nodes and fix r_ref.
- Hash-to-prime details. The 1024-bit discriminant search averages 17 ms (prime density), acceptable, but the verifier pays it too; the proof could carry D with the verifier checking only that D matches the hash, as chiavdf does. Also decide whether to mirror chiavdf's byte layout for HashPrime exactly so chiavdf tooling can be used.
- Fallback rule for a node without the seed at epoch start (above), and whether headers should name the program seed.
- Checkpoint hash definition must commit to full block hashes (header plus nonce).
- Era draw: the same code with T x 6. The era draw samples parameters from chain state; check that the VDF output enters that sampling as the only randomness.
- Not done: no constant-time anything (not needed, all inputs public), no fuzzing of
deserializeon hostile bytes beyond the validity checks, no measurement on NVIDIA or AMD hosts' CPUs.
Files
| File | What |
|---|---|
src/classgroup.rs |
Forms, reduction, Cohen 5.4.7 compose, duplication formula, NUDUPL, NUCOMP, Lehmer partial xgcd, discriminant from seed |
src/rsa.rs |
RSA-2048 stand-in, factors from a public seed, timing only |
src/wesolowski.rs |
eval with checkpoints, block prover (kappa digits, gamma residue classes, threads), naive prover, verify, Fiat-Shamir prime |
src/seed.rs |
epoch_seed, verify_epoch_seed, proof plan |
src/grind.rs |
Analytic and Monte Carlo grinding model |
src/hash.rs |
SHA-256 helpers, hash-to-prime, encodings |
src/main.rs |
CLI: selftest, bench, eval, demo, grind |
References
- B. Wesolowski, Efficient Verifiable Delay Functions, EUROCRYPT 2019 (proof, digit algorithm).
- D. Boneh, B. Bünz, B. Fisch, A Survey of Two Verifiable Delay Functions, 2018 (class groups for VDFs, low-order and adaptive root assumptions).
- H. Cohen, A Course in Computational Algebraic Number Theory, Algorithms 5.4.2 (reduction), 5.4.7 (composition), 5.4.8 (duplication), 5.4.9 (NUDUPL).
- Chia Network, chiavdf, commit 7e62ce14 (29 Sep 2026), cloned to vendor/chiavdf.