mod common;
use common::{ADD, HASH, P, hash_chain, native, parse, synthetic};
use nebu::Goldilocks as F;
use zheng::envelope::{AnySuccinct, Envelope, SuccinctStatement};
use zheng::execution::succinct::{
self, MultilinearPcs, SuccinctPcs, SuccinctProof, TensorRs, TensorRsParams, Whir, WhirParams,
};
use zheng::execution::{ExecutionNoun, ExecutionStatement, certify_execution};
fn envelope_of<Pc: SuccinctPcs>(s: &ExecutionStatement, p: &SuccinctProof<Pc>) -> Envelope
where
SuccinctProof<Pc>: Into<AnySuccinct>,
{
Envelope::Succinct {
statement: SuccinctStatement::Execution(s.clone()),
proof: p.clone().into(),
}
}
fn accepted(e: &Envelope) -> bool {
Envelope::from_bytes(&e.to_bytes()).is_ok_and(|d| d.verify(None).is_ok())
}
fn fixtures() -> Vec<(&'static str, ExecutionNoun, Vec<u64>)> {
vec![
("hash", parse(HASH), vec![7]),
("hash", parse(HASH), vec![P - 1]),
("add", parse(ADD), vec![7, 5]),
("add", parse(ADD), vec![0, 0]),
("chain11", hash_chain(11), vec![7]),
]
}
fn complete<Pc: SuccinctPcs>(params: Pc::Params)
where
SuccinctProof<Pc>: Into<AnySuccinct>,
{
for (name, program, input) in fixtures() {
let (statement, proof) = succinct::prove::<Pc>(¶ms, &program, &input, 1_000_000)
.unwrap_or_else(|e| panic!("{name}: {e}"));
let (_, cert) = certify_execution(&program, &input, 1_000_000).unwrap();
assert_eq!(
statement,
certify_execution(&program, &input, 1_000_000).unwrap().0
);
succinct::verify(&statement, &proof).unwrap_or_else(|e| panic!("{name}: {e}"));
let e = envelope_of(&statement, &proof);
assert!(accepted(&e), "{name} envelope");
assert_eq!(e.to_bytes()[10], 1, "profile byte");
let decoded = Envelope::from_bytes(&e.to_bytes()).unwrap();
assert_eq!(decoded, e, "{name} round trip");
println!(
"{} {name} {input:?}: envelope {} B (public v3 certificate {} free values)",
Pc::NAME,
e.to_bytes().len(),
cert.free.len()
);
}
}
#[test]
fn whir_proves_every_fixture() {
complete::<Whir>(WhirParams::default());
}
#[test]
fn tensor_proves_every_fixture() {
complete::<TensorRs>(TensorRsParams::default());
}
#[test]
fn hash_chain_agrees_with_native_nox() {
for n in [1, 2, 11] {
let program = hash_chain(n);
let (statement, _) = certify_execution(&program, &[7], 1_000_000).unwrap();
let (output, cycles) = native(&program, &[7], 1_000_000);
assert_eq!(statement.public_output, output, "chain {n}");
assert_eq!(statement.cycles, cycles, "chain {n}");
}
}
#[test]
fn synthetic_relations_prove_under_both_schemes() {
let s = synthetic(10, 3);
let pw = succinct::prove_relation::<Whir>(
&WhirParams::default(),
&s.instance,
&s.z,
&s.pins,
&s.statement,
)
.unwrap();
succinct::verify_relation(&s.instance, &s.pins, &s.statement, &pw).unwrap();
let pt = succinct::prove_relation::<TensorRs>(
&TensorRsParams::default(),
&s.instance,
&s.z,
&s.pins,
&s.statement,
)
.unwrap();
succinct::verify_relation(&s.instance, &s.pins, &s.statement, &pt).unwrap();
let mut pins = s.pins.clone();
pins[2].1 += F::ONE;
assert!(succinct::verify_relation(&s.instance, &pins, &s.statement, &pw).is_err());
let mut pins = s.pins.clone();
pins[0].1 = F::ZERO;
assert!(succinct::verify_relation(&s.instance, &pins, &s.statement, &pw).is_err());
}
fn honest(program: &str, input: &[u64]) -> (ExecutionStatement, SuccinctProof<Whir>) {
succinct::prove::<Whir>(&WhirParams::default(), &parse(program), input, 1_000_000).unwrap()
}
#[test]
fn forged_statements_are_rejected() {
for (program, input) in [(HASH, vec![7]), (ADD, vec![7, 5])] {
let (statement, proof) = honest(program, &input);
assert!(accepted(&envelope_of(&statement, &proof)));
let forge = |edit: &dyn Fn(&mut ExecutionStatement)| {
let mut s = statement.clone();
edit(&mut s);
!accepted(&envelope_of(&s, &proof))
};
assert!(
forge(&|s| s.public_input[0] = (s.public_input[0] + 1) % P),
"input"
);
assert!(
forge(&|s| s.public_output[0] = (s.public_output[0] + 1) % P),
"output"
);
assert!(forge(&|s| s.public_output.push(0)), "extra output");
assert!(forge(&|s| s.cycles += 1), "cycles");
assert!(forge(&|s| s.cycles -= 1), "fewer cycles");
assert!(forge(&|s| s.budget += 1), "budget");
let program_edit = |s: &mut ExecutionStatement| {
let i = s
.program
.iter()
.rposition(|t| matches!(t, zheng::execution::NounToken::Atom(v) if *v == 0))
.unwrap();
s.program[i] = zheng::execution::NounToken::Atom(1);
};
assert!(forge(&program_edit), "program");
}
}
#[test]
fn a_satisfying_witness_of_another_statement_is_rejected() {
let runs = [
honest(ADD, &[7, 5]),
honest(ADD, &[1, 2]),
honest(ADD, &[0, 0]),
honest(HASH, &[7]),
honest(HASH, &[8]),
];
for (i, (statement, _)) in runs.iter().enumerate() {
for (j, (_, proof)) in runs.iter().enumerate() {
let ok = succinct::verify(statement, proof).is_ok();
assert_eq!(ok, i == j, "statement {i} with proof {j}");
}
}
}
#[test]
fn tampered_messages_are_rejected() {
let (statement, proof) = honest(HASH, &[7]);
let one = nebu::Fp3::new(F::ZERO, F::ONE, F::ZERO);
let reject = |p: SuccinctProof<Whir>| succinct::verify(&statement, &p).is_err();
let mut p = proof.clone();
p.witness_eval += one;
assert!(reject(p), "witness evaluation");
for i in 0..proof.matrix_evals.len() {
let mut p = proof.clone();
p.matrix_evals[i] += one;
assert!(reject(p), "matrix {i}");
}
let mut p = proof.clone();
p.outer.rounds[0][0] += one;
assert!(reject(p), "outer round");
let mut p = proof.clone();
let last = p.inner.rounds.len() - 1;
p.inner.rounds[last][1] += one;
assert!(reject(p), "inner round");
let mut p = proof.clone();
p.root = succinct::prove::<Whir>(&WhirParams::default(), &parse(HASH), &[8], 1_000_000)
.unwrap()
.1
.root;
assert!(reject(p), "root");
let mut p = proof.clone();
p.params.pow_bits = 20;
assert!(reject(p), "params");
}
#[test]
fn parameters_below_policy_are_refused_by_prover_and_verifier() {
let weak = WhirParams {
security_target: 96,
..WhirParams::default()
};
assert!(succinct::prove::<Whir>(&weak, &parse(HASH), &[7], 1_000_000).is_err());
let few = WhirParams {
pow_bits: 0,
log_inv_rate: 1,
..WhirParams::default()
};
let bits = <Whir as MultilinearPcs>::security_bits(&few, 11);
let r = succinct::prove::<Whir>(&few, &parse(HASH), &[7], 1_000_000);
assert_eq!(r.is_ok(), bits >= 128.0, "{bits}");
let (statement, proof) = honest(HASH, &[7]);
let mut p = proof.clone();
p.params.security_target = 100;
assert!(succinct::verify(&statement, &p).is_err());
let bytes = envelope_of(&statement, &p).to_bytes();
assert!(Envelope::from_bytes(&bytes).is_ok_and(|e| e.verify(None).is_err()));
}
#[test]
fn proofs_are_deterministic() {
for (program, input) in [(HASH, vec![7]), (ADD, vec![7, 5])] {
let a = envelope_of(&honest(program, &input).0, &honest(program, &input).1).to_bytes();
let b = envelope_of(&honest(program, &input).0, &honest(program, &input).1).to_bytes();
assert_eq!(a, b);
let pt = TensorRsParams::default();
let t1 = succinct::prove::<TensorRs>(&pt, &parse(program), &input, 1_000_000).unwrap();
let t2 = succinct::prove::<TensorRs>(&pt, &parse(program), &input, 1_000_000).unwrap();
assert_eq!(
envelope_of(&t1.0, &t1.1).to_bytes(),
envelope_of(&t2.0, &t2.1).to_bytes()
);
}
}