informalsystems_ics23/
api.rs

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// Use CommitmentRoot vs &[u8] to stick with ics naming
11#[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    // ugly attempt to conditionally decompress...
20    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(ics23::commitment_proof::Proof::Exist(ex)) = &proof.proof {
32    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// Use CommitmentRoot vs &[u8] to stick with ics naming
41#[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    // ugly attempt to conditionally decompress...
49    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    // ugly attempt to conditionally decompress...
76    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    // ugly attempt to conditionally decompress...
100    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                    // use iter/all - true if None, must check if Some
145                    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}