1use pasta_curves::Fp;
2
3use crate::hasher::PoseidonHasher;
4use crate::tree::TREE_DEPTH;
5
6pub const PUNCTURE_K: usize = 2;
11
12fn verify_merkle_path(
14 hasher: &PoseidonHasher,
15 leaf: Fp,
16 mut pos: u32,
17 path: &[Fp; TREE_DEPTH],
18 root: Fp,
19) -> bool {
20 let mut current = leaf;
21 for sibling in path.iter() {
22 let (l, r) = if pos & 1 == 0 {
23 (current, *sibling)
24 } else {
25 (*sibling, current)
26 };
27 current = hasher.hash(l, r);
28 pos >>= 1;
29 }
30 current == root
31}
32
33#[derive(Clone, Debug)]
75pub struct ImtProofData {
76 pub root: Fp,
78 pub nf_bounds: [Fp; PUNCTURE_K + 1],
80 pub leaf_pos: u32,
82 pub path: [Fp; TREE_DEPTH],
84}
85
86impl ImtProofData {
87 pub fn verify(&self, value: Fp) -> bool {
93 let [nf_lo, nf_mid, nf_hi] = self.nf_bounds;
94
95 let offset = value - nf_lo;
99 let span = nf_hi - nf_lo;
100 if offset == Fp::zero() || offset >= span {
101 return false;
102 }
103
104 if value == nf_mid {
106 return false;
107 }
108
109 let hasher = PoseidonHasher::new();
110 let leaf = hasher.hash3(nf_lo, nf_mid, nf_hi);
111 verify_merkle_path(&hasher, leaf, self.leaf_pos, &self.path, self.root)
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use crate::test_helpers::fp;
119 use crate::tree::{precompute_empty_hashes, TREE_DEPTH};
120
121 fn make_punctured_proof(nf_bounds: [Fp; 3]) -> ImtProofData {
122 let hasher = PoseidonHasher::new();
123 let empty = precompute_empty_hashes();
124 let leaf = hasher.hash3(nf_bounds[0], nf_bounds[1], nf_bounds[2]);
125 let mut current = leaf;
126 let mut path = [Fp::zero(); TREE_DEPTH];
127 for i in 0..TREE_DEPTH {
128 path[i] = empty[i];
129 current = hasher.hash(current, empty[i]);
130 }
131 ImtProofData {
132 root: current,
133 nf_bounds,
134 leaf_pos: 0,
135 path,
136 }
137 }
138
139 #[test]
140 fn punctured_verify_accepts_value_in_gap() {
141 let proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
142 assert!(proof.verify(fp(15)), "value in first gap should pass");
143 assert!(proof.verify(fp(25)), "value in second gap should pass");
144 assert!(proof.verify(fp(11)), "value just above nf_lo should pass");
145 assert!(proof.verify(fp(29)), "value just below nf_hi should pass");
146 }
147
148 #[test]
149 fn punctured_verify_rejects_boundary_nullifiers() {
150 let proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
151 assert!(!proof.verify(fp(10)), "nf_lo should be rejected");
152 assert!(!proof.verify(fp(20)), "nf_mid should be rejected");
153 assert!(!proof.verify(fp(30)), "nf_hi should be rejected");
154 }
155
156 #[test]
157 fn punctured_verify_rejects_out_of_range() {
158 let proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
159 assert!(!proof.verify(fp(5)), "value below nf_lo should fail");
160 assert!(!proof.verify(fp(31)), "value above nf_hi should fail");
161 assert!(!proof.verify(fp(0)), "zero should fail");
162 assert!(!proof.verify(Fp::one().neg()), "p-1 should fail");
163 }
164
165 #[test]
166 fn punctured_verify_rejects_tampered_path() {
167 let mut proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
168 proof.path[0] += Fp::one();
169 assert!(!proof.verify(fp(15)), "tampered path should fail");
170 }
171
172 #[test]
173 fn punctured_verify_rejects_tampered_bounds() {
174 let mut proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
175 proof.nf_bounds[1] = fp(21);
176 assert!(!proof.verify(fp(15)), "tampered nf_mid should fail Merkle check");
177 }
178
179 #[test]
180 fn punctured_verify_rejects_wrong_root() {
181 let mut proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
182 proof.root = Fp::zero();
183 assert!(!proof.verify(fp(15)), "wrong root should fail");
184 }
185
186 #[test]
187 fn punctured_verify_rejects_wrong_position() {
188 let mut proof = make_punctured_proof([fp(10), fp(20), fp(30)]);
189 proof.leaf_pos = 1;
190 assert!(!proof.verify(fp(15)), "wrong leaf_pos should fail");
191 }
192}