From c75477115dd72b8a84e4291251665ce188a22667 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Sat, 3 Oct 2026 16:39:01 +0000 Subject: [PATCH] proto-vdf: Wesolowski VDF between the certified checkpoint and the program seed 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 --- docs/bench-log.md | 13 +++ proto-vdf/README.md | 241 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 proto-vdf/README.md diff --git a/docs/bench-log.md b/docs/bench-log.md index 9279dc85..b53e8ff5 100644 --- a/docs/bench-log.md +++ b/docs/bench-log.md @@ -146,3 +146,16 @@ Fork points mapped with line numbers in `docs/fork-map.md` (hash, coinbase, DAA, | Hash rate at 1 GiB | 228.95 Mhash/s, 95.2 GB/s useful, 23.8 G random loads/s | Reading: the memory-hard construction is now bit-exact across Apple Metal, NVIDIA CUDA and the CPU reference, cache and dataset included. Hash rate is unchanged from the closed-form dataset on both vendors, as expected, since the hash kernel only loads; what changed is that computing items on the fly is now slower than loading them (4.8x slower measured on Apple, not yet measured on NVIDIA). Still unmeasured: the inline shortcut ratio on NVIDIA, and AMD on any dataset. + +## 2026-10-03 proto-vdf, Wesolowski VDF between the certified checkpoint and the program seed (epoch 10 min, era 1 h) + +Machine: Apple M5 Max (18 logical cores), rustc 1.69.0, GMP 6.3.0 via rug 1.19. Source `proto-vdf/`, details in `proto-vdf/README.md`. Single core sequential squaring unless stated. +Rates: class group 1024-bit prime discriminant (production choice, chiavdf construction, NUDUPL/NUCOMP ported from vendor/chiavdf) 163,000 sq/s; class group 2048-bit 83,500 sq/s; RSA-2048 trusted-setup stand-in (public trapdoor, timing only) 1,257,000 sq/s. +T for 10 min / 60 min on this core: class 1024: 98.0 M / 588 M; class 2048: 50.1 M / 301 M; RSA-2048: 754 M / 4.53 G. +Full 10-min runs: RSA T=756,516,411 eval 607.1 s (1,246,000 sq/s), prove 9.0 s on 12 threads (71.2 s on 1), verify 0.88 ms, proof 512 bytes. Class 1024 T=97,126,043 eval 585.4 s (165,900 sq/s, the RSA run sharing the chip ended midway), prove 9.1 s on 12 threads (56.8 s on 1), verify 4.47 ms, proof 516 bytes. +Prover costs 12 to 13 percent of eval single-threaded (12-bit digits, at most 65,536 checkpoints, 17 MB) and parallelises over residue classes; verify is two 256-bit exponentiations, 4.5 ms class 1024 (12.6 ms including deriving D from the checkpoint hash), 1.4 ms RSA. +Seed pipeline: epoch_seed(checkpoint) -> (seed, proof) and verify_epoch_seed; the same checkpoint hash gave the same seed and identical proof bytes in two separate processes at T=1,000,000; wrong checkpoint, flipped seed bit and T+1 all rejected. +Attacker speed: delay must only exceed the 2 s publish-or-lose window; margin is 300x at the epoch and 1,800x at the era, so a 2x (or 10x, or 100x) faster evaluator leaves grinding impossible. Requirement: the checkpoint hash must commit to full block hashes incl. nonce. +Grinding model (3,600 blocks/epoch, advantage uniform 0 to 15%, keep top quartile, one block burned per withheld candidate), gain per epoch in blocks, no delay vs with delay: s=0.1 +0.40 vs 0; s=0.2 +1.66 vs 0; s=0.3 +3.62 (+0.32%, 13.5:1 on burned blocks) vs 0; s=0.4 +6.06 vs 0. Monte Carlo over 2,000,000 epochs agrees to 0.03 blocks. +Correctness: NUDUPL, NUCOMP and the Lehmer partial xgcd agree with Cohen 5.4.7 / plain duplication / plain-division xgcd on 15,000 random cases; block prover equals the naive O(T) prover at T = 37, 5,000 and 100,000 in both groups; 216 associativity triples; 3 tamper cases rejected per size. +Recommend: class group 1024-bit D from the checkpoint hash, epoch T = 600 x r_ref and era T = 3,600 x r_ref with r_ref the fastest honest single-core rate measured on the devnet (98 M and 588 M on this Mac), fixed at genesis, 20 min lead time for the epoch seed and 2 h for the era draw, 256-bit Fiat-Shamir prime. Open: external review of classgroup.rs against chiavdf, reference core choice, fallback rule for a node without the seed at epoch start, carry D in the proof. diff --git a/proto-vdf/README.md b/proto-vdf/README.md new file mode 100644 index 00000000..9e617306 --- /dev/null +++ b/proto-vdf/README.md @@ -0,0 +1,241 @@ +# 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.