use nebu::Goldilocks;
use hemera::field::Goldilocks as HGold;
use hemera::permutation::{permute, permute_traced};
use super::transcript::{squeeze_ccs_pairs, SqueezeVisitor};
use crate::types::{CCSInstance, CCSWitness};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RootLeaves {
pub dims: [[Goldilocks; 4]; 11],
pub a: [Goldilocks; 4],
pub n: [Goldilocks; 4],
pub stats: [Goldilocks; 4],
}
impl RootLeaves {
pub fn ordered(&self) -> [[Goldilocks; 4]; 14] {
let mut out = [[Goldilocks::ZERO; 4]; 14];
out[..11].copy_from_slice(&self.dims);
out[11] = self.a;
out[12] = self.n;
out[13] = self.stats;
out
}
pub fn solo(ns: usize, dim: [Goldilocks; 4]) -> Self {
let mut leaves = Self {
dims: [[Goldilocks::ZERO; 4]; 11],
a: [Goldilocks::ZERO; 4],
n: [Goldilocks::ZERO; 4],
stats: [Goldilocks::ZERO; 4],
};
leaves.dims[ns] = dim;
leaves
}
}
pub const ROOT_TAG: u64 = u64::from_le_bytes(*b"bbg-root");
pub fn root_iv() -> [Goldilocks; 4] {
[Goldilocks::new(ROOT_TAG), Goldilocks::ZERO, Goldilocks::ZERO, Goldilocks::ZERO]
}
fn to_hstate(a: &[Goldilocks; 4], b: &[Goldilocks; 4]) -> [HGold; 16] {
let mut state = [HGold::ZERO; 16];
for i in 0..4 {
state[i] = HGold::new(a[i].canonicalize().as_u64());
state[4 + i] = HGold::new(b[i].canonicalize().as_u64());
}
state
}
fn first4(state: &[HGold; 16]) -> [Goldilocks; 4] {
core::array::from_fn(i Goldilocks::new(state[i].as_canonical_u64()))
}
pub fn compress4(a: &[Goldilocks; 4], b: &[Goldilocks; 4]) -> [Goldilocks; 4] {
let mut state = to_hstate(a, b);
permute(&mut state);
first4(&state)
}
pub fn root_from_leaves(leaves: &RootLeaves) -> [Goldilocks; 4] {
leaves.ordered().iter().fold(root_iv(), acc, leaf compress4(&acc, leaf))
}
pub fn build_root_steps(leaves: &RootLeaves) -> (Vec<(CCSInstance, CCSWitness)>, [Goldilocks; 4]) {
let mut steps = Vec::new();
let mut acc = root_iv();
for leaf in leaves.ordered() {
let mut state = to_hstate(&acc, &leaf);
let mut visitor = SqueezeVisitor::new();
permute_traced(&mut state, &mut visitor);
steps.extend(squeeze_ccs_pairs(&visitor));
acc = first4(&state);
}
(steps, acc)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ccs::selector::is_satisfied;
fn g(v: u64) -> Goldilocks {
Goldilocks::new(v)
}
fn sample_leaves() -> RootLeaves {
RootLeaves {
dims: core::array::from_fn(d core::array::from_fn(k g((d * 4 + k + 1) as u64))),
a: [g(101), g(102), g(103), g(104)],
n: [g(201), g(202), g(203), g(204)],
stats: [g(301), g(302), g(303), g(304)],
}
}
#[test]
fn root_is_deterministic() {
assert_eq!(root_from_leaves(&sample_leaves()), root_from_leaves(&sample_leaves()));
}
#[test]
fn root_binds_every_leaf() {
let base = root_from_leaves(&sample_leaves());
let mut l = sample_leaves();
l.dims[0][0] = g(999);
assert_ne!(root_from_leaves(&l), base);
let mut l = sample_leaves();
l.dims[10][3] = g(999);
assert_ne!(root_from_leaves(&l), base);
let mut l = sample_leaves();
l.a[0] = g(999);
assert_ne!(root_from_leaves(&l), base);
let mut l = sample_leaves();
l.stats[3] = g(999);
assert_ne!(root_from_leaves(&l), base);
}
#[test]
fn compress_is_order_sensitive() {
let a = [g(1), g(2), g(3), g(4)];
let b = [g(5), g(6), g(7), g(8)];
assert_ne!(compress4(&a, &b), compress4(&b, &a));
}
#[test]
fn root_steps_match_native_and_satisfy() {
let leaves = sample_leaves();
let (steps, computed) = build_root_steps(&leaves);
assert_eq!(computed, root_from_leaves(&leaves), "replay matches native fold");
assert_eq!(steps.len(), 14 * 24, "24 round pairs per compression");
for (i, (instance, witness)) in steps.iter().enumerate() {
assert!(is_satisfied(instance, witness), "root step {i} unsatisfied");
}
}
}