use super::*;
use nox::call::NullCalls;
use nox::trace::NoTrace;
use nox::{Order, Outcome, Reduction, reduce};
fn atom(a: u64) -> ExecutionNoun {
ExecutionNoun::Atom(a)
}
fn pair(a: ExecutionNoun, b: ExecutionNoun) -> ExecutionNoun {
ExecutionNoun::Pair(Box::new(a), Box::new(b))
}
fn op(t: u64, a: ExecutionNoun, b: ExecutionNoun) -> ExecutionNoun {
pair(atom(t), pair(a, b))
}
fn quote(a: u64) -> ExecutionNoun {
pair(atom(1), atom(a))
}
fn axis(a: u64) -> ExecutionNoun {
pair(atom(0), atom(a))
}
fn arena<const N: usize>(r: &mut Reduction<N>, n: &ExecutionNoun) -> Order {
match n {
ExecutionNoun::Atom(v) => r.atom(F::new(*v)).unwrap(),
ExecutionNoun::Pair(a, b) => {
let a = arena(r, a);
let b = arena(r, b);
r.pair(a, b).unwrap()
}
}
}
fn compare(program: &ExecutionNoun, input: u64) {
let rel = compile_relation(program, &SubjectShape::Atom).unwrap();
let w = rel.witness(&[F::new(input)]).unwrap();
assert!(rel.instance.is_satisfied_by(&w));
let mut ar = Reduction::<8192>::new();
let s = ar.atom(F::new(input)).unwrap();
let f = arena(&mut ar, program);
let (out, remaining) = match reduce(&mut ar, s, f, rel.max_cost, &NullCalls, &mut NoTrace) {
Outcome::Ok(o, b) => (o, b),
o => panic!("{o:?}"),
};
assert_eq!(ar.atom_value(out).unwrap(), w.z[rel.output_indices[0]]);
assert_eq!(rel.max_cost - remaining, w.z[rel.cost_index].as_u64());
for &i in rel
.output_indices
.iter()
.chain(std::iter::once(&rel.cost_index))
{
let mut bad = w.clone();
bad.z[i] += F::ONE;
assert!(!rel.instance.is_satisfied_by(&bad));
}
}
#[test]
fn native_arithmetic_and_static_compose() {
let arithmetic = op(7, op(5, axis(1), quote(9)), op(6, axis(1), quote(2)));
let composed = op(2, arithmetic, pair(atom(1), op(5, axis(1), quote(7))));
for x in [0, 1, 2, 13, 1000] {
compare(&composed, x)
}
}
#[test]
fn both_branch_arms_and_zero_test_are_constrained() {
let p = pair(
atom(4),
pair(
op(9, axis(1), quote(7)),
pair(quote(123), op(7, axis(1), quote(2))),
),
);
for x in [0, 1, 7, 55] {
compare(&p, x)
}
let rel = compile_relation(&p, &SubjectShape::Atom).unwrap();
let w = rel.witness(&[F::new(7)]).unwrap();
let mut bad = w.clone();
bad.z[rel.input_indices[0]] = F::new(8);
assert!(!rel.instance.is_satisfied_by(&bad));
}
#[test]
fn inverse_and_zero_failure() {
let p = pair(atom(8), axis(1));
compare(&p, 7);
let rel = compile_relation(&p, &SubjectShape::Atom).unwrap();
assert!(
!rel.instance
.is_satisfied_by(&rel.witness(&[F::ZERO]).unwrap())
);
}
#[test]
fn structural_aliases_are_shared_and_outputs_left_to_right() {
let shape = SubjectShape::Pair(Box::new(SubjectShape::Atom), Box::new(SubjectShape::Atom));
let rel = compile_relation(&op(3, axis(3), axis(2)), &shape).unwrap();
let w = rel.witness(&[F::new(4), F::new(9)]).unwrap();
assert_eq!(
rel.output_indices
.iter()
.map(|&i| w.z[i].as_u64())
.collect::<Vec<_>>(),
vec![9, 4]
);
assert!(rel.instance.is_satisfied_by(&w));
}
#[test]
fn unsupported_and_noncanonical_programs_fail_closed() {
assert!(compile_relation(&pair(atom(17), atom(0)), &SubjectShape::Atom).is_err());
assert!(compile_relation(&op(2, quote(5), axis(1)), &SubjectShape::Atom).is_err());
assert!(compile_relation("e(u64::MAX), &SubjectShape::Atom).is_err());
}
#[test]
fn relation_is_witness_independent_and_public_constant_must_be_pinned() {
let rel = compile_relation(&op(5, axis(1), quote(7)), &SubjectShape::Atom).unwrap();
assert_eq!(
rel.instance,
compile_relation(&op(5, axis(1), quote(7)), &SubjectShape::Atom)
.unwrap()
.instance
);
assert!(
rel.instance
.is_satisfied_by(&rel.witness(&[F::new(1)]).unwrap())
);
assert!(
rel.instance
.is_satisfied_by(&rel.witness(&[F::new(200)]).unwrap())
);
let zero = CCSWitness {
z: vec![F::ZERO; rel.instance.num_cols],
};
assert!(rel.instance.is_satisfied_by(&zero));
}
#[test]
fn canonical_comparison_boundaries() {
for bound in [0, 1, 7, 0xffff_ffff, 0xffff_ffff_0000_0000] {
let p = op(10, axis(1), quote(bound));
for x in [0, 1, 6, 7, 8, 0xffff_fffe_ffff_ffff, 0xffff_ffff_0000_0000] {
compare(&p, x)
}
}
}
#[test]
fn word_operations_match_nox_and_reject_large_operands() {
for tag in [11, 12, 14] {
for operand in [0, 1, 7, 31, 32, 255, u32::MAX as u64] {
compare(&op(tag, axis(1), quote(operand)), 0x81234567)
}
}
compare(&pair(atom(13), axis(1)), 0x81234567);
let rel = compile_relation(&op(11, axis(1), quote(0)), &SubjectShape::Atom).unwrap();
assert!(
!rel.instance
.is_satisfied_by(&rel.witness(&[F::new(1u64 << 32)]).unwrap())
);
}
#[test]
fn noncanonical_bit_decomposition_cannot_forge_ordering() {
let rel = compile_relation(&op(10, axis(1), quote(1)), &SubjectShape::Atom).unwrap();
let mut z = vec![F::ONE, F::ZERO];
let mut replaced = 0;
for op in &rel.ops {
let eval = |l: &Linear| l.iter().fold(F::ZERO, |s, &(i, c)| s + z[i] * c);
let value = match op {
Op::Bit(_, k) if replaced < 64 => {
replaced += 1;
F::new((0xffff_ffff_0000_0001u64 >> k) & 1)
}
Op::Bit(a, k) => F::new((eval(a).as_u64() >> k) & 1),
Op::Secret(_) | Op::Look { .. } => unreachable!(),
Op::Linear(a) => eval(a),
Op::Product(a, b) => eval(a) * eval(b),
Op::Pow7(a) => {
let x = eval(a);
let x2 = x * x;
x2 * x2 * x2 * x
}
Op::Inverse(a) => {
let v = eval(a);
if v == F::ZERO { v } else { v.inv() }
}
};
z.push(value);
}
z.resize(rel.instance.num_cols, F::ZERO);
assert!(!rel.instance.is_satisfied_by(&CCSWitness { z }));
}
#[test]
fn unselected_invalid_inverse_is_allowed_but_selected_error_fails() {
let p = pair(
atom(4),
pair(quote(0), pair(quote(7), pair(atom(8), quote(0)))),
);
let rel = compile_relation(&p, &SubjectShape::Atom).unwrap();
let witness = rel.witness(&[F::ZERO]).unwrap();
assert!(rel.instance.is_satisfied_by(&witness));
assert_eq!(witness.z[rel.output_indices[0]], F::new(7));
compare(&p, 0);
}
#[test]
fn symbolic_cons_expansion_is_bounded() {
let mut p = axis(1);
for _ in 0..20 {
p = op(2, p, pair(atom(1), op(3, axis(1), axis(1))));
}
assert!(matches!(
compile_relation(&p, &SubjectShape::Atom),
Err(RelationError::Limit)
));
}
#[test]
fn compiled_trident_import_loop_branch_matches_native() {
let text = "[2 [[3 [[0 2] [0 1]]] [1 [2 [[2 [[3 [[1 0] [0 1]]] [1 [2 [[3 [[0 2] [3 [[2 [[3 [[0 6] [1 0]]] [1 [5 [[0 2] [1 3]]]]]] [0 7]]]]] [1 [3 [[0 6] [3 [[0 14] [0 15]]]]]]]]]]] [1 [2 [[2 [[3 [[1 1] [0 1]]] [1 [2 [[3 [[0 2] [3 [[2 [[3 [[0 6] [1 0]]] [1 [5 [[0 2] [1 3]]]]]] [0 7]]]]] [1 [3 [[0 6] [3 [[0 14] [0 15]]]]]]]]]]] [1 [2 [[2 [[3 [[1 2] [0 1]]] [1 [2 [[3 [[0 2] [3 [[2 [[3 [[0 6] [1 0]]] [1 [5 [[0 2] [1 3]]]]]] [0 7]]]]] [1 [3 [[0 6] [3 [[0 14] [0 15]]]]]]]]]]] [1 [2 [[4 [[9 [[0 2] [1 13]]] [[2 [[3 [[5 [[0 2] [1 100]]] [0 3]]] [1 [0 1]]]] [2 [[3 [[7 [[0 2] [1 2]]] [0 3]]] [1 [0 1]]]]]]] [1 [0 2]]]]]]]]]]]]]]]]";
fn parse(bytes: &[u8], p: &mut usize) -> ExecutionNoun {
while *p < bytes.len() && bytes[*p].is_ascii_whitespace() {
*p += 1;
}
if bytes[*p] == b'[' {
*p += 1;
let a = parse(bytes, p);
let b = parse(bytes, p);
assert_eq!(bytes[*p], b']');
*p += 1;
pair(a, b)
} else {
let start = *p;
while *p < bytes.len() && bytes[*p].is_ascii_digit() {
*p += 1;
}
atom(
std::str::from_utf8(&bytes[start..*p])
.unwrap()
.parse()
.unwrap(),
)
}
}
let program = parse(text.as_bytes(), &mut 0);
let shape = SubjectShape::Pair(Box::new(SubjectShape::Atom), Box::new(SubjectShape::Atom));
let relation = compile_relation(&program, &shape).unwrap();
for (input, expected) in [(4, 113), (0, 18), (10, 38)] {
let w = relation.witness(&[F::new(input), F::ZERO]).unwrap();
assert!(relation.instance.is_satisfied_by(&w));
assert_eq!(w.z[relation.output_indices[0]], F::new(expected));
let mut ar = Reduction::<8192>::new();
let subject = arena(&mut ar, &pair(atom(input), atom(0)));
let formula = arena(&mut ar, &program);
match reduce(
&mut ar,
subject,
formula,
relation.max_cost,
&NullCalls,
&mut NoTrace,
) {
Outcome::Ok(out, remaining) => {
assert_eq!(ar.atom_value(out).unwrap(), F::new(expected));
assert_eq!(
relation.max_cost - remaining,
w.z[relation.cost_index].as_u64()
);
}
outcome => panic!("{outcome:?}"),
}
}
}
#[test]
fn pair_equality_binds_every_digest_limb_and_matches_native() {
let made = op(3, axis(1), quote(9));
let other = op(3, quote(7), quote(9));
for x in [7, 8] {
compare(&op(9, made.clone(), other.clone()), x);
}
compare(&op(9, made, quote(7)), 7);
}