mod common;
use common::{node_merkle_program, node_merkle_root};
use nebu::Goldilocks as F;
use zheng::envelope::{Envelope, SuccinctStatement};
use zheng::execution::relation::{SubjectShape, compile_relation};
use zheng::execution::{ExecutionNoun as N, node, succinct};
const BUDGET: u64 = 1 << 40;
fn pr(a: N, b: N) -> N {
N::Pair(Box::new(a), Box::new(b))
}
fn at(v: u64) -> N {
N::Atom(v)
}
fn ax(n: u64) -> N {
pr(at(0), at(n))
}
fn op(t: u64, a: N, b: N) -> N {
pr(at(t), pr(a, b))
}
fn q(x: N) -> N {
pr(at(1), x)
}
fn nox_run(program: &N, input: &[u64]) -> Result<(N, u64), String> {
fn into<const S: usize>(r: &mut nox::Reduction<S>, n: &N) -> nox::Order {
match n {
N::Atom(v) => r.atom(F::new(*v)).expect("arena"),
N::Pair(a, b) => {
let (a, b) = (into(r, a), into(r, b));
r.pair(a, b).expect("arena")
}
}
}
fn out<const S: usize>(r: &nox::Reduction<S>, o: nox::Order) -> N {
match r.get(o).expect("noun").inner {
nox::data::Data::Pair { left, right } => pr(out(r, left), out(r, right)),
_ => N::Atom(r.atom_value(o).expect("atom").as_u64()),
}
}
let subject = input.iter().rev().fold(at(0), |s, &v| pr(at(v), s));
let program = program.clone();
std::thread::Builder::new()
.stack_size(1 << 30)
.spawn(move || {
let mut r = Box::new(nox::Reduction::<{ 1 << 21 }>::new());
let s = into(&mut r, &subject);
let f = into(&mut r, &program);
match nox::reduce(
&mut r,
s,
f,
BUDGET,
&nox::call::NullCalls,
&mut nox::trace::NoTrace,
) {
nox::Outcome::Ok(o, left) => Ok((out(&r, o), BUDGET - left)),
other => Err(format!("{other:?}")),
}
})
.expect("thread")
.join()
.expect("nox")
}
fn input(k: usize) -> N {
ax((1u64 << (k + 2)) - 2)
}
fn witness_path_program(levels: usize, root_flags: u64) -> N {
let digest = |k: usize| {
op(
3,
op(3, input(k), input(k + 1)),
op(3, input(k + 2), input(k + 3)),
)
};
let mut path = q(at(0));
for i in (0..levels).rev() {
let base = 4 + 5 * i;
path = op(3, op(3, digest(base), input(base + 4)), path);
}
let level = |flags: u64| {
let children = pr(
at(4),
pr(ax(25), pr(op(3, ax(24), ax(2)), op(3, ax(2), ax(24)))),
);
let next = op(2, op(3, children, q(at(flags))), ax(14));
op(3, next, op(3, ax(13), ax(7)))
};
let lib = pr(
node::formula().clone(),
pr(level(node::FLAGS_NODE), level(root_flags)),
);
let start = op(3, digest(0), op(3, path, q(lib)));
let step = |axis: u64| op(2, ax(1), ax(axis));
let mut steps = step(31);
for _ in 1..levels {
steps = op(2, step(30), q(steps));
}
let folded = op(2, start, q(steps));
op(2, folded, q(ax(2)))
}
fn splitmix(seed: &mut u64) -> u64 {
*seed = seed.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = *seed;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
}
struct Path {
leaf: [u64; 4],
levels: Vec<([u64; 4], u64)>,
}
impl Path {
fn random(levels: usize, seed: &mut u64) -> Self {
let leaf = core::array::from_fn(|_| splitmix(seed) % nebu::field::P);
let levels = (0..levels)
.map(|_| {
let sib = core::array::from_fn(|_| splitmix(seed) % nebu::field::P);
(sib, splitmix(seed) & 1)
})
.collect();
Self { leaf, levels }
}
fn inputs(&self) -> Vec<u64> {
let mut v = self.leaf.to_vec();
for (sib, dir) in &self.levels {
v.extend_from_slice(sib);
v.push(*dir);
}
v
}
fn root(&self) -> [u64; 4] {
let n = self.levels.len();
let mut cur = self.leaf;
for (i, (sib, dir)) in self.levels.iter().enumerate() {
let flags = if i + 1 == n {
node::FLAGS_ROOT
} else {
node::FLAGS_NODE
};
cur = if *dir == 0 {
node::native(*sib, cur, flags)
} else {
node::native(cur, *sib, flags)
};
}
cur
}
}
fn digest_of(n: &N) -> [u64; 4] {
let N::Pair(ab, cd) = n else { panic!("digest") };
let (N::Pair(a, b), N::Pair(c, d)) = (&**ab, &**cd) else {
panic!("digest")
};
let v = |x: &N| match x {
N::Atom(v) => *v,
_ => panic!("limb"),
};
[v(a), v(b), v(c), v(d)]
}
fn compiled(program: &N, inputs: &[u64]) -> ([u64; 4], u64) {
let shape = inputs.iter().fold(SubjectShape::Atom, |s, _| {
SubjectShape::Pair(Box::new(SubjectShape::Atom), Box::new(s))
});
let relation = compile_relation(program, &shape).expect("compile");
let mut z_in: Vec<F> = inputs.iter().map(|&v| F::new(v)).collect();
z_in.push(F::ZERO);
let w = relation.witness(&z_in).expect("witness");
assert!(relation.instance.is_satisfied_by(&w));
let out: Vec<u64> = relation
.output_indices
.iter()
.map(|&i| w.z[i].as_u64())
.collect();
(
out.try_into().expect("four limbs"),
w.z[relation.cost_index].as_u64(),
)
}
#[test]
fn compiled_paths_equal_native_nox_and_hemera_on_random_paths() {
let mut seed = 0x5eed;
for levels in [1, 2, 3, 5] {
for _ in 0..3 {
let path = Path::random(levels, &mut seed);
let program = witness_path_program(levels, node::FLAGS_ROOT);
let inputs = path.inputs();
let (native_out, native_cycles) = nox_run(&program, &inputs).expect("native");
let (out, cycles) = compiled(&program, &inputs);
assert_eq!(
digest_of(&native_out),
path.root(),
"native nox = hemera, depth {levels}"
);
assert_eq!(out, path.root(), "relation = hemera, depth {levels}");
assert_eq!(
cycles, native_cycles,
"relation cycles = nox cycles, depth {levels}"
);
}
}
}
#[test]
fn fixture_root_matches_native_nox_and_the_check_passes() {
for levels in [1, 4, 12] {
let (out, cycles) = nox_run(&node_merkle_program(levels, None), &[5]).expect("native");
assert_eq!(
digest_of(&out),
node_merkle_root(levels, 5),
"depth {levels}"
);
let root = zheng::execution::node::digest_noun(node_merkle_root(levels, 5));
let check = node_merkle_program(levels, Some(&root));
let (ok, check_cycles) = nox_run(&check, &[5]).expect("native");
assert_eq!(ok, N::Atom(0), "path verifies natively, depth {levels}");
let (statement, proof) =
succinct::prove_with(&check, &[5], BUDGET, succinct::device_params).expect("prove");
assert_eq!(statement.public_output, vec![0], "depth {levels}");
assert_eq!(statement.cycles, check_cycles, "cycles, depth {levels}");
let e = Envelope::Succinct {
statement: SuccinctStatement::Execution(statement),
proof,
};
Envelope::from_bytes(&e.to_bytes())
.unwrap()
.verify(None)
.expect("verify");
assert!(cycles > 0);
}
}
#[test]
fn tampered_sibling_flipped_direction_and_wrong_flag_do_not_verify() {
let mut seed = 0xbad;
let levels = 4;
let path = Path::random(levels, &mut seed);
let root = path.root();
let program = witness_path_program(levels, node::FLAGS_ROOT);
let honest = path.inputs();
let mut forged = vec![];
for level in 0..levels {
for limb in 0..4 {
let mut v = honest.clone();
v[4 + 5 * level + limb] = (v[4 + 5 * level + limb] + 1) % nebu::field::P;
forged.push(("sibling", v));
}
let mut v = honest.clone();
v[4 + 5 * level + 4] ^= 1;
forged.push(("direction", v));
}
for (what, inputs) in &forged {
let (out, _) = compiled(&program, inputs);
assert_ne!(out, root, "a tampered {what} reaches the root");
let (native_out, _) = nox_run(&program, inputs).unwrap();
assert_eq!(
digest_of(&native_out),
out,
"relation follows nox on the tampered {what}"
);
}
let interior = witness_path_program(levels, node::FLAGS_NODE);
assert_ne!(compiled(&interior, &honest).0, root);
}
#[test]
fn a_proof_cannot_claim_the_root_for_a_tampered_path() {
let mut seed = 0xface;
let levels = 3;
let path = Path::random(levels, &mut seed);
let root = path.root();
let program = witness_path_program(levels, node::FLAGS_ROOT);
let mut inputs = path.inputs();
inputs[4] = (inputs[4] + 1) % nebu::field::P;
let (mut statement, proof) =
succinct::prove_with(&program, &inputs, BUDGET, succinct::device_params).expect("prove");
assert_ne!(statement.public_output, root.to_vec());
let honest = Envelope::Succinct {
statement: SuccinctStatement::Execution(statement.clone()),
proof: proof.clone(),
};
honest
.verify(None)
.expect("the tampered path's own root verifies");
statement.public_output = root.to_vec();
let forged = Envelope::Succinct {
statement: SuccinctStatement::Execution(statement),
proof,
};
assert!(
forged.verify(None).is_err(),
"claimed the honest root for a tampered path"
);
}
#[test]
fn a_node_subject_of_another_shape_fails_closed_like_nox() {
let bad = op(
2,
q(pr(pr(at(1), node::digest_noun([1, 2, 3, 4])), at(2))),
q(node::formula().clone()),
);
assert!(nox_run(&bad, &[0]).is_err());
assert!(
compile_relation(
&bad,
&SubjectShape::Pair(Box::new(SubjectShape::Atom), Box::new(SubjectShape::Atom))
)
.is_err()
);
let bad = op(
2,
q(pr(
pr(
node::digest_noun([1, 2, 3, 4]),
node::digest_noun([5, 6, 7, 8]),
),
pr(at(2), at(2)),
)),
q(node::formula().clone()),
);
assert!(nox_run(&bad, &[0]).is_err());
assert!(
compile_relation(
&bad,
&SubjectShape::Pair(Box::new(SubjectShape::Atom), Box::new(SubjectShape::Atom))
)
.is_err()
);
}
#[test]
fn one_level_costs_one_permutation() {
let size = levels: usize {
let r = compile_relation(
&node_merkle_program(levels, None),
&SubjectShape::Pair(Box::new(SubjectShape::Atom), Box::new(SubjectShape::Atom)),
)
.unwrap();
r.instance.matrices[0]
.entries
.iter()
.filter(e !e.is_empty())
.count()
+ r.instance.matrices[3]
.entries
.iter()
.filter(e !e.is_empty())
.count()
};
let per_level = (size(9) - size(1)) / 8;
println!("rows per level: {per_level}");
assert!(per_level < 260, "{per_level} rows per level");
}
#[test]
fn nox_depth_limit_does_not_bind_inside_the_node() {
const WRAP: usize = 640;
let l = [1, 2, 3, 4];
let r = [5, 6, 7, 8];
let call = op(
2,
q(pr(
pr(node::digest_noun(l), node::digest_noun(r)),
at(node::FLAGS_ROOT),
)),
q(node::formula().clone()),
);
let mut program = call;
for _ in 0..WRAP {
program = op(3, program, q(at(0)));
}
let (out, _) = nox_run(&program, &[0]).expect("nox under WRAP levels");
let mut v = &out;
for _ in 0..WRAP {
let N::Pair(a, _) = v else { panic!("wrapper") };
v = a;
}
assert_eq!(digest_of(v), node::native(l, r, node::FLAGS_ROOT));
}
#[test]
fn zheng_machine_runs_the_node_formula_to_the_same_root() {
let levels = 2;
let program = node_merkle_program(levels, None);
let run = zheng::machine::execute(&program, &[5], BUDGET).expect("machine");
let out = zheng::machine::statement::parse(&run.statement.output).expect("output");
assert_eq!(digest_of(&out), node_merkle_root(levels, 5));
let (_, cycles) = nox_run(&program, &[5]).unwrap();
assert_eq!(run.statement.cycles, cycles);
}