use nebu::{Fp3, Goldilocks};
use crate::air::Trace;
use crate::execution::ExecutionNoun;
use crate::machine::{self, air::Machine};
use crate::recursion::circuit::{air::CircuitAir, builder::Builder, trace};
use crate::recursion::decide;
use crate::recursion::ivc::{self, IvcProof};
use crate::recursion::ops::{Native, Ops};
use crate::recursion::params::Params;
use crate::recursion::perm::RATE;
use crate::recursion::program;
use crate::recursion::prove::{self, AccData, StepInput, pbar_nox, pbar_v};
use crate::recursion::relation::{G_POINT, Relation, WORD};
use crate::recursion::state::{self, CtxParts, State, ZeroWord};
use crate::recursion::step;
use crate::recursion::word::{Digest, Word};
const N: u32 = 15;
fn whir() -> lens::WhirParams {
let mut w = crate::execution::succinct::params_for(20);
w.log_inv_rate = 4;
w.pow_bits = 24;
w
}
fn pr(a: ExecutionNoun, b: ExecutionNoun) -> ExecutionNoun {
ExecutionNoun::Pair(Box::new(a), Box::new(b))
}
fn op(t: u64, a: ExecutionNoun, b: ExecutionNoun) -> ExecutionNoun {
pr(ExecutionNoun::Atom(t), pr(a, b))
}
fn tree_program(k: usize) -> ExecutionNoun {
let axis2 = pr(ExecutionNoun::Atom(0), ExecutionNoun::Atom(2));
let quote1 = pr(ExecutionNoun::Atom(1), ExecutionNoun::Atom(1));
(0..k).fold(op(5, axis2, quote1), |f, _| op(5, f.clone(), f))
}
fn base_lift(d: Digest) -> [Fp3; 4] {
d.map(Fp3::from_base)
}
fn row0(seg: &Trace, n2: &Trace) -> Vec<Goldilocks> {
seg.row(0).iter().chain(n2.row(0)).copied().collect()
}
fn unchecked_digest(st: &State) -> Digest {
let mut o = Native::new();
let d = state::digest(&mut o, st);
[d[0].c0, d[1].c0, d[2].c0, d[3].c0]
}
fn state_lanes(st: &State) -> Vec<(usize, usize)> {
let items = st.items();
let (mut used, mut block) = (0, 0);
let mut pos = Vec::with_capacity(items.len());
for (i, &(_, ext)) in items.iter().enumerate() {
let w = if ext { 3 } else { 1 };
if used + w > RATE {
block += 1;
used = 0;
}
pos.push((block, used));
used += w;
if used == RATE && i + 1 < items.len() {
block += 1;
used = 0;
}
}
pos
}
#[test]
fn a_state_unbound_from_the_last_public_input_is_refused() {
let w = whir();
let run = machine::execute_exact(&tree_program(12), &[3], 1 << 30, N).unwrap();
assert_eq!(run.segments(), 2);
let k = ivc::key(&w, N as usize).unwrap();
let p: &Params = &k.params;
let n = p.n;
let layout = p.cfg.layout;
let segs = [Trace::new(WORD, 1 << n), run.segment(1)];
let mut chain = [Goldilocks::ZERO; 4];
let mut prefix = Vec::new();
let mut ood_a = Vec::new();
for seg in &segs {
let wa = Word::commit_base_a(layout, &seg.column_major(WORD), p.arity);
let mut o = Native::new();
let root = base_lift(wa.root());
let z = step::pre_points(&mut o, root, p.fresh);
let ys: Vec<Fp3> = z.iter().map(|&z| wa.univariate(z)).collect();
prefix.push(chain);
let c = step::chain_next(&mut o, base_lift(chain), root, &ys);
chain = c.map(|x| x.c0);
ood_a.push(ys);
}
let sd = ivc::statement_digest(&run.statement, n, run.start as u64, 2);
let ch = ivc::run_challenges(sd, chain);
let global = Machine::new(run.constants.clone(), &run.init, run.start, 0, 2 << n).publics;
let rel = Relation::new(run.machine(1), ch);
let parts = CtxParts {
statement: sd,
chain,
g0: rel.g(&vec![Fp3::ZERO; G_POINT]),
pn0: pbar_nox(&global, &vec![Fp3::ZERO; p.dims.pn], n),
pv0: pbar_v(&k.sparse, &vec![Fp3::ZERO; p.dims.pv], n),
};
let (n2_0, carry) = machine::phase2_build(&run.machine(0), &run.segment(0), &ch, Fp3::ZERO);
let (n2_1, _) = machine::phase2_build(&rel.machine, &run.segment(1), &ch, carry);
let mut b = Builder::new(false);
let live = b.live_var();
let (init_v, h) = program::run(
&mut b,
p,
live,
&program::dummy_state(p),
&program::dummy_proof(p),
&parts,
|b, c| b.set_output(c),
);
b.finish().unwrap();
let d: Digest = h.map(|v| b.value(v).c0);
let honest_prev: State = state::StateV::from_items(
&p.dims,
&init_v
.items()
.iter()
.map(|&(v, _)| b.value(v))
.collect::<Vec<_>>(),
);
let (v1, _, _) = trace::generate(&b, &CircuitAir::default(), n).unwrap();
let word_a = Word::commit_base_a(layout, &segs[1].column_major(WORD), p.arity);
let b_out = row0(&run.segment(0), &n2_0);
let input = StepInput {
rel: &rel,
global: &global,
n1: &segs[1],
n2: &n2_1,
word_a: &word_a,
ood_a: &ood_a[1],
b_out: &b_out,
v1: &v1,
key: &k.pre,
sparse: &k.sparse,
};
let forge = |s: &State, x: Digest| -> IvcProof {
let (pf, data, _) = prove::prove(
p,
&input,
s,
x,
&AccData::Zero(ZeroWord::new(&layout, p.arity)),
)
.unwrap();
let mut o = Native::batched();
let fin = step::verify(&mut o, p, s, base_lift(x), &pf);
o.finish().unwrap();
let AccData::Word(word) = data else {
panic!("last accumulator")
};
let decider = decide::prove(&k.dcfg, &word, &fin.acc, &fin.pv, k.words(), n).unwrap();
IvcProof {
log_rows: N,
start: run.start as u64,
segments: 2,
chain,
state: s.clone(),
step: pf,
decider,
}
};
let mut s = honest_prev.clone();
s.step = Fp3::ONE;
s.chain = base_lift(prefix[1]);
s.b_first = b_out.iter().map(|&x| Fp3::from_base(x)).collect();
s.b_last = row0(&segs[1], &n2_1)
.iter()
.map(|&x| Fp3::from_base(x))
.collect();
s.acc.spot[0].0 = Fp3::new(Goldilocks::ONE, Goldilocks::ONE, Goldilocks::ZERO);
let lanes = state_lanes(&s);
let last = lanes.last().unwrap().0;
let tail: Vec<usize> = (0..lanes.len()).filter(|&i| lanes[i].0 == last).collect();
let pv = p.dims.pv;
let d = if tail.len() == 3 {
s.pv.point[pv - 2] = Fp3::new(d[0], d[1], d[2]);
s.pv.point[pv - 1] = Fp3::new(d[3], Goldilocks::ZERO, Goldilocks::ZERO);
s.pv.value = pbar_v(&k.sparse, &s.pv.point, n);
assert_eq!(
unchecked_digest(&s),
d,
"the unchecked digest is the raw lanes"
);
d
} else {
s.pv.value = pbar_v(&k.sparse, &s.pv.point, n);
unchecked_digest(&s)
};
assert!(!state::is_canonical(&s));
let forged = forge(&s, d);
let r = ivc::verify(&run.statement, &forged, &w);
assert!(
r.is_err(),
"a state unbound from the step's public input was accepted"
);
let x = unchecked_digest(&honest_prev);
assert_eq!(x, h.map(|v| b.value(v).c0));
let restarted = forge(&honest_prev, x);
let r = ivc::verify(&run.statement, &restarted, &w);
assert!(r.is_err(), "a base step in the last position was accepted");
}
#[test]
fn the_base_step_outputs_the_initial_state_whatever_it_is_given() {
let p = Params::new(&whir(), N as usize).unwrap();
let parts = CtxParts {
statement: [
Goldilocks::new(1),
Goldilocks::new(2),
Goldilocks::new(3),
Goldilocks::new(4),
],
chain: [Goldilocks::new(5); 4],
g0: Fp3::from_base(Goldilocks::new(6)),
pn0: Fp3::from_base(Goldilocks::new(7)),
pv0: Fp3::from_base(Goldilocks::new(8)),
};
let mut prev = program::dummy_state(&p);
prev.step = Fp3::from_base(Goldilocks::new(41));
prev.g.value = Fp3::from_base(Goldilocks::new(99));
let mut b = Builder::new(false);
let live = b.live_var();
let (out, _) = program::run(
&mut b,
&p,
live,
&prev,
&program::dummy_proof(&p),
&parts,
|_, _| {},
);
b.finish().unwrap();
let got: Vec<Fp3> = out.items().iter().map(|&(v, _)| b.value(v)).collect();
let mut o = Native::new();
let want = state::init(
&mut o,
&p.dims,
base_lift(state::ctx_native(&parts)),
p.zero_root,
parts.g0,
parts.pn0,
parts.pv0,
);
let want: Vec<Fp3> = want.items().iter().map(|&(v, _)| v).collect();
assert_eq!(got, want);
assert_eq!(got[4], Fp3::ZERO, "step count");
}
#[test]
fn a_non_canonical_grinding_nonce_is_refused() {
let p = Params::new(&whir(), N as usize).unwrap();
let pf = program::dummy_proof(&p);
assert!(step::check_shape(&p, &pf).is_ok());
for k in 0..2 {
let mut bad = pf.clone();
let nonce = if k == 0 {
&mut bad.acc.comb_nonce
} else {
&mut bad.acc.query_nonce
};
*nonce = nebu::field::P;
assert!(step::check_shape(&p, &bad).is_err());
}
}