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:
parent
641a1a4ad5
commit
39008cb1a1
6 changed files with 624 additions and 4 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -12,3 +12,5 @@ vendor/
|
|||
# secrets never live here
|
||||
*.env
|
||||
.env*
|
||||
sim/__pycache__/
|
||||
*.pyc
|
||||
|
|
|
|||
|
|
@ -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
180
proto-vdf/Cargo.lock
generated
Normal 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"
|
||||
|
|
@ -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
426
proto-vdf/src/main.rs
Normal 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);
|
||||
}
|
||||
Binary file not shown.
Loading…
Reference in a new issue