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>
241 lines
14 KiB
Markdown
241 lines
14 KiB
Markdown
# 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_nudupl` and `qfb_nucomp` (William Hart's FLINT code), with the Lehmer-accelerated
|
|
partial extended gcd from `xgcd_partial.c`. The textbook Cohen 5.4.7 composition and the
|
|
plain duplication formula are kept as test oracles; `vdf selftest` checks 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
|
|
|
|
1. 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.rs` against
|
|
vendor/chiavdf/src/nucomp.h and xgcd_partial.c. Reduction runs every squaring; chiavdf
|
|
reduces only when `a` exceeds 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.
|
|
2. 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.
|
|
3. 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.
|
|
4. Fallback rule for a node without the seed at epoch start (above), and whether headers
|
|
should name the program seed.
|
|
5. Checkpoint hash definition must commit to full block hashes (header plus nonce).
|
|
6. 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.
|
|
7. Not done: no constant-time anything (not needed, all inputs public), no fuzzing of
|
|
`deserialize` on 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.
|