RTX 5090: memory-hard pack 96/96 PASS, cache bit-exact with the Mac; ignore pycache

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-03 16:18:44 +00:00
parent 5f0efda007
commit b4a9d3f0cf
6 changed files with 624 additions and 4 deletions

2
.gitignore vendored
View file

@ -12,3 +12,5 @@ vendor/
# secrets never live here
*.env
.env*
sim/__pycache__/
*.pyc

View file

@ -134,3 +134,15 @@ Blocks per second over the 180 s run: 31.76 on all three nodes (1,691 to 7,409 b
Propagation: block counts, DAA scores and sink hash were identical on all three nodes at 18 of 19 ten-second samples; the one miss was node 2 trailing by a single block for one sample. Tips stayed at 1 because a single serial miner never produced parallel blocks, so GHOSTDAG k was not exercised; a second miner is the next step for that.
Earlier 30 s warm-up run: 1,690 blocks, 56.3 blocks/s on all three nodes, 28.1 MH/s.
Fork points mapped with line numbers in `docs/fork-map.md` (hash, coinbase, DAA, header, depth constants, BPS and k). All nodes stopped at the end. Miner source kept outside the repo (scratchpad); re-create from `testing/integration/src/common/utils.rs:271` if needed.
## 3 October 2026, RTX 5090, memory-hard dataset (pack igneum-genesis-mh)
| Check | Result |
|---|---|
| 256 MiB cache, GPU vs host, all 67,108,864 words | PASS, FNV-1a 48c4f5bf24166b2e matches the Mac |
| Cache fill | 0.67 ms GPU, 223 ms one host thread |
| Dataset build from the cache, 1 GiB | 13.4 ms, 1,253 M items/s |
| Vectors, 3 warps, standalone and in batch | 96/96 PASS |
| 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.

180
proto-vdf/Cargo.lock generated Normal file
View file

@ -0,0 +1,180 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "az"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cfg-if"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "gmp-mpfr-sys"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13eabc29d16e4a621b495e3919c71ebb7caaed24380955671e7d417370fea95d"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "igneum-vdf"
version = "0.1.0"
dependencies = [
"az",
"gmp-mpfr-sys",
"rug",
"sha2",
]
[[package]]
name = "libc"
version = "0.2.190"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
[[package]]
name = "rug"
version = "1.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555e8b44763d034526db899c88cd56ccc4486cd38b444c8aa0e79d4e70ae5a34"
dependencies = [
"az",
"gmp-mpfr-sys",
"libc",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

View file

@ -20,6 +20,7 @@
use crate::group::Group;
use crate::hash::{bytes_to_int, hash_prime, signed_to_bytes};
use rug::ops::{NegAssign, RemRounding};
use rug::Integer;
use std::cmp::Ordering;
@ -149,8 +150,8 @@ impl ClassGroup {
// Step 4.
let v1 = Integer::from(&f1.a / &d1);
let v2 = Integer::from(&f2.a / &d1);
let mut r = Integer::from(&y1 * &y2) * &n - Integer::from(&x2 * &f2.c);
r.rem_euc_mut(&v1);
let r = Integer::from(&y1 * &y2) * &n - Integer::from(&x2 * &f2.c);
let r = r.rem_euc(&v1);
let v2r = Integer::from(&v2 * &r);
let b3 = Integer::from(&f2.b + Integer::from(&v2r << 1u32));
let a3 = Integer::from(&v1 * &v2);
@ -170,8 +171,7 @@ impl ClassGroup {
*f = r;
return;
}
let mut u = Integer::from(&f.c * &s);
u.rem_euc_mut(&f.a);
let u = Integer::from(&f.c * &s).rem_euc(&f.a);
let au = Integer::from(&f.a * &u);
let bu_c = Integer::from(&f.b * &u) - &f.c;
let q = bu_c / &f.a; // exact: bu = c mod a

426
proto-vdf/src/main.rs Normal file
View file

@ -0,0 +1,426 @@
mod classgroup;
mod grind;
mod group;
mod hash;
mod rsa;
mod seed;
mod wesolowski;
use classgroup::ClassGroup;
use group::Group;
use hash::{from_hex, hex, sha256};
use rsa::RsaGroup;
use rug::Integer;
use std::time::Instant;
const DECISION_WINDOW_S: f64 = 2.0;
const EPOCH_DELAY_S: f64 = 600.0;
const ERA_DELAY_S: f64 = 3600.0;
fn arg(args: &[String], name: &str) -> Option<String> {
args.iter().position(|a| a == name).and_then(|i| args.get(i + 1).cloned())
}
fn arg_u64(args: &[String], name: &str, default: u64) -> u64 {
arg(args, name).map(|v| v.replace('_', "").parse().expect("number")).unwrap_or(default)
}
fn arg_f64(args: &[String], name: &str, default: f64) -> f64 {
arg(args, name).map(|v| v.parse().expect("number")).unwrap_or(default)
}
fn default_threads() -> usize {
std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4).min(12)
}
fn usage() {
eprintln!(
"usage: vdf <command> [options]
selftest group laws, proof round trips, tamper checks
bench [--seconds S] [--threads N]
squarings/s per group, T for 10 and 60 min, prove and verify at a short T
eval --group rsa|class (--minutes M | --t T) [--threads N] [--kappa K]
run a full-length delay, then prove and verify, report times
demo [--checkpoint HEX] [--t T] [--threads N]
epoch_seed / verify_epoch_seed, determinism, tamper tests
grind [--epochs N] grinding revenue table with and without the delay"
);
}
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
match args.first().map(|s| s.as_str()) {
Some("selftest") => selftest(),
Some("bench") => bench(&args),
Some("eval") => eval_cmd(&args),
Some("demo") => demo(&args),
Some("grind") => grind_cmd(&args),
_ => usage(),
}
}
// ---------------------------------------------------------------- selftest
fn selftest() {
println!("== class group laws (1024-bit prime discriminant from a test seed) ==");
let g = ClassGroup::from_seed(b"igneum-selftest-discriminant", 1024);
println!("D bits {} -D mod 8 = {}", g.d.significant_bits(), Integer::from(-&g.d) % 8u32);
let id = g.identity();
let gen = g.generator();
assert!(ClassGroup::is_reduced(&gen) && ClassGroup::discriminant_of(&gen) == g.d);
// Random-ish elements: generator to random exponents.
let mut elems = Vec::new();
for i in 0..6u32 {
let e = Integer::from_digits(&sha256(&[b"exp", &i.to_be_bytes()]), rug::integer::Order::MsfBe);
let f = g.pow(&gen, &e);
assert!(g.is_valid(&f), "pow output not valid");
elems.push(f);
}
let mut checks = 0;
for a in &elems {
assert_eq!(g.mul(a, &id), *a);
assert_eq!(g.mul(&id, a), *a);
assert_eq!(g.mul(a, &g.inverse(a)), id);
let mut sq = a.clone();
g.square(&mut sq);
assert_eq!(sq, g.compose(a, a), "square != compose(a,a)");
for b in &elems {
assert_eq!(g.mul(a, b), g.mul(b, a));
for c in &elems {
assert_eq!(g.mul(&g.mul(a, b), c), g.mul(a, &g.mul(b, c)));
checks += 1;
}
}
}
// Exponent laws: (gen^e1)^e2 == gen^(e1 e2), gen^(2^k) by squaring == pow.
let e1 = Integer::from(123456789u64);
let e2 = Integer::from(987654321u64);
assert_eq!(g.pow(&g.pow(&gen, &e1), &e2), g.pow(&gen, &Integer::from(&e1 * &e2)));
assert_eq!(g.pow2k(&gen, 40), g.pow(&gen, &Integer::from(Integer::u_pow_u(2, 40))));
// Serialization round trip.
for a in &elems {
assert_eq!(g.deserialize(&g.serialize(a)).unwrap(), *a);
}
println!("identity, inverse, commutativity, {} associativity triples, square==compose, exponent laws, serialization: PASS", checks);
println!("== RSA stand-in group ==");
let r = RsaGroup::from_public_seed("selftest");
let x = r.hash_to_elem(b"x");
let e = Integer::from(Integer::u_pow_u(2, 1000));
let want = x.clone().pow_mod(&e, &r.n).unwrap();
assert_eq!(r.pow2k(&x, 1000), want);
assert_eq!(r.pow(&x, &e), want);
println!("x^(2^1000) by squaring == GMP powm: PASS");
println!("== Wesolowski round trips ==");
for (name, t, spacing, kappa) in [("tiny", 37u64, 12u64, 4u32), ("small", 5000, 240, 8), ("medium", 100_000, 1200, 12)] {
// class group
let ev = wesolowski::eval(&g, &gen, t, spacing);
let pf = wesolowski::prove(&g, &gen, &ev, kappa, 4);
let naive = wesolowski::prove_naive(&g, &gen, &ev.y, t);
assert_eq!(pf.pi, naive.pi, "block proof != naive proof ({})", name);
assert_eq!(ev.y, g.pow2k(&gen, t));
assert!(wesolowski::verify(&g, &gen, &pf, t), "class verify failed ({})", name);
assert!(!wesolowski::verify(&g, &gen, &pf, t + 1), "wrong T accepted");
let mut bad = pf.clone();
bad.pi = g.mul(&bad.pi, &gen);
assert!(!wesolowski::verify(&g, &gen, &bad, t), "tampered pi accepted");
let mut bad = pf.clone();
bad.y = g.mul(&bad.y, &gen);
assert!(!wesolowski::verify(&g, &gen, &bad, t), "tampered y accepted");
// rsa
let ev = wesolowski::eval(&r, &x, t, spacing);
let pf = wesolowski::prove(&r, &x, &ev, kappa, 4);
let naive = wesolowski::prove_naive(&r, &x, &ev.y, t);
assert_eq!(pf.pi, naive.pi, "rsa block proof != naive ({})", name);
assert!(wesolowski::verify(&r, &x, &pf, t));
let mut bad = pf.clone();
bad.pi = r.mul(&bad.pi, &x);
assert!(!wesolowski::verify(&r, &x, &bad, t));
println!("T={:>7} spacing={:>5} kappa={:>2}: class and RSA proofs match the naive O(T) prover, verify PASS, 3 tamper cases rejected", t, spacing, kappa);
}
println!("== seed pipeline ==");
let h = sha256(&[b"checkpoint"]);
let (s1, p1) = seed::epoch_seed(&h, 20_000, 4);
let (s2, _p2) = seed::epoch_seed(&h, 20_000, 4);
assert_eq!(s1, s2);
assert!(seed::verify_epoch_seed(&h, &s1, &p1));
let mut h2 = h;
h2[0] ^= 1;
assert!(!seed::verify_epoch_seed(&h2, &s1, &p1));
let mut s3 = s1;
s3[31] ^= 1;
assert!(!seed::verify_epoch_seed(&h, &s3, &p1));
println!("same checkpoint twice -> same seed; verify PASS; wrong checkpoint and wrong seed rejected");
println!("== grinding model ==");
for s in [0.1, 0.3] {
let row = grind::analytic(s);
let (mc, _) = grind::monte_carlo(s, 200_000, 7);
assert!((row.gain_no_delay - mc).abs() < 0.15, "analytic {} vs MC {}", row.gain_no_delay, mc);
}
println!("analytic gain within 0.15 blocks of Monte Carlo at s=0.1 and 0.3");
println!("ALL SELFTESTS PASS");
}
// ---------------------------------------------------------------- bench
fn measure_rate<G: Group>(g: &G, x: &G::Elem, seconds: f64) -> f64 {
let mut cur = x.clone();
// warm-up
for _ in 0..2000 {
g.square(&mut cur);
}
let start = Instant::now();
let mut n: u64 = 0;
let batch = 2000;
while start.elapsed().as_secs_f64() < seconds {
for _ in 0..batch {
g.square(&mut cur);
}
n += batch;
}
n as f64 / start.elapsed().as_secs_f64()
}
struct ProofTiming {
t: u64,
eval_s: f64,
prove_1_s: f64,
prove_n_s: f64,
threads: usize,
verify_ms: f64,
proof_bytes: usize,
kappa: u32,
spacing: u64,
}
fn time_proof<G: Group>(g: &G, x: &G::Elem, t: u64, threads: usize, kappa_opt: Option<u32>) -> ProofTiming {
let spacing0 = wesolowski::choose_spacing(t, 1 << 16);
let kappa = kappa_opt.unwrap_or_else(|| seed::pick_kappa(spacing0));
let spacing = (spacing0 - spacing0 % kappa as u64).max(kappa as u64);
let ev = wesolowski::eval(g, x, t, spacing);
let s = Instant::now();
let pf1 = wesolowski::prove(g, x, &ev, kappa, 1);
let prove_1_s = s.elapsed().as_secs_f64();
let s = Instant::now();
let pf = wesolowski::prove(g, x, &ev, kappa, threads);
let prove_n_s = s.elapsed().as_secs_f64();
assert_eq!(pf1.pi, pf.pi);
let reps = 20;
let s = Instant::now();
let mut ok = true;
for _ in 0..reps {
ok &= wesolowski::verify(g, x, &pf, t);
}
let verify_ms = s.elapsed().as_secs_f64() * 1000.0 / reps as f64;
assert!(ok, "verification failed");
ProofTiming {
t,
eval_s: ev.seconds,
prove_1_s,
prove_n_s,
threads,
verify_ms,
proof_bytes: g.serialize(&pf.y).len() + g.serialize(&pf.pi).len(),
kappa,
spacing,
}
}
fn print_timing(pt: &ProofTiming) {
println!(
" T={} spacing={} kappa={}: eval {:.2} s, prove {:.2} s (1 thread) / {:.2} s ({} threads), verify {:.2} ms, proof {} bytes",
pt.t, pt.spacing, pt.kappa, pt.eval_s, pt.prove_1_s, pt.prove_n_s, pt.threads, pt.verify_ms, pt.proof_bytes
);
println!(
" prove/eval ratio {:.3} (1 thread), {:.3} ({} threads)",
pt.prove_1_s / pt.eval_s,
pt.prove_n_s / pt.eval_s,
pt.threads
);
}
fn attacker_lines(rate: f64, label: &str) {
let t10 = (rate * EPOCH_DELAY_S).round() as u64;
let t60 = (rate * ERA_DELAY_S).round() as u64;
println!(" {}: {:.0} squarings/s single core", label, rate);
println!(" T(10 min) = {} T(60 min) = {}", t10, t60);
println!(" speed-up needed to evaluate inside the {} s decision window: {:.0}x (epoch), {:.0}x (era)",
DECISION_WINDOW_S, EPOCH_DELAY_S / DECISION_WINDOW_S, ERA_DELAY_S / DECISION_WINDOW_S);
for f in [2.0f64, 10.0, 100.0] {
println!(" attacker {:>3}x faster: epoch delay {:>6.1} s, era delay {:>7.1} s, grinding {}",
f, EPOCH_DELAY_S / f, ERA_DELAY_S / f,
if EPOCH_DELAY_S / f > DECISION_WINDOW_S { "still impossible" } else { "POSSIBLE" });
}
}
fn bench(args: &[String]) {
let seconds = arg_f64(args, "--seconds", 5.0);
let threads = arg_u64(args, "--threads", default_threads() as u64) as usize;
println!("Machine: {}", machine_string());
println!("Decision window {} s, epoch delay {} s, era delay {} s\n", DECISION_WINDOW_S, EPOCH_DELAY_S, ERA_DELAY_S);
println!("== {} ==", "RSA-2048 stand-in");
let r = RsaGroup::from_public_seed("igneum-bench");
let x = r.hash_to_elem(b"bench");
let rate_rsa = measure_rate(&r, &x, seconds);
attacker_lines(rate_rsa, "RSA-2048");
let pt = time_proof(&r, &x, 1 << 20, threads, None);
print_timing(&pt);
println!(" projected prove time at T(10 min), {} threads: {:.0} s; at T(60 min): {:.0} s",
threads, pt.prove_n_s / pt.eval_s * EPOCH_DELAY_S, pt.prove_n_s / pt.eval_s * ERA_DELAY_S);
for bits in [1024u32, 2048] {
println!("\n== class group, {}-bit prime discriminant ==", bits);
let s = Instant::now();
let g = ClassGroup::from_seed(&sha256(&[b"igneum-bench-disc", &bits.to_be_bytes()]), bits);
println!(" discriminant derivation {:.3} s", s.elapsed().as_secs_f64());
let gen = g.generator();
let rate = measure_rate(&g, &gen, seconds);
attacker_lines(rate, &format!("class group {} bits", bits));
let t = if bits == 1024 { 1 << 17 } else { 1 << 15 };
let pt = time_proof(&g, &gen, t, threads, None);
print_timing(&pt);
println!(" projected prove time at T(10 min), {} threads: {:.0} s; at T(60 min): {:.0} s",
threads, pt.prove_n_s / pt.eval_s * EPOCH_DELAY_S, pt.prove_n_s / pt.eval_s * ERA_DELAY_S);
}
}
fn machine_string() -> String {
let cpu = std::process::Command::new("sysctl")
.args(["-n", "machdep.cpu.brand_string"])
.output()
.ok()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|| "unknown".into());
let cores = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0);
format!("{} ({} logical cores), rustc {}, GMP via rug", cpu, cores, rustc_version())
}
fn rustc_version() -> String {
std::process::Command::new("rustc")
.arg("--version")
.output()
.ok()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|| "?".into())
}
// ---------------------------------------------------------------- eval
fn eval_cmd(args: &[String]) {
let group = arg(args, "--group").unwrap_or_else(|| "class".into());
let threads = arg_u64(args, "--threads", default_threads() as u64) as usize;
let kappa = arg(args, "--kappa").map(|k| k.parse::<u32>().unwrap());
let minutes = arg_f64(args, "--minutes", 0.0);
println!("Machine: {}", machine_string());
match group.as_str() {
"rsa" => {
let r = RsaGroup::from_public_seed("igneum-bench");
let x = r.hash_to_elem(b"eval");
let t = if minutes > 0.0 {
let rate = measure_rate(&r, &x, 3.0);
let t = (rate * minutes * 60.0).round() as u64;
println!("rate {:.0} sq/s, T for {} min = {}", rate, minutes, t);
t
} else {
arg_u64(args, "--t", 1 << 22)
};
println!("== {} ==", r.name());
let pt = time_proof(&r, &x, t, threads, kappa);
print_timing(&pt);
println!(" effective rate during eval {:.0} sq/s", pt.t as f64 / pt.eval_s);
}
_ => {
let g = ClassGroup::from_seed(b"igneum-eval-disc", 1024);
let gen = g.generator();
let t = if minutes > 0.0 {
let rate = measure_rate(&g, &gen, 3.0);
let t = (rate * minutes * 60.0).round() as u64;
println!("rate {:.0} sq/s, T for {} min = {}", rate, minutes, t);
t
} else {
arg_u64(args, "--t", 1 << 20)
};
println!("== {} ==", g.name());
let pt = time_proof(&g, &gen, t, threads, kappa);
print_timing(&pt);
println!(" effective rate during eval {:.0} sq/s", pt.t as f64 / pt.eval_s);
}
}
}
// ---------------------------------------------------------------- demo
fn demo(args: &[String]) {
let threads = arg_u64(args, "--threads", default_threads() as u64) as usize;
let t = arg_u64(args, "--t", 200_000);
let h: [u8; 32] = match arg(args, "--checkpoint") {
Some(hx) => {
let v = from_hex(&hx).expect("hex");
assert_eq!(v.len(), 32, "checkpoint hash must be 32 bytes");
let mut a = [0u8; 32];
a.copy_from_slice(&v);
a
}
None => sha256(&[b"igneum devnet checkpoint 0000"]),
};
println!("checkpoint_hash = {}", hex(&h));
println!("T = {} squarings (demo value; production T is set from the reference core, see README)", t);
let g = seed::class_group_for(&h);
println!("discriminant: {} bits, -D mod 8 = {}", g.d.significant_bits(), Integer::from(-&g.d) % 8u32);
let s = Instant::now();
let (seed1, proof) = seed::epoch_seed(&h, t, threads);
let e1 = s.elapsed().as_secs_f64();
println!("run 1: program_seed = {} ({:.2} s eval+prove, proof {} bytes)", hex(&seed1), e1, proof.wire_size());
let s = Instant::now();
let (seed2, proof2) = seed::epoch_seed(&h, t, threads);
let e2 = s.elapsed().as_secs_f64();
println!("run 2: program_seed = {} ({:.2} s)", hex(&seed2), e2);
println!("same seed on both runs: {}", seed1 == seed2);
println!("same proof bytes on both runs: {}", proof.y == proof2.y && proof.pi == proof2.pi);
let s = Instant::now();
let ok = seed::verify_epoch_seed(&h, &seed1, &proof);
let v = s.elapsed().as_secs_f64() * 1000.0;
println!("verify_epoch_seed: {} in {:.2} ms (includes deriving the discriminant from the hash)", ok, v);
let mut h2 = h;
h2[5] ^= 0x80;
println!("verify with a different checkpoint hash: {}", seed::verify_epoch_seed(&h2, &seed1, &proof));
let mut bad = seed1;
bad[0] ^= 1;
println!("verify with a bit flipped in the seed: {}", seed::verify_epoch_seed(&h, &bad, &proof));
let mut badp = proof.clone();
badp.t += 1;
println!("verify with T+1 claimed: {}", seed::verify_epoch_seed(&h, &seed1, &badp));
println!("proof: t={} y={}... pi={}...", proof.t, hex(&proof.y[..16]), hex(&proof.pi[..16]));
}
// ---------------------------------------------------------------- grind
fn grind_cmd(args: &[String]) {
let epochs = arg_u64(args, "--epochs", 1_000_000);
println!("Grinding model: {} blocks/epoch, advantage uniform on [0, {}], grinder keeps top quartile (a >= {:.4}),",
grind::BLOCKS_PER_EPOCH, grind::ADV_MAX, grind::TOP_QUARTILE);
println!("one block reward per withheld block, {} Monte Carlo epochs per share.\n", epochs);
println!("| share | honest rev/epoch | P(grind lands) | withheld blocks | gain NO delay (analytic) | gain NO delay (MC) | gain % | gain WITH delay | gain:cost |");
println!("|---|---|---|---|---|---|---|---|---|");
for row in grind::table(epochs) {
println!(
"| {:.1} | {:.1} | {:.3} | {:.3} | {:+.2} | {:+.2} | {:+.3}% | {:+.2} | {:.1} : 1 |",
row.share,
row.honest,
row.p_grind_success,
row.withheld_no_delay,
row.gain_no_delay,
row.mc_gain_no_delay,
100.0 * row.gain_no_delay / row.honest,
row.gain_with_delay,
(row.gain_no_delay + row.withheld_no_delay) / row.withheld_no_delay
);
}
println!("\ngain:cost = gross gain from the better program divided by the block rewards burned withholding.");
println!("With the delay the candidate's program is unknown for {} s, {}x the decision window, so the grinder must publish: gain 0, nothing burned.",
EPOCH_DELAY_S, EPOCH_DELAY_S / DECISION_WINDOW_S);
}