diff --git a/rust/CHANGELOG.md b/rust/CHANGELOG.md index f384b40a..f51e58ec 100644 --- a/rust/CHANGELOG.md +++ b/rust/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +# Unreleased + +- fix(rust): Interpret `max_depth` in proof specs as 128 if left to 0 [#371](https://github.com/cosmos/ics23/pull/371). + # v0.12.0 - chore(rust): Update `prost` to v0.13 ([#335](https://github.com/cosmos/ics23/pull/335), [#336](https://github.com/cosmos/ics23/pull/336)) diff --git a/rust/src/api.rs b/rust/src/api.rs index 6ba17b6d..030c2c29 100644 --- a/rust/src/api.rs +++ b/rust/src/api.rs @@ -580,6 +580,69 @@ mod tests { verify_test_vector("../testdata/smt/nonexist_middle.json", &spec) } + #[derive(Deserialize)] + #[serde(rename_all = "PascalCase")] + struct ExistenceProofTest { + proof: crate::ExistenceProof, + is_err: bool, + expected: Option>, + } + + #[test] + #[cfg(feature = "std")] + fn test_existence_proof() -> Result<()> { + use crate::calculate_existence_root; + + let data = std::fs::read_to_string("../testdata/TestExistenceProofData.json")?; + let tests: BTreeMap = serde_json::from_str(&data)?; + + for (name, test) in tests { + println!("Test: {name}"); + let result = calculate_existence_root::(&test.proof); + if test.is_err { + assert!(result.is_err()); + } else { + assert!(result.is_ok()); + assert_eq!( + result.unwrap().as_slice(), + test.expected.unwrap().as_slice() + ); + } + } + + Ok(()) + } + + #[derive(Deserialize)] + #[serde(rename_all = "PascalCase")] + struct CheckAgainstSpecTest { + proof: crate::ExistenceProof, + spec: crate::ProofSpec, + err: String, + } + + #[test] + #[cfg(feature = "std")] + fn test_check_against_spec() -> Result<()> { + use crate::verify::check_existence_spec; + + let data = std::fs::read_to_string("../testdata/TestCheckAgainstSpecData.json")?; + let tests: BTreeMap = serde_json::from_str(&data)?; + + for (name, test) in tests { + println!("Test: {name}"); + let result = check_existence_spec(&test.proof, &test.spec); + if test.err.is_empty() { + assert!(result.is_ok()); + } else { + assert!(result.is_err()); + // assert_eq!(result.unwrap_err().to_string(), test.err); + } + } + + Ok(()) + } + #[cfg(feature = "std")] fn load_batch(files: &[&str]) -> Result<(ics23::CommitmentProof, Vec)> { let (data, entries) = files diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4799357b..aff96078 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -14,6 +14,10 @@ mod verify; mod ics23 { include!("cosmos.ics23.v1.rs"); + impl ProofSpec { + pub const DEFAULT_MAX_DEPTH: i32 = 128; + } + #[cfg(feature = "serde")] include!("cosmos.ics23.v1.serde.rs"); } diff --git a/rust/src/verify.rs b/rust/src/verify.rs index ade0b93a..01eee6a2 100644 --- a/rust/src/verify.rs +++ b/rust/src/verify.rs @@ -114,31 +114,47 @@ fn calculate_existence_root_for_spec( } } -fn check_existence_spec(proof: &ics23::ExistenceProof, spec: &ics23::ProofSpec) -> Result<()> { - if let (Some(leaf), Some(leaf_spec)) = (&proof.leaf, &spec.leaf_spec) { - ensure_leaf_prefix(&leaf.prefix, spec)?; - ensure_leaf(leaf, leaf_spec)?; - // ensure min/max depths - if spec.min_depth != 0 { - ensure!( - proof.path.len() >= spec.min_depth as usize, - "Too few InnerOps: {}", - proof.path.len(), - ); - ensure!( - proof.path.len() <= spec.max_depth as usize, - "Too many InnerOps: {}", - proof.path.len(), - ); - } - for (idx, step) in proof.path.iter().enumerate() { - ensure_inner_prefix(&step.prefix, spec, (idx as i64) + 1, step.hash)?; - ensure_inner(step, spec)?; - } - Ok(()) +pub(crate) fn check_existence_spec( + proof: &ics23::ExistenceProof, + spec: &ics23::ProofSpec, +) -> Result<()> { + let Some(leaf) = &proof.leaf else { + bail!("existence Proof needs defined LeafOp"); + }; + + let Some(leaf_spec) = &spec.leaf_spec else { + bail!("existence Proof needs defined LeafSpec"); + }; + + ensure_leaf_prefix(&leaf.prefix, spec)?; + ensure_leaf(leaf, leaf_spec)?; + + let max_depth = if spec.max_depth == 0 { + ics23::ProofSpec::DEFAULT_MAX_DEPTH } else { - bail!("Leaf and Leaf Spec must be set") + spec.max_depth + }; + + // ensure min/max depths + if spec.min_depth != 0 { + ensure!( + proof.path.len() >= spec.min_depth as usize, + "innerOps depth too short: {}", + proof.path.len(), + ); + ensure!( + proof.path.len() <= max_depth as usize, + "innerOps depth too long: {}", + proof.path.len(), + ); + } + + for (idx, step) in proof.path.iter().enumerate() { + ensure_inner_prefix(&step.prefix, spec, (idx as i64) + 1, step.hash)?; + ensure_inner(step, spec)?; } + + Ok(()) } fn ensure_leaf(leaf: &ics23::LeafOp, leaf_spec: &ics23::LeafOp) -> Result<()> {