use nebu::{Fp3, Goldilocks};
use super::perm::{self, NODE_TAG, RATE, WIDTH};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Gate {
pub qm: Fp3,
pub qs: Fp3,
pub qa: Fp3,
pub qb: Fp3,
pub qc: Fp3,
pub qk: Fp3,
}
impl Gate {
pub fn eval(&self, x: Fp3, y: Fp3, z: Fp3) -> Fp3 {
let (zero, one) = (Fp3::ZERO, Fp3::ONE);
let scale = |c: Fp3, v: Fp3| if c == one { v } else { c * v };
let mut out = self.qk;
if self.qm != zero {
let t = if self.qs == zero {
y
} else {
y + scale(self.qs, z)
};
out += scale(self.qm, x * t);
}
if self.qa != zero {
out += scale(self.qa, x);
}
if self.qb != zero {
out += scale(self.qb, y);
}
if self.qc != zero {
out += scale(self.qc, z);
}
out
}
}
#[derive(Clone, Copy, Debug)]
pub enum In<V> {
Var(V, bool),
Free(V, bool),
Zero,
Keep,
}
impl<V> In<V> {
pub fn width(&self) -> usize {
match self {
In::Var(_, true) | In::Free(_, true) => 3,
_ => 1,
}
}
}
pub trait Ops {
type V: Copy + core::fmt::Debug;
type Chain;
fn value(&self, v: Self::V) -> Fp3;
fn native(&self) -> bool {
false
}
fn gate(&mut self, g: Gate, x: Self::V, y: Self::V, z: Self::V) -> Self::V;
fn assert_gate(&mut self, g: Gate, x: Self::V, y: Self::V, z: Self::V, what: &'static str);
fn witness(&mut self, x: Fp3) -> Self::V;
fn alloc(&mut self, x: Fp3) -> Self::V;
fn constant(&mut self, x: Fp3) -> Self::V;
fn chain(&mut self, tag: u64) -> Self::Chain;
fn permute(&mut self, c: &mut Self::Chain, items: &[In<Self::V>]);
fn out_base(&mut self, c: &Self::Chain, lane: usize) -> Self::V;
fn out_ext(&mut self, c: &Self::Chain, lane: usize) -> Self::V;
fn node(&mut self, c: &mut Self::Chain, bit: Self::V, sibling: [Goldilocks; 4]);
fn node4(&mut self, c: &mut Self::Chain, bits: [Self::V; 2], siblings: [[Goldilocks; 4]; 3]);
fn digest_eq(&mut self, c: &Self::Chain, root: [Self::V; 4], what: &'static str);
fn bits(&mut self, v: Self::V, want: usize) -> Vec<Self::V>;
fn graph(&mut self, g: &crate::air::num::Graph, inputs: &[Self::V]) -> Vec<Self::V>
where
Self: Sized,
{
super::expr::compile(self, g, inputs)
}
fn message(&mut self, items: &[Fp3]) -> (Vec<Self::V>, [Self::V; 4])
where
Self: Sized,
{
super::msg::absorb_generic(self, items)
}
fn graph_compiled(
&mut self,
g: &crate::air::num::Graph,
c: &crate::air::num::Compiled,
inputs: &[Self::V],
) -> Vec<Self::V>
where
Self: Sized,
{
let _ = c;
self.graph(g, inputs)
}
}
#[derive(Default)]
pub struct Native {
pub error: Option<String>,
pub batch: bool,
deferred: Vec<Job>,
}
#[derive(Clone)]
enum NOp {
Block([Option<Goldilocks>; RATE]),
Node(bool, [Goldilocks; 4]),
Node4(usize, [[Goldilocks; 4]; 3]),
}
#[derive(Clone)]
pub struct NChain {
tag: u64,
state: Option<[Goldilocks; WIDTH]>,
ops: Option<Vec<NOp>>,
}
impl NChain {
pub fn input(&self, items: &[In<Fp3>]) -> [Goldilocks; WIDTH] {
let mut s = self.state.unwrap_or_else( {
let mut s = [Goldilocks::ZERO; WIDTH];
s[RATE] = Goldilocks::new(self.tag);
s
});
let fresh = self.state.is_none();
let mut lane = 0;
for it in items {
match *it {
In::Var(v, ext) In::Free(v, ext) => {
s[lane] = v.c0;
if ext {
s[lane + 1] = v.c1;
s[lane + 2] = v.c2;
}
}
In::Zero => s[lane] = Goldilocks::ZERO,
In::Keep => {
if fresh {
s[lane] = Goldilocks::ZERO;
}
}
}
lane += it.width();
}
s
}
}
impl Native {
pub fn new() -> Self {
Self::default()
}
pub fn batched() -> Self {
Self {
batch: true,
..Self::default()
}
}
fn fail(&mut self, what: &str) {
if self.error.is_none() {
self.error = Some(what.to_string());
}
}
pub fn finish(&mut self) -> Result<(), String> {
let jobs = core::mem::take(&mut self.deferred);
if self.error.is_none() && !jobs.is_empty() {
let threads = crate::recursion::wrap::verifier_threads()
.min(jobs.len().div_ceil(8))
.max(1);
let size = jobs.len().div_ceil(threads);
let groups = jobs.len().div_ceil(size);
let bad: Option<&'static str> = super::pool::map_chunks(groups, threads, r {
r.filter_map(g run_chains(&jobs[g * size..((g + 1) * size).min(jobs.len())]))
.next()
})
.into_iter()
.flatten()
.next();
if let Some(what) = bad {
self.fail(what);
}
}
match &self.error {
None => Ok(()),
Some(e) => Err(format!("recursion: {e}")),
}
}
}
type Job = (Vec<NOp>, [Goldilocks; 4], &'static str);
fn run_chains(jobs: &[Job]) -> Option<&'static str> {
let mut fresh = [Goldilocks::ZERO; WIDTH];
fresh[RATE] = Goldilocks::new(super::perm::tag::LEAF);
let mut states = vec![fresh; jobs.len()];
let longest = jobs.iter().map(j j.0.len()).max().unwrap_or(0);
for k in 0..longest {
let idx: Vec<usize> = (0..jobs.len()).filter(&q k < jobs[q].0.len()).collect();
let mut batch: Vec<[Goldilocks; WIDTH]> = idx
.iter()
.map(&q {
let prev = states[q];
match &jobs[q].0[k] {
NOp::Block(l) => {
let mut x = prev;
for (i, v) in l.iter().enumerate() {
match v {
Some(v) => x[i] = *v,
None if k == 0 => x[i] = Goldilocks::ZERO,
None => {}
}
}
x
}
NOp::Node(right, sib) => {
let cur = [prev[0], prev[1], prev[2], prev[3]];
let (l, r) = if *right { (*sib, cur) } else { (cur, *sib) };
perm::node_input(l, r)
}
NOp::Node4(pos, sibs) => {
let cur = [prev[0], prev[1], prev[2], prev[3]];
perm::node4_input(perm::children4(cur, *pos, *sibs))
}
}
})
.collect();
perm::permute_many_recall(&mut batch);
for (&q, s) in idx.iter().zip(batch) {
states[q] = s;
}
}
states
.iter()
.zip(jobs)
.find(|(s, j)| [s[0], s[1], s[2], s[3]] != j.1)
.map(|(_, j)| j.2)
}
fn base_of(x: Fp3) -> Option<Goldilocks> {
(x.c1 == Goldilocks::ZERO && x.c2 == Goldilocks::ZERO).then_some(x.c0)
}
impl Ops for Native {
type V = Fp3;
type Chain = NChain;
fn native(&self) -> bool {
true
}
fn graph(&mut self, g: &crate::air::num::Graph, inputs: &[Fp3]) -> Vec<Fp3> {
g.eval(inputs)
}
fn message(&mut self, items: &[Fp3]) -> (Vec<Fp3>, [Fp3; 4]) {
if self.error.is_some() {
return (items.to_vec(), [Fp3::ZERO; 4]);
}
(
items.to_vec(),
super::msg::digest_native(items).map(Fp3::from_base),
)
}
fn graph_compiled(
&mut self,
_: &crate::air::num::Graph,
c: &crate::air::num::Compiled,
inputs: &[Fp3],
) -> Vec<Fp3> {
c.eval(inputs)
}
fn value(&self, v: Fp3) -> Fp3 {
v
}
fn gate(&mut self, g: Gate, x: Fp3, y: Fp3, z: Fp3) -> Fp3 {
g.eval(x, y, z)
}
fn assert_gate(&mut self, g: Gate, x: Fp3, y: Fp3, z: Fp3, what: &'static str) {
if g.eval(x, y, z) != Fp3::ZERO {
self.fail(what);
}
}
fn witness(&mut self, x: Fp3) -> Fp3 {
x
}
fn alloc(&mut self, x: Fp3) -> Fp3 {
x
}
fn constant(&mut self, x: Fp3) -> Fp3 {
x
}
fn chain(&mut self, tag: u64) -> NChain {
let ops = (self.batch && tag == super::perm::tag::LEAF).then(Vec::new);
NChain {
tag,
state: None,
ops,
}
}
fn permute(&mut self, c: &mut NChain, items: &[In<Fp3>]) {
if let Some(ops) = &mut c.ops {
let mut l = [None; RATE];
let mut lane = 0;
for it in items {
match *it {
In::Var(v, ext) | In::Free(v, ext) => {
if ext {
l[lane] = Some(v.c0);
l[lane + 1] = Some(v.c1);
l[lane + 2] = Some(v.c2);
} else {
if base_of(v).is_none() {
self.error.get_or_insert_with(|| {
"a base lane holds an extension value".into()
});
}
l[lane] = Some(v.c0);
}
}
In::Zero => l[lane] = Some(Goldilocks::ZERO),
In::Keep => {}
}
lane += it.width();
}
ops.push(NOp::Block(l));
return;
}
let mut s = match c.state {
Some(s) => s,
None => {
let mut s = [Goldilocks::ZERO; WIDTH];
s[RATE] = Goldilocks::new(c.tag);
s
}
};
let fresh = c.state.is_none();
let mut lane = 0;
for it in items {
let w = it.width();
assert!(lane + w <= RATE, "permutation items straddle the rate");
match *it {
In::Var(v, ext) | In::Free(v, ext) => {
if ext {
s[lane] = v.c0;
s[lane + 1] = v.c1;
s[lane + 2] = v.c2;
} else {
match base_of(v) {
Some(b) => s[lane] = b,
None => {
self.fail("a base lane holds an extension value");
s[lane] = v.c0;
}
}
}
}
In::Zero => s[lane] = Goldilocks::ZERO,
In::Keep => {
if fresh {
s[lane] = Goldilocks::ZERO;
}
}
}
lane += w;
}
assert_eq!(lane, RATE, "permutation items cover the rate");
if self.error.is_none() {
perm::permute(&mut s);
}
c.state = Some(s);
}
fn out_base(&mut self, c: &NChain, lane: usize) -> Fp3 {
Fp3::from_base(c.state.expect("a block")[lane])
}
fn out_ext(&mut self, c: &NChain, lane: usize) -> Fp3 {
let s = c.state.expect("a block");
Fp3::new(s[lane], s[lane + 1], s[lane + 2])
}
fn node(&mut self, c: &mut NChain, bit: Fp3, sibling: [Goldilocks; 4]) {
if let Some(ops) = &mut c.ops {
if bit != Fp3::ONE && bit != Fp3::ZERO {
self.error
.get_or_insert_with(|| "a Merkle direction is not a bit".into());
}
ops.push(NOp::Node(bit == Fp3::ONE, sibling));
return;
}
let s = c.state.expect("a block");
let cur = [s[0], s[1], s[2], s[3]];
let right = if bit == Fp3::ONE {
true
} else {
if bit != Fp3::ZERO {
self.fail("a Merkle direction is not a bit");
}
false
};
let (l, r) = if right {
(sibling, cur)
} else {
(cur, sibling)
};
c.state = Some(if self.error.is_none() {
perm::node_state(l, r)
} else {
perm::node_input(l, r)
});
}
fn node4(&mut self, c: &mut NChain, bits: [Fp3; 2], siblings: [[Goldilocks; 4]; 3]) {
let mut pos = 0;
for (k, &b) in bits.iter().enumerate() {
if b == Fp3::ONE {
pos = 1 << k;
} else if b != Fp3::ZERO {
self.fail("a Merkle direction is not a bit");
}
}
if let Some(ops) = &mut c.ops {
ops.push(NOp::Node4(pos, siblings));
return;
}
let s = c.state.expect("a block");
let mut x = perm::node4_input(perm::children4([s[0], s[1], s[2], s[3]], pos, siblings));
if self.error.is_none() {
perm::permute(&mut x);
}
c.state = Some(x);
}
fn digest_eq(&mut self, c: &NChain, root: [Fp3; 4], what: &'static str) {
if let Some(ops) = &c.ops {
match root.iter().map(|r| base_of(*r)).collect::<Option<Vec<_>>>() {
Some(r) => self
.deferred
.push((ops.clone(), [r[0], r[1], r[2], r[3]], what)),
None => self.fail(what),
}
return;
}
let s = c.state.expect("a block");
if (0..4).any(|i| Fp3::from_base(s[i]) != root[i]) {
self.fail(what);
}
}
fn bits(&mut self, v: Fp3, want: usize) -> Vec<Fp3> {
let Some(b) = base_of(v) else {
self.fail("bits of an extension value");
return vec![Fp3::ZERO; want];
};
let x = b.as_u64();
(0..want)
.map(|i| Fp3::from_base(Goldilocks::new((x >> i) & 1)))
.collect()
}
}
pub fn node_digest(l: [Goldilocks; 4], r: [Goldilocks; 4]) -> [Goldilocks; 4] {
let s = perm::node_state(l, r);
[s[0], s[1], s[2], s[3]]
}
pub trait Arith: Ops {
fn zero(&mut self) -> Self::V {
self.constant(Fp3::ZERO)
}
fn one(&mut self) -> Self::V {
self.constant(Fp3::ONE)
}
fn add(&mut self, x: Self::V, y: Self::V) -> Self::V {
let g = Gate {
qa: Fp3::ONE,
qb: Fp3::ONE,
..Gate::default()
};
self.gate(g, x, y, y)
}
fn sub(&mut self, x: Self::V, y: Self::V) -> Self::V {
let g = Gate {
qa: Fp3::ONE,
qb: -Fp3::ONE,
..Gate::default()
};
self.gate(g, x, y, y)
}
fn mul(&mut self, x: Self::V, y: Self::V) -> Self::V {
let g = Gate {
qm: Fp3::ONE,
..Gate::default()
};
self.gate(g, x, y, y)
}
fn mul_add(&mut self, x: Self::V, y: Self::V, z: Self::V) -> Self::V {
let g = Gate {
qm: Fp3::ONE,
qc: Fp3::ONE,
..Gate::default()
};
self.gate(g, x, y, z)
}
fn scale_add(&mut self, c: Fp3, x: Self::V, z: Self::V) -> Self::V {
let g = Gate {
qa: c,
qc: Fp3::ONE,
..Gate::default()
};
self.gate(g, x, z, z)
}
fn affine(&mut self, c: Fp3, x: Self::V, k: Fp3) -> Self::V {
let g = Gate {
qa: c,
qk: k,
..Gate::default()
};
self.gate(g, x, x, x)
}
fn lerp(&mut self, r: Self::V, a: Self::V, b: Self::V) -> Self::V {
let g = Gate {
qm: Fp3::ONE,
qs: -Fp3::ONE,
qc: Fp3::ONE,
..Gate::default()
};
self.gate(g, r, b, a)
}
fn mul_const_add(&mut self, x: Self::V, y: Self::V, k: Fp3) -> Self::V {
let g = Gate {
qm: Fp3::ONE,
qk: k,
..Gate::default()
};
self.gate(g, x, y, y)
}
fn assert_eq(&mut self, x: Self::V, y: Self::V, what: &'static str) {
let g = Gate {
qa: Fp3::ONE,
qb: -Fp3::ONE,
..Gate::default()
};
self.assert_gate(g, x, y, y, what);
}
fn assert_const(&mut self, x: Self::V, c: Fp3, what: &'static str) {
let g = Gate {
qa: Fp3::ONE,
qk: -c,
..Gate::default()
};
self.assert_gate(g, x, x, x, what);
}
fn inv(&mut self, x: Self::V, what: &'static str) -> Self::V {
let xv = self.value(x);
let w = self.witness(if xv == Fp3::ZERO { Fp3::ZERO } else { xv.inv() });
let g = Gate {
qm: Fp3::ONE,
qk: -Fp3::ONE,
..Gate::default()
};
self.assert_gate(g, x, w, w, what);
w
}
fn assert_ne_const(&mut self, x: Self::V, c: Fp3, what: &'static str) {
let d = self.affine(Fp3::ONE, x, -c);
self.inv(d, what);
}
fn lin(&mut self, terms: &[(Fp3, Self::V)]) -> Self::V {
let mut acc = self.zero();
for &(c, x) in terms {
acc = self.scale_add(c, x, acc);
}
acc
}
fn dot(&mut self, xs: &[Self::V], ys: &[Self::V]) -> Self::V {
let mut acc = self.zero();
for (&x, &y) in xs.iter().zip(ys) {
acc = self.mul_add(x, y, acc);
}
acc
}
fn sum(&mut self, xs: &[Self::V]) -> Self::V {
let mut acc = self.zero();
for &x in xs {
acc = self.add(acc, x);
}
acc
}
}
impl<O: Ops> Arith for O {}
pub const NODE: u64 = NODE_TAG;