1use std::collections::btree_map::BTreeMap;
2
3#[cfg(not(feature = "std"))]
4use std::prelude::*;
5
6use crate::compress::{decompress, is_compressed};
7use crate::ics23;
8use crate::verify::{verify_existence, verify_non_existence, CommitmentRoot};
9
10#[allow(clippy::ptr_arg)]
12pub fn verify_membership(
13 proof: &ics23::CommitmentProof,
14 spec: &ics23::ProofSpec,
15 root: &CommitmentRoot,
16 key: &[u8],
17 value: &[u8],
18) -> bool {
19 let mut proof = proof;
21 let my_proof;
22 if is_compressed(proof) {
23 if let Ok(p) = decompress(proof) {
24 my_proof = p;
25 proof = &my_proof;
26 } else {
27 return false;
28 }
29 }
30
31 if let Some(ex) = get_exist_proof(proof, key) {
33 let valid = verify_existence(ex, spec, root, key, value);
34 valid.is_ok()
35 } else {
36 false
37 }
38}
39
40#[allow(clippy::ptr_arg)]
42pub fn verify_non_membership(
43 proof: &ics23::CommitmentProof,
44 spec: &ics23::ProofSpec,
45 root: &CommitmentRoot,
46 key: &[u8],
47) -> bool {
48 let mut proof = proof;
50 let my_proof;
51 if is_compressed(proof) {
52 if let Ok(p) = decompress(proof) {
53 my_proof = p;
54 proof = &my_proof;
55 } else {
56 return false;
57 }
58 }
59
60 if let Some(non) = get_nonexist_proof(proof, key) {
61 let valid = verify_non_existence(non, spec, root, key);
62 valid.is_ok()
63 } else {
64 false
65 }
66}
67
68#[allow(clippy::ptr_arg)]
69pub fn verify_batch_membership(
70 proof: &ics23::CommitmentProof,
71 spec: &ics23::ProofSpec,
72 root: &CommitmentRoot,
73 items: BTreeMap<&[u8], &[u8]>,
74) -> bool {
75 let mut proof = proof;
77 let my_proof;
78 if is_compressed(proof) {
79 if let Ok(p) = decompress(proof) {
80 my_proof = p;
81 proof = &my_proof;
82 } else {
83 return false;
84 }
85 }
86
87 items
88 .iter()
89 .all(|(key, value)| verify_membership(proof, spec, root, key, value))
90}
91
92#[allow(clippy::ptr_arg)]
93pub fn verify_batch_non_membership(
94 proof: &ics23::CommitmentProof,
95 spec: &ics23::ProofSpec,
96 root: &CommitmentRoot,
97 keys: &[&[u8]],
98) -> bool {
99 let mut proof = proof;
101 let my_proof;
102 if is_compressed(proof) {
103 if let Ok(p) = decompress(proof) {
104 my_proof = p;
105 proof = &my_proof;
106 } else {
107 return false;
108 }
109 }
110
111 keys.iter()
112 .all(|key| verify_non_membership(proof, spec, root, key))
113}
114
115fn get_exist_proof<'a>(
116 proof: &'a ics23::CommitmentProof,
117 key: &[u8],
118) -> Option<&'a ics23::ExistenceProof> {
119 match &proof.proof {
120 Some(ics23::commitment_proof::Proof::Exist(ex)) => Some(ex),
121 Some(ics23::commitment_proof::Proof::Batch(batch)) => {
122 for entry in &batch.entries {
123 if let Some(ics23::batch_entry::Proof::Exist(ex)) = &entry.proof {
124 if ex.key == key {
125 return Some(ex);
126 }
127 }
128 }
129 None
130 }
131 _ => None,
132 }
133}
134
135fn get_nonexist_proof<'a>(
136 proof: &'a ics23::CommitmentProof,
137 key: &[u8],
138) -> Option<&'a ics23::NonExistenceProof> {
139 match &proof.proof {
140 Some(ics23::commitment_proof::Proof::Nonexist(non)) => Some(non),
141 Some(ics23::commitment_proof::Proof::Batch(batch)) => {
142 for entry in &batch.entries {
143 if let Some(ics23::batch_entry::Proof::Nonexist(non)) = &entry.proof {
144 if non.left.iter().all(|x| x.key.as_slice() < key)
146 && non.right.iter().all(|x| x.key.as_slice() > key)
147 {
148 return Some(non);
149 }
150 }
151 }
152 None
153 }
154 _ => None,
155 }
156}
157
158#[warn(clippy::ptr_arg)]
159pub fn iavl_spec() -> ics23::ProofSpec {
160 let leaf = ics23::LeafOp {
161 hash: ics23::HashOp::Sha256.into(),
162 prehash_key: 0,
163 prehash_value: ics23::HashOp::Sha256.into(),
164 length: ics23::LengthOp::VarProto.into(),
165 prefix: vec![0_u8],
166 };
167 let inner = ics23::InnerSpec {
168 child_order: vec![0, 1],
169 min_prefix_length: 4,
170 max_prefix_length: 12,
171 child_size: 33,
172 empty_child: vec![],
173 hash: ics23::HashOp::Sha256.into(),
174 };
175 ics23::ProofSpec {
176 leaf_spec: Some(leaf),
177 inner_spec: Some(inner),
178 min_depth: 0,
179 max_depth: 0,
180 }
181}
182
183pub fn tendermint_spec() -> ics23::ProofSpec {
184 let leaf = ics23::LeafOp {
185 hash: ics23::HashOp::Sha256.into(),
186 prehash_key: 0,
187 prehash_value: ics23::HashOp::Sha256.into(),
188 length: ics23::LengthOp::VarProto.into(),
189 prefix: vec![0_u8],
190 };
191 let inner = ics23::InnerSpec {
192 child_order: vec![0, 1],
193 min_prefix_length: 1,
194 max_prefix_length: 1,
195 child_size: 32,
196 empty_child: vec![],
197 hash: ics23::HashOp::Sha256.into(),
198 };
199 ics23::ProofSpec {
200 leaf_spec: Some(leaf),
201 inner_spec: Some(inner),
202 min_depth: 0,
203 max_depth: 0,
204 }
205}
206
207#[cfg(feature = "std")]
208#[cfg(test)]
209mod tests {
210 extern crate std as _std;
211 use super::*;
212
213 #[cfg(feature = "std")]
214 use _std::fs::File;
215 #[cfg(feature = "std")]
216 use _std::io::prelude::*;
217 use alloc::string::String;
218 use anyhow::{bail, ensure};
219 use prost::Message;
220 use serde::Deserialize;
221 use std::vec::Vec;
222
223 use crate::compress::compress;
224 use crate::helpers::Result;
225
226 #[derive(Deserialize, Debug)]
227 struct TestVector {
228 pub root: String,
229 pub proof: String,
230 pub key: String,
231 pub value: String,
232 }
233
234 struct RefData {
235 pub root: Vec<u8>,
236 pub key: Vec<u8>,
237 pub value: Option<Vec<u8>>,
238 }
239
240 #[cfg(feature = "std")]
241 fn load_file(filename: &str) -> Result<(ics23::CommitmentProof, RefData)> {
242 let mut file = File::open(filename)?;
243 let mut contents = String::new();
244 file.read_to_string(&mut contents)?;
245
246 let data: TestVector = serde_json::from_str(&contents)?;
247 let proto_bin = hex::decode(&data.proof)?;
248 let mut parsed = ics23::CommitmentProof { proof: None };
249 parsed.merge(proto_bin.as_slice())?;
250
251 let root = hex::decode(data.root)?;
252 let key = hex::decode(data.key)?;
253 let value = if data.value.is_empty() {
254 None
255 } else {
256 Some(hex::decode(data.value)?)
257 };
258 let data = RefData { root, key, value };
259
260 Ok((parsed, data))
261 }
262
263 #[cfg(feature = "std")]
264 fn verify_test_vector(filename: &str, spec: &ics23::ProofSpec) -> Result<()> {
265 let (proof, data) = load_file(filename)?;
266
267 if let Some(value) = data.value {
268 let valid = super::verify_membership(&proof, spec, &data.root, &data.key, &value);
269 ensure!(valid, "invalid test vector");
270 Ok(())
271 } else {
272 let valid = super::verify_non_membership(&proof, spec, &data.root, &data.key);
273 ensure!(valid, "invalid test vector");
274 Ok(())
275 }
276 }
277
278 #[test]
279 #[cfg(feature = "std")]
280 fn test_vector_iavl_left() -> Result<()> {
281 let spec = iavl_spec();
282 verify_test_vector("../testdata/iavl/exist_left.json", &spec)
283 }
284
285 #[test]
286 #[cfg(feature = "std")]
287 fn test_vector_iavl_right() -> Result<()> {
288 let spec = iavl_spec();
289 verify_test_vector("../testdata/iavl/exist_right.json", &spec)
290 }
291
292 #[test]
293 #[cfg(feature = "std")]
294 fn test_vector_iavl_middle() -> Result<()> {
295 let spec = iavl_spec();
296 verify_test_vector("../testdata/iavl/exist_middle.json", &spec)
297 }
298
299 #[test]
300 #[cfg(feature = "std")]
301 fn test_vector_iavl_left_non() -> Result<()> {
302 let spec = iavl_spec();
303 verify_test_vector("../testdata/iavl/nonexist_left.json", &spec)
304 }
305
306 #[test]
307 #[cfg(feature = "std")]
308 fn test_vector_iavl_right_non() -> Result<()> {
309 let spec = iavl_spec();
310 verify_test_vector("../testdata/iavl/nonexist_right.json", &spec)
311 }
312
313 #[test]
314 #[cfg(feature = "std")]
315 fn test_vector_iavl_middle_non() -> Result<()> {
316 let spec = iavl_spec();
317 verify_test_vector("../testdata/iavl/nonexist_middle.json", &spec)
318 }
319
320 #[test]
321 #[cfg(feature = "std")]
322 fn test_vector_tendermint_left() -> Result<()> {
323 let spec = tendermint_spec();
324 verify_test_vector("../testdata/tendermint/exist_left.json", &spec)
325 }
326
327 #[test]
328 #[cfg(feature = "std")]
329 fn test_vector_tendermint_right() -> Result<()> {
330 let spec = tendermint_spec();
331 verify_test_vector("../testdata/tendermint/exist_right.json", &spec)
332 }
333
334 #[test]
335 #[cfg(feature = "std")]
336 fn test_vector_tendermint_middle() -> Result<()> {
337 let spec = tendermint_spec();
338 verify_test_vector("../testdata/tendermint/exist_middle.json", &spec)
339 }
340
341 #[test]
342 #[cfg(feature = "std")]
343 fn test_vector_tendermint_left_non() -> Result<()> {
344 let spec = tendermint_spec();
345 verify_test_vector("../testdata/tendermint/nonexist_left.json", &spec)
346 }
347
348 #[test]
349 #[cfg(feature = "std")]
350 fn test_vector_tendermint_right_non() -> Result<()> {
351 let spec = tendermint_spec();
352 verify_test_vector("../testdata/tendermint/nonexist_right.json", &spec)
353 }
354
355 #[test]
356 #[cfg(feature = "std")]
357 fn test_vector_tendermint_middle_non() -> Result<()> {
358 let spec = tendermint_spec();
359 verify_test_vector("../testdata/tendermint/nonexist_middle.json", &spec)
360 }
361
362 #[cfg(feature = "std")]
363 fn load_batch(files: &[&str]) -> Result<(ics23::CommitmentProof, Vec<RefData>)> {
364 let mut entries = Vec::new();
365 let mut data = Vec::new();
366
367 for &file in files {
368 let (proof, datum) = load_file(file)?;
369 data.push(datum);
370 match proof.proof {
371 Some(ics23::commitment_proof::Proof::Nonexist(non)) => {
372 entries.push(ics23::BatchEntry {
373 proof: Some(ics23::batch_entry::Proof::Nonexist(non)),
374 })
375 }
376 Some(ics23::commitment_proof::Proof::Exist(ex)) => {
377 entries.push(ics23::BatchEntry {
378 proof: Some(ics23::batch_entry::Proof::Exist(ex)),
379 })
380 }
381 _ => bail!("unknown proof type to batch"),
382 }
383 }
384
385 let batch = ics23::CommitmentProof {
386 proof: Some(ics23::commitment_proof::Proof::Batch(ics23::BatchProof {
387 entries,
388 })),
389 };
390
391 Ok((batch, data))
392 }
393
394 fn verify_batch(
395 spec: &ics23::ProofSpec,
396 proof: &ics23::CommitmentProof,
397 data: &RefData,
398 ) -> Result<()> {
399 if let Some(value) = &data.value {
400 let valid = super::verify_membership(proof, spec, &data.root, &data.key, value);
401 ensure!(valid, "invalid test vector");
402 let mut items = BTreeMap::new();
403 items.insert(data.key.as_slice(), value.as_slice());
404 let valid = super::verify_batch_membership(proof, spec, &data.root, items);
405 ensure!(valid, "invalid test vector");
406 Ok(())
407 } else {
408 let valid = super::verify_non_membership(proof, spec, &data.root, &data.key);
409 ensure!(valid, "invalid test vector");
410 let keys = &[data.key.as_slice()];
411 let valid = super::verify_batch_non_membership(proof, spec, &data.root, keys);
412 ensure!(valid, "invalid test vector");
413 Ok(())
414 }
415 }
416
417 #[test]
418 #[cfg(feature = "std")]
419 fn test_vector_iavl_batch_exist() -> Result<()> {
420 let spec = iavl_spec();
421 let (proof, data) = load_batch(&[
422 "../testdata/iavl/exist_left.json",
423 "../testdata/iavl/exist_right.json",
424 "../testdata/iavl/exist_middle.json",
425 "../testdata/iavl/nonexist_left.json",
426 "../testdata/iavl/nonexist_right.json",
427 "../testdata/iavl/nonexist_middle.json",
428 ])?;
429 verify_batch(&spec, &proof, &data[0])
430 }
431
432 #[test]
433 #[cfg(feature = "std")]
434 fn compressed_iavl_batch_exist() -> Result<()> {
435 let spec = iavl_spec();
436 let (proof, data) = load_batch(&[
437 "../testdata/iavl/exist_left.json",
438 "../testdata/iavl/exist_right.json",
439 "../testdata/iavl/exist_middle.json",
440 "../testdata/iavl/nonexist_left.json",
441 "../testdata/iavl/nonexist_right.json",
442 "../testdata/iavl/nonexist_middle.json",
443 ])?;
444 let comp = compress(&proof)?;
445 verify_batch(&spec, &comp, &data[0])
446 }
447
448 #[test]
449 #[cfg(feature = "std")]
450 fn test_vector_iavl_batch_nonexist() -> Result<()> {
451 let spec = iavl_spec();
452 let (proof, data) = load_batch(&[
453 "../testdata/iavl/exist_left.json",
454 "../testdata/iavl/exist_right.json",
455 "../testdata/iavl/exist_middle.json",
456 "../testdata/iavl/nonexist_left.json",
457 "../testdata/iavl/nonexist_right.json",
458 "../testdata/iavl/nonexist_middle.json",
459 ])?;
460 verify_batch(&spec, &proof, &data[4])
461 }
462
463 #[test]
464 #[cfg(feature = "std")]
465 fn compressed_iavl_batch_nonexist() -> Result<()> {
466 let spec = iavl_spec();
467 let (proof, data) = load_batch(&[
468 "../testdata/iavl/exist_left.json",
469 "../testdata/iavl/exist_right.json",
470 "../testdata/iavl/exist_middle.json",
471 "../testdata/iavl/nonexist_left.json",
472 "../testdata/iavl/nonexist_right.json",
473 "../testdata/iavl/nonexist_middle.json",
474 ])?;
475 let comp = compress(&proof)?;
476 verify_batch(&spec, &comp, &data[4])
477 }
478
479 #[test]
480 #[cfg(feature = "std")]
481 fn test_vector_tendermint_batch_exist() -> Result<()> {
482 let spec = tendermint_spec();
483 let (proof, data) = load_batch(&[
484 "../testdata/tendermint/exist_left.json",
485 "../testdata/tendermint/exist_right.json",
486 "../testdata/tendermint/exist_middle.json",
487 "../testdata/tendermint/nonexist_left.json",
488 "../testdata/tendermint/nonexist_right.json",
489 "../testdata/tendermint/nonexist_middle.json",
490 ])?;
491 verify_batch(&spec, &proof, &data[2])
492 }
493
494 #[test]
495 #[cfg(feature = "std")]
496 fn test_vector_tendermint_batch_nonexist() -> Result<()> {
497 let spec = tendermint_spec();
498 let (proof, data) = load_batch(&[
499 "../testdata/tendermint/exist_left.json",
500 "../testdata/tendermint/exist_right.json",
501 "../testdata/tendermint/exist_middle.json",
502 "../testdata/tendermint/nonexist_left.json",
503 "../testdata/tendermint/nonexist_right.json",
504 "../testdata/tendermint/nonexist_middle.json",
505 ])?;
506 verify_batch(&spec, &proof, &data[5])
507 }
508}