use nebu::{Fp3, Goldilocks};
use super::acc::{self, AccProof, ClaimRef, InstV};
use super::gm;
use super::ops::{Arith, Ops};
use super::params::{BV, CBITS, PN_COLS, Params, STEP_BITS};
use super::perm::tag;
use super::relation::{COLS, PUB_NOX, PUB_V, WORD};
use super::sponge::Sponge;
use super::state::{ClaimV, StateV};
use super::word::Digest;
use crate::machine::layout::{W1, W2};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StepProof {
pub roots: [Digest; 3],
pub ood: [Vec<Fp3>; 3],
pub b_in: Vec<Goldilocks>,
pub b_out: Vec<Goldilocks>,
pub b_v: Vec<Goldilocks>,
pub zerocheck: Vec<Vec<Fp3>>,
pub local: Vec<Fp3>,
pub next: Vec<Fp3>,
pub pub_nox: Vec<Fp3>,
pub pub_v: Vec<Fp3>,
pub fold_g: Vec<Fp3>,
pub fold_pn: Vec<Fp3>,
pub fold_pv: Vec<Fp3>,
pub shift: Vec<Fp3>,
pub vals: [Fp3; 3],
pub acc: AccProof,
}
pub fn pre_points<O: Ops>(o: &mut O, root: [O::V; 4], s: usize) -> Vec<O::V> {
let mut sp = Sponge::new(o, tag::PRE);
sp.absorb_all(o, &root);
sp.squeeze_exts(o, s)
}
pub fn chain_next<O: Ops>(
o: &mut O,
chain: [O::V; 4],
root: [O::V; 4],
answers: &[O::V],
) -> [O::V; 4] {
let mut sp = Sponge::new(o, tag::CHAIN);
sp.absorb_all(o, &chain);
sp.absorb_all(o, &root);
sp.absorb_all_ext(o, answers);
core::array::from_fn(|_| sp.squeeze(o))
}
pub fn word_row<V: Copy>(w: usize, nox: &[V], bv: &[V], zero: V) -> Vec<V> {
match w {
0 => nox[..W1].to_vec(),
1 => nox[W1..W1 + W2]
.iter()
.chain(&bv[..WORD - W2])
.copied()
.collect(),
_ => bv[WORD - W2..]
.iter()
.copied()
.chain(core::iter::repeat(zero))
.take(WORD)
.collect(),
}
}
fn fold<O: Ops>(
o: &mut O,
t: &mut Sponge<O>,
old: &ClaimV<O::V>,
point: &[O::V],
value: O::V,
evals: &[Fp3],
deg: usize,
) -> ClaimV<O::V> {
assert_eq!(evals.len() + 1, deg, "fold: line polynomial");
assert_eq!(point.len(), old.point.len(), "fold: dimension");
let mut h = vec![old.value, value];
h.extend(evals.iter().map(|&e| t.absorb_free_ext(o, e)));
let r = t.squeeze_ext(o);
let value = gm::interpolate(o, &h, r);
let point = old
.point
.iter()
.zip(point)
.map(|(&a, &b)| o.lerp(r, a, b))
.collect();
ClaimV { point, value }
}
fn bind<O: Ops>(o: &mut O, t: &mut Sponge<O>, answers: &[Fp3], vars: usize) -> Vec<ClaimRef<O::V>> {
let _ = vars;
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 verify<O: Ops>(
o: &mut O,
p: &Params,
st: &StateV<O::V>,
x: [O::V; 4],
pf: &StepProof,
) -> StateV<O::V> {
let n = p.n;
shape(p, pf);
let mut t = Sponge::new(o, tag::STEP);
t.absorb_all(o, &x);
let root_a: [O::V; 4] = core::array::from_fn(|i| t.absorb_free(o, pf.roots[0][i]));
let ans_a: Vec<O::V> = pf.ood[0].iter().map(|&y| t.absorb_free_ext(o, y)).collect();
let za = pre_points(o, root_a, p.fresh);
let ood_a: Vec<ClaimRef<O::V>> = za
.iter()
.zip(&ans_a)
.map(|(&z, &y)| ClaimRef::Uni(z, y))
.collect();
let chain = chain_next(o, st.chain, root_a, &ans_a);
let root_b: [O::V; 4] = core::array::from_fn(|i| t.absorb_free(o, pf.roots[1][i]));
let ood_b = bind(o, &mut t, &pf.ood[1], p.vars);
let ch_v = [t.squeeze_ext(o), t.squeeze_ext(o)];
let root_c: [O::V; 4] = core::array::from_fn(|i| t.absorb_free(o, pf.roots[2][i]));
let ood_c = bind(o, &mut t, &pf.ood[2], p.vars);
let b_in: Vec<O::V> = pf.b_in.iter().map(|&b| t.absorb_free(o, b)).collect();
let b_out: Vec<O::V> = pf.b_out.iter().map(|&b| t.absorb_free(o, b)).collect();
let b_v: Vec<O::V> = pf.b_v.iter().map(|&b| t.absorb_free(o, b)).collect();
let tau = t.squeeze_exts(o, n);
let seeds = t.squeeze_exts(o, p.seeds());
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: Vec<O::V> = pf.local.iter().map(|&v| t.absorb_free_ext(o, v)).collect();
let next: Vec<O::V> = pf.next.iter().map(|&v| t.absorb_free_ext(o, v)).collect();
let pub_nox: Vec<O::V> = pf
.pub_nox
.iter()
.map(|&v| t.absorb_free_ext(o, v))
.collect();
let pub_v: Vec<O::V> = pf.pub_v.iter().map(|&v| t.absorb_free_ext(o, v)).collect();
let e_out = gm::eq_row(o, p.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, "eq(Ο, Ο) is invertible");
let c = o.mul(claim, einv);
let gamma_n = t.squeeze_exts(o, PN_COLS);
let gamma_v = t.squeeze_exts(o, super::circuit::layout::pre::LOG);
let mut point_g = Vec::with_capacity(p.dims.g);
for v in [&local, &next, &pub_nox, &pub_v, &pin] {
point_g.extend_from_slice(v);
}
point_g.extend_from_slice(&ch_v);
point_g.extend_from_slice(&seeds);
let g = fold(o, &mut t, &st.g, &point_g, c, &pf.fold_g, p.g_deg);
let step_bits = o.bits(st.step, STEP_BITS);
let point_n: Vec<O::V> = rho
.iter()
.chain(&step_bits)
.chain(&gamma_n)
.copied()
.collect();
let vn = gm::mle(o, &pub_nox, &gamma_n);
let pn = fold(o, &mut t, &st.pn, &point_n, vn, &pf.fold_pn, p.pn_deg);
let point_v: Vec<O::V> = rho.iter().chain(&gamma_v).copied().collect();
let vv = gm::mle(o, &pub_v, &gamma_v);
let pv = fold(o, &mut t, &st.pv, &point_v, vv, &pf.fold_pv, p.pv_deg);
let gs: Vec<Vec<O::V>> = (0..3).map(|_| t.squeeze_exts(o, CBITS)).collect();
let beta = t.squeeze_ext(o);
let zeta = t.squeeze_ext(o);
let last = gm::product(o, &rho);
let zero = o.zero();
let mut sigma: Option<O::V> = None;
let zp = gm::powers(o, zeta, 3);
for w in 0..3 {
let lw = &local[WORD * w..WORD * (w + 1)];
let nw = &next[WORD * w..WORD * (w + 1)];
let bw = word_row(w, &b_out, &b_v, zero);
let a = gm::mle(o, lw, &gs[w]);
let nn = gm::mle(o, nw, &gs[w]);
let bb = gm::mle(o, &bw, &gs[w]);
let lb = o.mul(last, bb);
let nx = o.sub(nn, lb);
let s = o.mul_add(beta, nx, a);
sigma = Some(match sigma {
None => s,
Some(acc) => o.mul_add(zp[w], s, acc),
});
}
let mut claim = sigma.expect("three words");
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 mut wrap: Option<O::V> = None;
for (&a, &b) in rho.iter().zip(&rho2) {
let g = super::ops::Gate {
qm: -Fp3::ONE,
qa: Fp3::ONE,
..Default::default()
};
let f = o.gate(g, a, b, b);
wrap = Some(match wrap {
None => f,
Some(w) => o.mul(w, f),
});
}
let d = o.sub(nxt, wrap.expect("rows"));
let k = o.mul_add(beta, d, eqr);
let vz = gm::combine(o, &zp, &vals);
let lhs = o.mul(k, vz);
o.assert_eq(lhs, claim, "shift reduction");
let own: Vec<ClaimRef<O::V>> = (0..3)
.map(|w| ClaimRef::Multi(rho2.iter().chain(&gs[w]).copied().collect(), vals[w]))
.collect();
let gb: Vec<Vec<O::V>> = (0..3).map(|_| t.squeeze_exts(o, CBITS)).collect();
let mut bnd = Vec::with_capacity(3);
for (w, gw) in gb.iter().enumerate() {
let bw = word_row(w, &b_in, &b_v, zero);
let v = gm::mle(o, &bw, gw);
let pt: Vec<O::V> = core::iter::repeat_n(zero, n)
.chain(gw.iter().copied())
.collect();
bnd.push(ClaimRef::Multi(pt, v));
}
let first = is_zero(o, st.step);
let not_first = o.affine(-Fp3::ONE, first, Fp3::ONE);
for (&bi, &bl) in b_in.iter().zip(&st.b_last) {
let g = super::ops::Gate {
qm: Fp3::ONE,
qs: -Fp3::ONE,
..Default::default()
};
o.assert_gate(g, not_first, bi, bl, "boundary continuity");
}
let b_first: Vec<O::V> = st
.b_first
.iter()
.zip(&b_in)
.map(|(&f, &bi)| o.lerp(first, f, bi))
.collect();
let mut words = Vec::with_capacity(4);
words.push(st.acc.instance());
for (w, (root, oods)) in [(root_a, ood_a), (root_b, ood_b), (root_c, ood_c)]
.into_iter()
.enumerate()
{
let mut claims = oods;
claims.push(own[w].clone());
claims.push(bnd[w].clone());
words.push(InstV {
root,
ext: false,
claims,
});
}
let acc = acc::verify(o, &p.cfg, p.arity, &mut t, &words, &pf.acc);
let step = o.affine(Fp3::ONE, st.step, Fp3::ONE);
StateV {
ctx: st.ctx,
step,
chain,
b_first,
b_last: b_out,
acc,
g,
pn,
pv,
}
}
pub fn is_zero<O: Ops>(o: &mut O, x: O::V) -> O::V {
let xv = o.value(x);
let inv = o.witness(if xv == Fp3::ZERO { Fp3::ZERO } else { xv.inv() });
let z = {
let g = super::ops::Gate {
qm: -Fp3::ONE,
qk: Fp3::ONE,
..Default::default()
};
o.gate(g, x, inv, inv)
};
let g = super::ops::Gate {
qm: Fp3::ONE,
..Default::default()
};
o.assert_gate(g, x, z, z, "is_zero");
z
}
fn shape(p: &Params, pf: &StepProof) {
check_shape(p, pf).expect("step proof shape");
}
pub fn check_shape(p: &Params, pf: &StepProof) -> Result<(), String> {
let ok = pf.ood.iter().all(|a| a.len() == p.fresh)
&& pf.b_in.len() == W1 + W2
&& pf.b_out.len() == W1 + W2
&& pf.b_v.len() == BV
&& pf.zerocheck.len() == p.n
&& pf.zerocheck.iter().all(|m| m.len() == p.zc_deg())
&& pf.local.len() == COLS
&& pf.next.len() == COLS
&& pf.pub_nox.len() == PUB_NOX
&& pf.pub_v.len() == PUB_V
&& pf.fold_g.len() + 1 == p.g_deg
&& pf.fold_pn.len() + 1 == p.pn_deg
&& pf.fold_pv.len() + 1 == p.pv_deg
&& pf.shift.len() == 2 * p.n
&& pf.acc.sumcheck.len() == 2 * p.vars
&& pf.acc.evals.len() == 4
&& pf.acc.ood.len() == p.cfg.ood
&& pf.acc.comb_nonce < nebu::field::P
&& pf.acc.query_nonce < nebu::field::P
&& pf.acc.openings.len() == p.cfg.queries
&& pf.acc.openings.iter().all(|q| {
q.len() == 4
&& q.iter().enumerate().all(|(i, l)| {
l.symbols.len() == 1 << p.cfg.layout.log_width
&& l.path.len() == p.arity.path_len(p.cfg.layout.log_leaves() as usize)
&& (i == 0
|| l.symbols
.iter()
.all(|s| s.c1 == Goldilocks::ZERO && s.c2 == Goldilocks::ZERO))
})
});
if ok {
Ok(())
} else {
Err("recursion: step proof shape".into())
}
}