use kbfold::field::{ExtField, Field, Fp, Fp2, Fp4};
use kbfold::merkle::MerkleTree;
use kbfold::pcs::{Basis, Params, commit, open, to_coefficients, verify};
use kbfold::poly::ntt;
use std::time::Instant;
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 ^= self.0 << 13;
self.0 ^= self.0 >> 7;
self.0 ^= self.0 << 17;
self.0
}
fn fp(&mut self) -> Fp {
Fp::new(self.next())
}
fn ext<E: ExtField>(&mut self) -> E {
let d: Vec<Fp> = (0..E::DEGREE).map(|_| self.fp()).collect();
E::from_digits(&d)
}
}
fn median(mut v: Vec<f64>) -> f64 {
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
v[v.len() / 2]
}
fn ms(t: Instant) -> f64 {
t.elapsed().as_secs_f64() * 1e3
}
fn threads() -> usize {
std::env::var("RAYON_NUM_THREADS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or_else(|| std::thread::available_parallelism().map_or(1, |n| n.get()))
}
fn machine() -> String {
let t = if cfg!(feature = "parallel") {
threads()
} else {
1
};
format!("{t},{}", cfg!(feature = "parallel"))
}
struct Row {
commit_ms: f64,
open_ms: f64,
verify_ms: f64,
proof_kib: f64,
}
fn reps_for(m: usize) -> usize {
match m {
0..=16 => 11,
17..=20 => 5,
_ => 3,
}
}
fn run<E: ExtField>(p: &Params, reps: usize, seed: u64) -> Row {
let mut rng = Rng(seed);
let table: Vec<Fp> = (0..1usize << p.m).map(|_| rng.fp()).collect();
let z: Vec<E> = (0..p.m).map(|_| rng.ext()).collect();
let mut cm = vec![];
for _ in 0..reps {
let t = Instant::now();
let r = commit(p, &table).unwrap();
cm.push(ms(t));
std::hint::black_box(r.0);
}
let (root, pd) = commit(p, &table).unwrap();
let mut op = vec![];
let mut last = None;
for _ in 0..reps {
let t = Instant::now();
let r = open(p, &pd, &z).unwrap();
op.push(ms(t));
last = Some(r);
}
let (v, proof) = last.unwrap();
let mut ve = vec![];
for _ in 0..21 {
let t = Instant::now();
let ok = verify(p, &root, &z, v, &proof);
ve.push(ms(t));
assert_eq!(ok, Ok(()));
}
Row {
commit_ms: median(cm),
open_ms: median(op),
verify_ms: median(ve),
proof_kib: proof.to_bytes().len() as f64 / 1024.0,
}
}
fn header(extra: &str) {
println!("{extra},threads,parallel,commit_ms,open_ms,prover_ms,verify_ms,proof_kib");
}
fn line(extra: String, r: &Row) {
println!(
"{extra},{},{:.2},{:.2},{:.2},{:.3},{:.1}",
machine(),
r.commit_ms,
r.open_ms,
r.commit_ms + r.open_ms,
r.verify_ms,
r.proof_kib
);
}
fn main() {
let mode = std::env::args().nth(1).unwrap_or_else(|| "scaling".into());
let max_m: usize = std::env::args()
.nth(2)
.and_then(|s| s.parse().ok())
.unwrap_or(22);
match mode.as_str() {
"scaling" => {
header("basis,m,R,s,queries,field");
for m in (12..=max_m).step_by(2) {
for basis in [Basis::Kernel, Basis::Monomial] {
let p = Params {
basis,
..Params::classical(m)
};
let r = run::<Fp2>(&p, reps_for(m), 1 + m as u64);
line(format!("{basis:?},{m},2,{},{},p^2", p.s, p.queries), &r);
}
}
}
"pq" => {
header("basis,m,R,s,queries,field");
for m in (12..=max_m).step_by(2) {
let p = Params::post_quantum(m);
let r = run::<Fp4>(&p, reps_for(m), 2 + m as u64);
line(format!("Kernel,{m},2,{},{},p^4", p.s, p.queries), &r);
}
}
"salt" => {
header("m,salt_len");
let m = max_m.min(20);
for salt_len in [0, 32] {
let p = Params {
salt_len,
..Params::classical(m)
};
let r = run::<Fp2>(&p, 5, 3);
line(format!("{m},{salt_len}"), &r);
}
}
"stop" => {
header("m,s,final_len");
let m = max_m.min(20);
for s in (8..=m).step_by(2) {
let p = Params {
s,
..Params::classical(m)
};
let r = run::<Fp2>(&p, 3, 77);
line(format!("{m},{s},{}", 1 << (m - s)), &r);
}
}
"rate" => {
header("m,R,queries");
let m = max_m.min(20);
for rr in [1, 2, 3] {
let delta = (1.0 - 1.0 / (1u64 << rr) as f64) / 2.0;
let q = (100.0 / -(1.0 - delta).log2()).ceil() as usize;
let p = Params {
log_inv_rate: rr,
queries: q,
..Params::classical(m)
};
let r = run::<Fp2>(&p, 3, 99);
line(format!("{m},{rr},{q}"), &r);
}
}
"arity" => {
header("m,k,field");
let m = max_m.min(20);
for k in 1..=4 {
let p = Params {
fold_log: k,
..Params::classical(m)
};
let r = run::<Fp2>(&p, 5, 11);
line(format!("{m},{k},p^2"), &r);
}
for k in [1, 4] {
let p = Params {
fold_log: k,
..Params::post_quantum(m)
};
let r = run::<Fp4>(&p, 5, 12);
line(format!("{m},{k},p^4"), &r);
}
}
"breakdown" => {
println!("basis,m,threads,parallel,transform_ms,ntt_ms,merkle_ms,commit_ms,open_ms");
let m = max_m.min(20);
for basis in [Basis::Kernel, Basis::Monomial] {
let p = Params {
basis,
..Params::classical(m)
};
let mut rng = Rng(5);
let table: Vec<Fp> = (0..1usize << m).map(|_| rng.fp()).collect();
let omega = Fp::two_adic_root((m + p.log_inv_rate) as u32);
let (mut tr, mut nt, mut mk) = (vec![], vec![], vec![]);
for _ in 0..5 {
let t0 = Instant::now();
let mut w = table.clone();
to_coefficients(p.basis, &mut w);
let t1 = Instant::now();
w.resize(p.n(), Fp::ZERO);
ntt(&mut w, omega);
let t2 = Instant::now();
let tree =
MerkleTree::new_fibres(&w, p.groups()[0].1, &[0u8; 32], b"w0", p.salt_len);
std::hint::black_box(tree.root());
tr.push((t1 - t0).as_secs_f64() * 1e3);
nt.push((t2 - t1).as_secs_f64() * 1e3);
mk.push(ms(t2));
}
let r = run::<Fp2>(&p, 5, 5);
println!(
"{basis:?},{m},{},{:.2},{:.2},{:.2},{:.2},{:.2}",
machine(),
median(tr),
median(nt),
median(mk),
r.commit_ms,
r.open_ms
);
}
}
_ => eprintln!("unknown mode {mode}"),
}
let _ = Fp2::ONE;
}