extern crate alloc;
use nebu::Goldilocks;
use crate::data::{Reduction, Order};
use crate::encode::{Particle, particle_id};
fn g(v: u64) -> Goldilocks { Goldilocks::new(v) }
fn atom<const N: usize>(ar: &mut Reduction<N>, v: u64) -> Option<Order> {
ar.atom(g(v))
}
fn axis_f<const N: usize>(ar: &mut Reduction<N>, n: u64) -> Option<Order> {
let t = atom(ar, 0)?;
let a = atom(ar, n)?;
ar.pair(t, a)
}
fn quote_f<const N: usize>(ar: &mut Reduction<N>, x: Order) -> Option<Order> {
let t = atom(ar, 1)?;
ar.pair(t, x)
}
fn compose_f<const N: usize>(ar: &mut Reduction<N>, a: Order, b: Order) -> Option<Order> {
let t = atom(ar, 2)?;
let body = ar.pair(a, b)?;
ar.pair(t, body)
}
fn cons_f<const N: usize>(ar: &mut Reduction<N>, a: Order, b: Order) -> Option<Order> {
let t = atom(ar, 3)?;
let body = ar.pair(a, b)?;
ar.pair(t, body)
}
fn branch_f<const N: usize>(ar: &mut Reduction<N>, test: Order, yes: Order, no: Order) -> Option<Order> {
let t = atom(ar, 4)?;
let arms = ar.pair(yes, no)?;
let body = ar.pair(test, arms)?;
ar.pair(t, body)
}
fn binop_f<const N: usize>(ar: &mut Reduction<N>, tag: u64, a: Order, b: Order) -> Option<Order> {
let t = atom(ar, tag)?;
let body = ar.pair(a, b)?;
ar.pair(t, body)
}
fn hash_f<const N: usize>(ar: &mut Reduction<N>, sub: Order) -> Option<Order> {
let t = atom(ar, 15)?;
ar.pair(t, sub)
}
pub fn build_poly_eval_formula<const N: usize>(ar: &mut Reduction<N>) -> Option<Order> {
let ax4 = axis_f(ar, 4)?; let ax5 = axis_f(ar, 5)?; let ax6 = axis_f(ar, 6)?; let ax12 = axis_f(ar, 12)?; let ax13 = axis_f(ar, 13)?; let ax14 = axis_f(ar, 14)?; let ax15 = axis_f(ar, 15)?;
let one = atom(ar, 1)?;
let q1 = quote_f(ar, one)?;
let k_minus_1 = binop_f(ar, 6, ax4, q1)?;
let build_lhs = cons_f(ar, k_minus_1, ax5)?;
let left_pair = cons_f(ar, ax12, ax15)?;
let new_left_obj = cons_f(ar, build_lhs, left_pair)?;
let call_left = compose_f(ar, new_left_obj, ax5)?;
let right_pair = cons_f(ar, ax13, ax15)?;
let new_right_obj = cons_f(ar, build_lhs, right_pair)?;
let call_right = compose_f(ar, new_right_obj, ax5)?;
let inner_cons = cons_f(ar, call_right, ax14)?;
let result_cons = cons_f(ar, call_left, inner_cons)?;
let ax2c = axis_f(ar, 2)?;
let ax6c = axis_f(ar, 6)?;
let ax7c = axis_f(ar, 7)?;
let diff = binop_f(ar, 6, ax6c, ax2c)?; let prod = binop_f(ar, 7, ax7c, diff)?; let combiner = binop_f(ar, 5, ax2c, prod)?;
let q_combiner = quote_f(ar, combiner)?;
let recursive_step = compose_f(ar, result_cons, q_combiner)?;
let zero = atom(ar, 0)?;
let q0 = quote_f(ar, zero)?;
let test = binop_f(ar, 9, ax4, q0)?;
branch_f(ar, test, ax6, recursive_step)
}
pub fn poly_eval_formula_hash<const N: usize>(ar: &mut Reduction<N>) -> Option<Particle> {
let formula = build_poly_eval_formula(ar)?;
particle_id(ar, formula)
}
pub fn build_merkle_verify_formula<const N: usize>(ar: &mut Reduction<N>) -> Option<Order> {
let ax4 = axis_f(ar, 4)?; let ax5 = axis_f(ar, 5)?; let ax6 = axis_f(ar, 6)?; let ax10 = axis_f(ar, 10)?; let ax11 = axis_f(ar, 11)?; let ax15 = axis_f(ar, 15)?; let ax28 = axis_f(ar, 28)?; let ax29 = axis_f(ar, 29)?;
let zero_a = atom(ar, 0)?;
let one_a = atom(ar, 1)?;
let q0 = quote_f(ar, zero_a)?;
let q1 = quote_f(ar, one_a)?;
let eq_cr = binop_f(ar, 9, ax6, ax10)?;
let base_case = branch_f(ar, eq_cr, q1, q0)?;
let cons_sib_cur = cons_f(ar, ax28, ax6)?;
let hash_sib_cur = hash_f(ar, cons_sib_cur)?;
let cons_cur_sib = cons_f(ar, ax6, ax28)?;
let hash_cur_sib = hash_f(ar, cons_cur_sib)?;
let new_current = branch_f(ar, ax29, hash_sib_cur, hash_cur_sib)?;
let one_b = atom(ar, 1)?;
let q1b = quote_f(ar, one_b)?;
let depth_m1 = binop_f(ar, 6, ax4, q1b)?;
let new_meta = cons_f(ar, depth_m1, ax5)?;
let new_data = cons_f(ar, new_current, ax15)?;
let new_obj = cons_f(ar, new_meta, new_data)?;
let recursive_step = compose_f(ar, new_obj, ax11)?;
let eq_depth_0 = binop_f(ar, 9, ax4, q0)?;
branch_f(ar, eq_depth_0, base_case, recursive_step)
}
pub fn merkle_verify_formula_hash<const N: usize>(ar: &mut Reduction<N>) -> Option<Particle> {
let formula = build_merkle_verify_formula(ar)?;
particle_id(ar, formula)
}
pub fn build_fri_fold_formula<const N: usize>(ar: &mut Reduction<N>) -> Option<Order> {
let ax4 = axis_f(ar, 4)?; let ax5 = axis_f(ar, 5)?; let ax6 = axis_f(ar, 6)?; let ax7 = axis_f(ar, 7)?; let ax12 = axis_f(ar, 12)?; let ax13 = axis_f(ar, 13)?;
let one = atom(ar, 1)?;
let q1 = quote_f(ar, one)?;
let k_m1 = binop_f(ar, 6, ax4, q1)?;
let lhs = cons_f(ar, k_m1, ax5)?; let lp = cons_f(ar, ax12, ax7)?; let rp = cons_f(ar, ax13, ax7)?; let lo = cons_f(ar, lhs, lp)?; let ro = cons_f(ar, lhs, rp)?; let vl = compose_f(ar, lo, ax5)?; let vr = compose_f(ar, ro, ax5)?;
let ic = cons_f(ar, vr, ax7)?; let rc = cons_f(ar, vl, ic)?;
let av2 = axis_f(ar, 2)?;
let av6 = axis_f(ar, 6)?;
let av7 = axis_f(ar, 7)?;
let diff = binop_f(ar, 6, av6, av2)?; let prod = binop_f(ar, 7, av7, diff)?; let combiner = binop_f(ar, 5, av2, prod)?; let qc = quote_f(ar, combiner)?;
let step = compose_f(ar, rc, qc)?;
let zero = atom(ar, 0)?;
let q0 = quote_f(ar, zero)?;
let test = binop_f(ar, 9, ax4, q0)?; branch_f(ar, test, ax6, step) }
pub fn build_ntt_formula<const N: usize>(ar: &mut Reduction<N>) -> Option<Order> {
let ax4 = axis_f(ar, 4)?; let ax6 = axis_f(ar, 6)?; let ax7 = axis_f(ar, 7)?; let ax12 = axis_f(ar, 12)?; let ax13 = axis_f(ar, 13)?;
let zero = atom(ar, 0)?;
let q0 = quote_f(ar, zero)?;
let test = binop_f(ar, 9, ax4, q0)?;
let wb = binop_f(ar, 7, ax7, ax13)?; let top = binop_f(ar, 5, ax12, wb)?; let bot = binop_f(ar, 6, ax12, wb)?; let butterfly = cons_f(ar, top, bot)?;
branch_f(ar, test, ax6, butterfly)
}
pub(crate) const CYBERLINK_TAG: u64 = 1000;
pub(crate) const TRANSFER_TAG: u64 = 1001;
pub(crate) const INSERT_TAG: u64 = 1002;
pub(crate) const UPDATE_TAG: u64 = 1003;
pub(crate) const AGGREGATE_TAG: u64 = 1004;
pub(crate) const CONSERVE_TAG: u64 = 1005;
pub fn build_cyberlink_formula<const N: usize>(ar: &mut Reduction<N>) -> Option<Order> {
let tag = atom(ar, CYBERLINK_TAG)?;
let body = axis_f(ar, 1)?; ar.pair(tag, body)
}
pub fn build_decider_formula<const N: usize>(ar: &mut Reduction<N>) -> Option<Order> {
const DECIDER_TAG: u64 = 1099;
let tag = atom(ar, DECIDER_TAG)?;
let lhs = axis_f(ar, 2)?;
let rhs = axis_f(ar, 3)?;
let body = ar.pair(lhs, rhs)?;
ar.pair(tag, body)
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use crate::reduce::{reduce, Outcome};
use crate::call::NullCalls;
use crate::trace::NoTrace;
use crate::data::{Reduction, Order};
use hemera::StepSponge;
use hemera::field::Goldilocks as HemeraGold;
fn g(v: u64) -> Goldilocks { Goldilocks::new(v) }
fn build_evals_tree<const N: usize>(ar: &mut Reduction<N>, vals: &[Goldilocks]) -> Order {
assert!(vals.len().is_power_of_two() && !vals.is_empty());
if vals.len() == 1 {
return ar.atom(vals[0]).unwrap();
}
let mid = vals.len() / 2;
let left = build_evals_tree(ar, &vals[..mid]);
let right = build_evals_tree(ar, &vals[mid..]);
ar.pair(left, right).unwrap()
}
fn build_point<const N: usize>(ar: &mut Reduction<N>, coords: &[Goldilocks]) -> Order {
assert!(!coords.is_empty());
let term = ar.atom(g(0)).unwrap();
coords.iter().rev().fold(term, |acc, &c| {
let head = ar.atom(c).unwrap();
ar.pair(head, acc).unwrap()
})
}
fn run_poly_eval_formula<const N: usize>(
ar: &mut Reduction<N>, evals: &[Goldilocks], point: &[Goldilocks],
) -> Outcome {
let formula = build_poly_eval_formula(ar).unwrap();
let evals_tree = build_evals_tree(ar, evals);
let point_data = build_point(ar, point);
let k = ar.atom(g(evals.len().trailing_zeros() as u64)).unwrap();
let lhs = ar.pair(k, formula).unwrap();
let rhs = ar.pair(evals_tree, point_data).unwrap();
let obj = ar.pair(lhs, rhs).unwrap();
reduce(ar, obj, formula, 1_000_000, &NullCalls, &mut NoTrace)
}
#[test]
fn poly_eval_formula_at_zero() {
let mut ar = Reduction::<4096>::new();
match run_poly_eval_formula(&mut ar, &[g(3), g(7)], &[g(0)]) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap(), g(3)),
o => panic!("{:?}", o),
}
}
#[test]
fn poly_eval_formula_at_one() {
let mut ar = Reduction::<4096>::new();
match run_poly_eval_formula(&mut ar, &[g(3), g(7)], &[g(1)]) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap(), g(7)),
o => panic!("{:?}", o),
}
}
#[test]
fn poly_eval_formula_two_vars_at_half() {
let mut ar = Reduction::<4096>::new();
let p = 0xFFFF_FFFF_0000_0001u64; let half = (p + 1) / 2; match run_poly_eval_formula(&mut ar, &[g(0), g(2), g(4), g(6)], &[g(0), g(half)]) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap(), g(1)),
o => panic!("{:?}", o),
}
}
#[test]
fn poly_eval_formula_hash_is_stable() {
let mut ar1 = Reduction::<4096>::new();
let mut ar2 = Reduction::<4096>::new();
let h1 = poly_eval_formula_hash(&mut ar1).unwrap();
let h2 = poly_eval_formula_hash(&mut ar2).unwrap();
assert_eq!(h1, h2, "formula hash must be deterministic");
}
fn nox_hash_data<const N: usize>(ar: &mut Reduction<N>, id: Order) -> Order {
let dig = *ar.digest(id).unwrap();
let mut rate = [HemeraGold::ZERO; 8];
for i in 0..4 { rate[i] = HemeraGold::new(dig[i].as_u64()); }
let mut sponge = StepSponge::absorb(&rate);
let mut state = [HemeraGold::ZERO; 16];
while !sponge.done() { state = sponge.step(); }
let out = [
g(state[0].as_canonical_u64()),
g(state[1].as_canonical_u64()),
g(state[2].as_canonical_u64()),
g(state[3].as_canonical_u64()),
];
ar.hash_data(&out).unwrap()
}
fn two_leaf_tree<const N: usize>(ar: &mut Reduction<N>) -> (Order, Order, Order) {
let leaf0 = ar.atom(g(100)).unwrap();
let leaf1 = ar.atom(g(200)).unwrap();
let hn0 = nox_hash_data(ar, leaf0);
let hn1 = nox_hash_data(ar, leaf1);
let cons_id = ar.pair(hn0, hn1).unwrap();
let root = nox_hash_data(ar, cons_id);
(root, hn0, hn1)
}
fn build_path<const N: usize>(ar: &mut Reduction<N>, steps: &[(Order, u64)]) -> Order {
let term = ar.atom(g(0)).unwrap();
steps.iter().rev().fold(term, |acc, &(sib, dir)| {
let d = ar.atom(g(dir)).unwrap();
let step = ar.pair(sib, d).unwrap();
ar.pair(step, acc).unwrap()
})
}
fn run_merkle_formula<const N: usize>(
ar: &mut Reduction<N>,
root: Order, leaf_hash: Order,
path: &[(Order, u64)],
) -> Outcome {
let formula = build_merkle_verify_formula(ar).unwrap();
let depth = ar.atom(g(path.len() as u64)).unwrap();
let root_pair = ar.pair(root, formula).unwrap();
let meta = ar.pair(depth, root_pair).unwrap();
let path_data = build_path(ar, path);
let data = ar.pair(leaf_hash, path_data).unwrap();
let obj = ar.pair(meta, data).unwrap();
reduce(ar, obj, formula, 10_000_000, &NullCalls, &mut NoTrace)
}
#[test]
fn merkle_formula_valid_path_returns_one() {
let mut ar = Reduction::<4096>::new();
let (root, hn0, hn1) = two_leaf_tree(&mut ar);
match run_merkle_formula(&mut ar, root, hn0, &[(hn1, 1)]) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap(), g(1)),
o => panic!("{:?}", o),
}
}
#[test]
fn merkle_formula_wrong_leaf_returns_zero() {
let mut ar = Reduction::<4096>::new();
let (root, _hn0, hn1) = two_leaf_tree(&mut ar);
let wrong = ar.atom(g(999)).unwrap();
let wrong_hash = nox_hash_data(&mut ar, wrong);
match run_merkle_formula(&mut ar, root, wrong_hash, &[(hn1, 1)]) {
Outcome::Ok(r, _) => assert_eq!(ar.atom_value(r).unwrap(), g(0)),
o => panic!("{:?}", o),
}
}
#[test]
fn merkle_formula_hash_is_stable() {
let mut ar1 = Reduction::<4096>::new();
let mut ar2 = Reduction::<4096>::new();
let h1 = merkle_verify_formula_hash(&mut ar1).unwrap();
let h2 = merkle_verify_formula_hash(&mut ar2).unwrap();
assert_eq!(h1, h2, "formula hash must be deterministic");
}
#[test]
fn fri_fold_formula_hash_is_stable() {
use crate::encode::particle_id;
let mut ar1 = Reduction::<4096>::new();
let mut ar2 = Reduction::<4096>::new();
let f1 = build_fri_fold_formula(&mut ar1).unwrap();
let h1 = particle_id(&ar1, f1).unwrap();
let f2 = build_fri_fold_formula(&mut ar2).unwrap();
let h2 = particle_id(&ar2, f2).unwrap();
assert_eq!(h1, h2);
}
#[test]
fn ntt_formula_hash_is_stable() {
use crate::encode::particle_id;
let mut ar1 = Reduction::<4096>::new();
let mut ar2 = Reduction::<4096>::new();
let f1 = build_ntt_formula(&mut ar1).unwrap();
let h1 = particle_id(&ar1, f1).unwrap();
let f2 = build_ntt_formula(&mut ar2).unwrap();
let h2 = particle_id(&ar2, f2).unwrap();
assert_eq!(h1, h2);
}
#[test]
fn genesis_formulas_have_distinct_hashes() {
use crate::encode::particle_id;
let mut ar = Reduction::<8192>::new();
let id_pe = build_poly_eval_formula(&mut ar).unwrap();
let h_pe = particle_id(&ar, id_pe).unwrap();
let id_mv = build_merkle_verify_formula(&mut ar).unwrap();
let h_mv = particle_id(&ar, id_mv).unwrap();
let id_ff = build_fri_fold_formula(&mut ar).unwrap();
let h_ff = particle_id(&ar, id_ff).unwrap();
let id_nt = build_ntt_formula(&mut ar).unwrap();
let h_nt = particle_id(&ar, id_nt).unwrap();
let id_cl = build_cyberlink_formula(&mut ar).unwrap();
let h_cl = particle_id(&ar, id_cl).unwrap();
let id_dc = build_decider_formula(&mut ar).unwrap();
let h_dc = particle_id(&ar, id_dc).unwrap();
let hashes = [h_pe, h_mv, h_ff, h_nt, h_cl, h_dc];
for i in 0..hashes.len() {
for j in (i + 1)..hashes.len() {
assert_ne!(hashes[i], hashes[j], "formulas {i} and {j} share a hash");
}
}
}
}