use lens::rspcs::field::eq_table;
use nebu::Fp3;
use super::{CBITS, DEGREE, WrapKey, WrapProof};
use crate::recursion::acc::{ClaimRef, InstV};
use crate::recursion::circuit::layout::{V1, pre};
use crate::recursion::gm;
use crate::recursion::ops::{Arith, Ops};
use crate::recursion::perm::tag;
use crate::recursion::sponge::Sponge;
use crate::recursion::whir;
pub fn check_shape(k: &WrapKey, pf: &WrapProof) -> Result<(), String> {
let n = k.params.n;
let inner = k.inner();
let words = k.trace_words();
let ok = pf.roots.len() == words
&& pf.ood.len() == words
&& pf.ood.iter().all(|a| a.len() == k.fresh)
&& pf.zerocheck.len() == n
&& pf.zerocheck.iter().all(|m| m.len() == DEGREE + 1)
&& pf.local.len() == k.cols()
&& pf.next.len() == k.next_cols.len()
&& pf.key.len() == if inner { k.key_phys.len() } else { 0 }
&& pf.shift.len() == if inner { 2 * n } else { 0 }
&& pf.vals.len() == if inner { words } else { 0 }
&& pf.kv.len() == if inner { k.key_words() } else { 0 };
if !ok {
return Err("wrap: proof shape".into());
}
whir::check_shape(&k.cfg, k.inputs(), &pf.whir)
}
fn bind<O: Ops>(o: &mut O, t: &mut Sponge<O>, answers: &[Fp3]) -> Vec<ClaimRef<O::V>> {
answers
.iter()
.map(|&y| {
let z = t.squeeze_ext(o);
let yv = t.absorb_free_ext(o, y);
ClaimRef::Uni(z, yv)
})
.collect()
}
pub fn threads() -> usize {
std::env::var("ZHENG_VERIFY_THREADS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or_else(|| {
std::thread::available_parallelism()
.map_or(1, |n| n.get())
.min(16)
})
.max(1)
}
pub(crate) fn par_chunks<T: Send>(
n: usize,
f: impl Fn(core::ops::Range<usize>) -> T + Sync,
) -> Vec<T> {
crate::recursion::pool::map_chunks(n, threads(), f)
}
pub(crate) fn par_eq_table(point: &[Fp3]) -> Vec<Fp3> {
let k = point.len().min(8);
if point.len() < 12 {
return eq_table(point);
}
let (lo, hi) = (eq_table(&point[..k]), eq_table(&point[k..]));
let (lo, hi) = (&lo, &hi);
par_chunks(hi.len(), |r| {
r.flat_map(|h| lo.iter().map(move |&l| l * hi[h]))
.collect::<Vec<_>>()
})
.concat()
}
pub(crate) fn key_at(k: &WrapKey, rho: &[Fp3]) -> Vec<Fp3> {
use lens::rspcs::field::mul_base;
let e = par_eq_table(rho);
let one = nebu::Goldilocks::ONE;
let col = |c: &[(u32, Fp3)]| {
c.iter().fold(Fp3::ZERO, |a, &(x, v)| {
let ex = e[x as usize];
if v.c1 != nebu::Goldilocks::ZERO || v.c2 != nebu::Goldilocks::ZERO {
a + v * ex
} else if v.c0 == one {
a + ex
} else {
a + mul_base(ex, v.c0)
}
})
};
par_chunks(k.sparse.len(), |r| {
r.map(|j| {
if k.key_cols[j] {
col(&k.sparse[j])
} else {
Fp3::ZERO
}
})
.collect::<Vec<_>>()
})
.concat()
}
pub(crate) fn powers(lambda: Fp3, n: usize) -> Vec<Fp3> {
let pow = |mut e: usize| {
let (mut acc, mut b) = (Fp3::ONE, lambda);
while e > 0 {
if e & 1 == 1 {
acc *= b;
}
b *= b;
e >>= 1;
}
acc
};
par_chunks(n, |r| {
let mut l = pow(r.start);
r.map(|_| {
let v = l;
l *= lambda;
v
})
.collect::<Vec<_>>()
})
.concat()
}
pub fn verify<O: Ops>(o: &mut O, k: &WrapKey, x: [O::V; 4], pf: &WrapProof) {
check_shape(k, pf).expect("wrap: proof shape");
let lap = crate::recursion::ivc::timer_pub(" wrap verify ");
let n = k.params.n;
let inner = k.inner();
assert!(
inner || o.native(),
"wrap: a final-mode level is verified natively only"
);
let mut t = Sponge::new(o, tag::WRAP);
t.absorb_all(o, &x);
let mut roots = Vec::with_capacity(2);
let mut oods: Vec<Vec<ClaimRef<O::V>>> = Vec::with_capacity(2);
let r1: [O::V; 4] = core::array::from_fn(|i| t.absorb_free(o, pf.roots[0][i]));
roots.push(r1);
oods.push(bind(o, &mut t, &pf.ood[0]));
let (ab, lambda) = if inner {
let ab = [t.squeeze_ext(o), t.squeeze_ext(o)];
let r2: [O::V; 4] = core::array::from_fn(|i| t.absorb_free(o, pf.roots[1][i]));
roots.push(r2);
oods.push(bind(o, &mut t, &pf.ood[1]));
(ab, None)
} else {
let z = o.zero();
([z, z], Some(t.squeeze_ext(o)))
};
let tau = t.squeeze_exts(o, n);
let mu = t.squeeze_ext(o);
let mut claim = o.zero();
let mut rho = Vec::with_capacity(n);
for msg in &pf.zerocheck {
let m: Vec<O::V> = msg.iter().map(|&h| t.absorb_free_ext(o, h)).collect();
let a = t.squeeze_ext(o);
let h1 = o.sub(claim, m[0]);
let mut h = vec![m[0], h1];
h.extend_from_slice(&m[1..]);
claim = gm::interpolate(o, &h, a);
rho.push(a);
}
let (local, sent): (Vec<O::V>, Vec<O::V>) = if inner {
(
pf.local.iter().map(|&v| t.absorb_free_ext(o, v)).collect(),
pf.next.iter().map(|&v| t.absorb_free_ext(o, v)).collect(),
)
} else {
let both: Vec<Fp3> = pf.local.iter().chain(&pf.next).copied().collect();
let mut v = crate::recursion::msg::absorb(o, &mut t, &both);
let next = v.split_off(pf.local.len());
(v, next)
};
let zero = o.zero();
let mut next = vec![zero; k.cols()];
for (&c, &v) in k.next_cols.iter().zip(&sent) {
next[c] = v;
}
lap("zerocheck");
let phys: Vec<O::V> = if inner {
pf.key.iter().map(|&v| t.absorb_free_ext(o, v)).collect()
} else {
Vec::new()
};
let key: Vec<O::V> = if inner {
crate::recursion::splitkey::logical(o, &k.key_phys, &phys, pre::COUNT)
} else {
let r: Vec<Fp3> = rho.iter().map(|&v| o.value(v)).collect();
key_at(k, &r).into_iter().map(|v| o.constant(v)).collect()
};
lap("key");
let e_out = gm::eq_row(o, k.out_row, &rho);
let pin: Vec<O::V> = x.iter().map(|&xj| o.mul(e_out, xj)).collect();
let e = gm::eq(o, &tau, &rho);
let einv = o.inv(e, "wrap: eq(τ, ρ) is invertible");
let c = o.mul(claim, einv);
let mut ins = local.clone();
ins.extend_from_slice(&next);
ins.extend_from_slice(&key);
ins.extend_from_slice(&pin);
ins.extend_from_slice(&[ab[0], ab[1], mu]);
let g = o.graph_compiled(&k.g, &k.gc, &ins)[0];
o.assert_eq(g, c, "wrap: constraints");
lap("constraints");
if !inner {
let gl = t.squeeze_exts(o, CBITS);
let gn = t.squeeze_exts(o, CBITS);
let cols = |o: &mut O, g: &[O::V], keep: &dyn Fn(usize) -> bool| -> Vec<O::V> {
(0..1usize << CBITS)
.map(|c| if keep(c) { gm::eq_row(o, c, g) } else { zero })
.collect()
};
let cl = cols(o, &gl, &|c| c < V1);
let cn = cols(o, &gn, &|c| k.next_cols.contains(&c));
let vl = gm::combine(o, &cl[..V1], &local);
let nsel: Vec<O::V> = k.next_cols.iter().map(|&c| cn[c]).collect();
let vn = gm::combine(o, &nsel, &sent);
let mut claims: Vec<(whir::Weight<O::V>, O::V)> = oods
.remove(0)
.into_iter()
.map(|c| match c {
ClaimRef::Uni(z, y) => (whir::Weight::Pow(z), y),
ClaimRef::Multi(p, y) => (whir::Weight::Eq(p), y),
})
.collect();
claims.push((
whir::Weight::RowCol {
next: false,
rho: rho.clone(),
col: cl,
},
vl,
));
claims.push((
whir::Weight::RowCol {
next: true,
rho: rho.clone(),
col: cn,
},
vn,
));
claims.push((whir::Weight::Native(0), zero));
let wiring = super::wiring::WiringWeight {
k,
lambda: o.value(lambda.expect("final mode")),
};
whir::verify_direct(
o,
&k.cfg,
&mut t,
roots[0],
false,
claims,
&[&wiring],
&pf.whir,
);
lap("opening");
return;
}
let gk = t.squeeze_exts(o, crate::recursion::splitkey::BITS);
let words = k.trace_words();
let gs: Vec<Vec<O::V>> = (0..words).map(|_| t.squeeze_exts(o, CBITS)).collect();
let beta = t.squeeze_ext(o);
let zeta = t.squeeze_ext(o);
let split = [(0, V1), (V1, k.cols())];
let mut sigma: Option<O::V> = None;
for (w, &(lo, hi)) in split.iter().take(words).enumerate() {
let a = gm::mle(o, &local[lo..hi], &gs[w]);
let nn = gm::mle(o, &next[lo..hi], &gs[w]);
let s = o.mul_add(beta, nn, a);
sigma = Some(match sigma {
None => s,
Some(acc) => o.mul_add(zeta, s, acc),
});
}
let mut claim = sigma.expect("a word");
let mut rho2 = Vec::with_capacity(n);
for pair in pf.shift.as_chunks::<2>().0 {
let h0 = t.absorb_free_ext(o, pair[0]);
let h2 = t.absorb_free_ext(o, pair[1]);
let a = t.squeeze_ext(o);
let h1 = o.sub(claim, h0);
claim = gm::quadratic(o, h0, h1, h2, a);
rho2.push(a);
}
let vals: Vec<O::V> = pf.vals.iter().map(|&v| t.absorb_free_ext(o, v)).collect();
let eqr = gm::eq(o, &rho, &rho2);
let nxt = gm::next_eval(o, &rho, &rho2);
let kk = o.mul_add(beta, nxt, eqr);
let vz = if inner {
o.mul_add(zeta, vals[1], vals[0])
} else {
vals[0]
};
let lhs = o.mul(kk, vz);
o.assert_eq(lhs, claim, "wrap: shift reduction");
let mut inputs: Vec<InstV<O::V>> = Vec::with_capacity(4);
for (w, (root, cl)) in roots.into_iter().zip(oods).enumerate() {
let mut claims = cl;
claims.push(ClaimRef::Multi(
rho2.iter().chain(&gs[w]).copied().collect(),
vals[w],
));
inputs.push(InstV {
root,
ext: false,
claims,
});
}
if let Some(key_root) = k.key_root {
let kv = gm::mle(o, &phys, &gk);
let v: Vec<O::V> = pf.kv.iter().map(|&x| t.absorb_free_ext(o, x)).collect();
let mut sum: Option<O::V> = None;
for (w, &vw) in v.iter().enumerate() {
let e = gm::eq_row(o, w, &gk[CBITS..]);
sum = Some(match sum {
None => o.mul(e, vw),
Some(acc) => o.mul_add(e, vw, acc),
});
}
o.assert_eq(sum.expect("a key word"), kv, "wrap: key claim");
let zg: Vec<O::V> = rho.iter().chain(&gk[..CBITS]).copied().collect();
for &vh in &v {
let root = key_root.map(|x| o.constant(Fp3::from_base(x)));
inputs.push(InstV {
root,
ext: false,
claims: vec![ClaimRef::Multi(zg.clone(), vh)],
});
}
}
whir::verify(o, &k.cfg, &mut t, &inputs, &pf.whir);
}