use hemera::field::Goldilocks as HGold;
use hemera::permutation::permute;
use nebu::Goldilocks;
#[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 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]
}
pub fn compress4(a: &[Goldilocks; 4], b: &[Goldilocks; 4]) -> [Goldilocks; 4] {
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());
}
permute(&mut state);
core::array::from_fn(i Goldilocks::new(state[i].as_canonical_u64()))
}
pub fn root_from_leaves(leaves: &RootLeaves) -> [Goldilocks; 4] {
leaves.ordered().iter().fold(root_iv(), acc, leaf compress4(&acc, leaf))
}
pub fn root_to_bytes(root: &[Goldilocks; 4]) -> [u8; 32] {
let mut out = [0u8; 32];
for (i, limb) in root.iter().enumerate() {
out[i * 8..(i + 1) * 8].copy_from_slice(&limb.as_u64().to_le_bytes());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
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)],
}
}
fn zero_leaves() -> RootLeaves {
RootLeaves {
dims: [[Goldilocks::ZERO; 4]; 11],
a: [Goldilocks::ZERO; 4],
n: [Goldilocks::ZERO; 4],
stats: [Goldilocks::ZERO; 4],
}
}
#[test]
fn root_is_the_hemera_fold_over_the_ordered_leaves() {
for leaves in [sample_leaves(), zero_leaves()] {
let mut acc = [ROOT_TAG, 0, 0, 0];
for leaf in leaves.ordered() {
let mut st = [HGold::ZERO; 16];
for i in 0..4 {
st[i] = HGold::new(acc[i]);
st[4 + i] = HGold::new(leaf[i].as_u64());
}
permute(&mut st);
acc = core::array::from_fn(i st[i].as_canonical_u64());
}
assert_eq!(root_from_leaves(&leaves).map(v v.as_u64()), acc);
}
assert_eq!(ROOT_TAG, u64::from_le_bytes(*b"bbg-root"));
}
#[test]
fn root_binds_every_leaf() {
let base = root_from_leaves(&sample_leaves());
for i in 0..14 {
for k in 0..4 {
let mut l = sample_leaves().ordered();
l[i][k] += g(1);
let leaves = RootLeaves {
dims: l[..11].try_into().unwrap(),
a: l[11],
n: l[12],
stats: l[13],
};
assert_ne!(root_from_leaves(&leaves), base, "leaf {i} limb {k}");
}
}
}
#[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));
}
}