use pasta_curves::Fp;
use crate::hasher::PoseidonHasher;
use crate::tree::TREE_DEPTH;
pub const PUNCTURE_K: usize = 2;
fn verify_merkle_path(
hasher: &PoseidonHasher,
leaf: Fp,
mut pos: u32,
path: &[Fp; TREE_DEPTH],
root: Fp,
) -> bool {
let mut current = leaf;
for sibling in path.iter() {
let (l, r) = if pos & 1 == 0 {
(current, *sibling)
} else {
(*sibling, current)
};
current = hasher.hash(l, r);
pos >>= 1;
}
current == root
}
#[derive(Clone, Debug)]
pub struct ImtProofData {
pub root: Fp,
pub nf_bounds: [Fp; PUNCTURE_K + 1],
pub leaf_pos: u32,
pub path: [Fp; TREE_DEPTH],
}
impl ImtProofData {
pub fn verify(&self, value: Fp) -> bool {
let [nf_lo, nf_mid, nf_hi] = self.nf_bounds;
let offset = value - nf_lo;
let span = nf_hi - nf_lo;
if offset == Fp::zero() || offset >= span {
return false;
}
if value == nf_mid {
return false;
}
let hasher = PoseidonHasher::new();
let leaf = hasher.hash3(nf_lo, nf_mid, nf_hi);
verify_merkle_path(&hasher, leaf, self.leaf_pos, &self.path, self.root)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::fp;
use crate::tree::{precompute_empty_hashes, TREE_DEPTH};
fn make_punctured_proof(nf_bounds: [Fp; 3]) -> ImtProofData {
let hasher = PoseidonHasher::new();
let empty = precompute_empty_hashes();
let leaf = hasher.hash3(nf_bounds[0], nf_bounds[1], nf_bounds[2]);
let mut current = leaf;
let mut path = [Fp::zero(); TREE_DEPTH];
for i in 0..TREE_DEPTH {
path[i] = empty[i];
current = hasher.hash(current, empty[i]);
}
ImtProofData {
root: current,
nf_bounds,
leaf_pos: 0,
path,
}
}
#[test]
fn punctured_verify_accepts_value_in_gap() {
let proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
assert!(proof.verify(fp(15)), "value in first gap should pass");
assert!(proof.verify(fp(25)), "value in second gap should pass");
assert!(proof.verify(fp(11)), "value just above nf_lo should pass");
assert!(proof.verify(fp(29)), "value just below nf_hi should pass");
}
#[test]
fn punctured_verify_rejects_boundary_nullifiers() {
let proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
assert!(!proof.verify(fp(10)), "nf_lo should be rejected");
assert!(!proof.verify(fp(20)), "nf_mid should be rejected");
assert!(!proof.verify(fp(30)), "nf_hi should be rejected");
}
#[test]
fn punctured_verify_rejects_out_of_range() {
let proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
assert!(!proof.verify(fp(5)), "value below nf_lo should fail");
assert!(!proof.verify(fp(31)), "value above nf_hi should fail");
assert!(!proof.verify(fp(0)), "zero should fail");
assert!(!proof.verify(Fp::one().neg()), "p-1 should fail");
}
#[test]
fn punctured_verify_rejects_tampered_path() {
let mut proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
proof.path[0] += Fp::one();
assert!(!proof.verify(fp(15)), "tampered path should fail");
}
#[test]
fn punctured_verify_rejects_tampered_bounds() {
let mut proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
proof.nf_bounds[1] = fp(21);
assert!(!proof.verify(fp(15)), "tampered nf_mid should fail Merkle check");
}
#[test]
fn punctured_verify_rejects_wrong_root() {
let mut proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
proof.root = Fp::zero();
assert!(!proof.verify(fp(15)), "wrong root should fail");
}
#[test]
fn punctured_verify_rejects_wrong_position() {
let mut proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
proof.leaf_pos = 1;
assert!(!proof.verify(fp(15)), "wrong leaf_pos should fail");
}
}