From e071c4e75d92acd843daef9d490724bbc140b26e Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Sat, 30 Dec 2023 03:46:25 -0800 Subject: [PATCH] Improved API for verifiers --- utils/cw-prover/src/main.rs | 30 +-- utils/cw-prover/src/merkle.rs | 381 +++++++++++++++++++++++++--------- 2 files changed, 294 insertions(+), 117 deletions(-) diff --git a/utils/cw-prover/src/main.rs b/utils/cw-prover/src/main.rs index 0851e50..4e4a344 100644 --- a/utils/cw-prover/src/main.rs +++ b/utils/cw-prover/src/main.rs @@ -14,23 +14,23 @@ unused_qualifications )] +mod merkle; + use std::error::Error; +use std::fmt::Debug; use std::fs::File; use std::io::{BufWriter, Write}; use std::path::PathBuf; use clap::{Parser, Subcommand}; use cosmrs::AccountId; -use ibc_relayer_types::{ - core::ics23_commitment::commitment::CommitmentRoot, core::ics23_commitment::specs::ProofSpecs, -}; use tendermint::block::Height; use tendermint::AppHash; use tendermint_rpc::endpoint::abci_query::AbciQuery; use tendermint_rpc::endpoint::status::Response; use tendermint_rpc::{client::HttpClient as TmRpcClient, Client, HttpClientUrl}; -mod merkle; +use crate::merkle::{CwProof, RawQueryProof}; #[derive(Debug, Parser)] #[command(author, version, about, long_about = None)] @@ -91,8 +91,10 @@ async fn main() -> Result<(), Box> { .abci_query(Some(path), data, Some(proof_height), true) .await?; - let value = verify_proof(latest_app_hash, result.clone())?; - println!("{}", String::from_utf8(value)?); + let proof: RawQueryProof = result.clone().try_into().expect("todo"); + proof.verify(latest_app_hash.clone().into())?; + + println!("{}", String::from_utf8(result.value.clone())?); if let Some(proof_file) = proof_file { write_proof_to_file(proof_file, result)?; @@ -141,22 +143,6 @@ fn encode_length(namespace: &[u8]) -> [u8; 2] { [length_bytes[2], length_bytes[3]] } -fn verify_proof(latest_app_hash: AppHash, result: AbciQuery) -> Result, Box> { - let proof = merkle::convert_tm_to_ics_merkle_proof(&result.proof.expect("queried with proof"))?; - let root = CommitmentRoot::from_bytes(latest_app_hash.as_bytes()); - let prefixed_key = vec!["wasm".to_string().into_bytes(), result.key]; - - proof.verify_membership( - &ProofSpecs::cosmos(), - root.into(), - prefixed_key, - result.value.clone(), - 0, - )?; - - Ok(result.value) -} - fn write_proof_to_file(proof_file: PathBuf, output: AbciQuery) -> Result<(), Box> { let file = File::create(proof_file)?; let mut writer = BufWriter::new(file); diff --git a/utils/cw-prover/src/merkle.rs b/utils/cw-prover/src/merkle.rs index 35c67e7..f6a9e65 100644 --- a/utils/cw-prover/src/merkle.rs +++ b/utils/cw-prover/src/merkle.rs @@ -1,103 +1,18 @@ -use ibc_proto::ibc::core::commitment::v1::{MerkleProof as RawMerkleProof, MerkleRoot}; -use ibc_relayer_types::{ - core::ics23_commitment::error::Error as ProofError, core::ics23_commitment::specs::ProofSpecs, -}; +use core::fmt::Debug; +use core::marker::PhantomData; + +use ibc_relayer_types::core::ics23_commitment::error::Error as ProofError; use ics23::{ calculate_existence_root, commitment_proof::Proof, verify_membership, CommitmentProof, ProofSpec, }; -use tendermint::merkle::proof::ProofOps as TendermintProof; - -// Copied from hermes and patched to allow non-string keys -#[derive(Clone, Debug, PartialEq)] -pub struct MerkleProof { - proofs: Vec, -} - -/// Convert to ics23::CommitmentProof -impl From for MerkleProof { - fn from(proof: RawMerkleProof) -> Self { - Self { - proofs: proof.proofs, - } - } -} - -impl From for RawMerkleProof { - fn from(proof: MerkleProof) -> Self { - Self { - proofs: proof.proofs, - } - } -} - -impl MerkleProof { - pub fn verify_membership( - &self, - specs: &ProofSpecs, - root: MerkleRoot, - keys: Vec>, - value: Vec, - start_index: usize, - ) -> Result<(), ProofError> { - // validate arguments - if self.proofs.is_empty() { - return Err(ProofError::empty_merkle_proof()); - } - if root.hash.is_empty() { - return Err(ProofError::empty_merkle_root()); - } - let num = self.proofs.len(); - let ics23_specs = Vec::::from(specs.clone()); - if ics23_specs.len() != num { - return Err(ProofError::number_of_specs_mismatch()); - } - if keys.len() != num { - return Err(ProofError::number_of_keys_mismatch()); - } - if value.is_empty() { - return Err(ProofError::empty_verified_value()); - } - - let mut subroot = value.clone(); - let mut value = value; - - // keys are represented from root-to-leaf - for ((proof, spec), key) in self - .proofs - .iter() - .zip(ics23_specs.iter()) - .zip(keys.iter().rev()) - .skip(start_index) - { - match &proof.proof { - Some(Proof::Exist(existence_proof)) => { - subroot = - calculate_existence_root::(existence_proof) - .map_err(|_| ProofError::invalid_merkle_proof())?; - - if !verify_membership::( - proof, spec, &subroot, key, &value, - ) { - return Err(ProofError::verification_failure()); - } - value = subroot.clone(); - } - _ => return Err(ProofError::invalid_merkle_proof()), - } - } - - if root.hash != subroot { - return Err(ProofError::verification_failure()); - } - - Ok(()) - } -} +use tendermint::merkle::proof::ProofOps; +use tendermint_rpc::endpoint::abci_query::AbciQuery; +// Copied from hermes pub fn convert_tm_to_ics_merkle_proof( - tm_proof: &TendermintProof, -) -> Result { + tm_proof: &ProofOps, +) -> Result, ProofError> { let mut proofs = Vec::new(); for op in &tm_proof.ops { @@ -109,5 +24,281 @@ pub fn convert_tm_to_ics_merkle_proof( proofs.push(parsed); } - Ok(MerkleProof::from(RawMerkleProof { proofs })) + Ok(proofs) +} + +trait Verifier { + type Proof; + type Root: Eq; + type Key; + type Value; + type Error; + + fn verify( + &self, + proof: &Self::Proof, + key: &Self::Key, + value: &Self::Value, + ) -> Result; + + fn verify_against_root( + &self, + proof: &Self::Proof, + key: &Self::Key, + value: &Self::Value, + root: &Self::Root, + ) -> Result { + let found_root = self.verify(proof, key, value)?; + Ok(root == &found_root) + } +} + +#[derive(Clone, Debug)] +struct Ics23MembershipVerifier { + spec: ProofSpec, + _phantom: PhantomData<(K, V)>, +} + +impl Ics23MembershipVerifier { + fn new(spec: ProofSpec) -> Self { + Self { + spec, + _phantom: Default::default(), + } + } +} + +impl Verifier for Ics23MembershipVerifier +where + K: AsRef<[u8]>, + V: AsRef<[u8]>, +{ + type Proof = CommitmentProof; + type Root = Vec; + type Key = K; + type Value = V; + type Error = ProofError; + + fn verify( + &self, + commitment_proof: &Self::Proof, + key: &Self::Key, + value: &Self::Value, + ) -> Result { + if value.as_ref().is_empty() { + return Err(ProofError::empty_verified_value()); + } + + let Some(Proof::Exist(existence_proof)) = &commitment_proof.proof else { + return Err(ProofError::invalid_merkle_proof()); + }; + + let root = calculate_existence_root::(existence_proof) + .map_err(|_| ProofError::invalid_merkle_proof())?; + + if !verify_membership::( + commitment_proof, + &self.spec, + &root, + key.as_ref(), + value.as_ref(), + ) { + return Err(ProofError::verification_failure()); + } + + Ok(root) + } +} + +#[derive(Clone, Debug)] +struct MultiVerifier { + verifiers: [V; N], +} + +impl MultiVerifier { + fn new(verifiers: [V; N]) -> Self { + assert!(N > 0, "cannot create empty multi-verifiers"); + Self { verifiers } + } +} + +impl Verifier for MultiVerifier +where + V: Verifier, + V::Value: Clone, + V::Root: Into + Clone, +{ + type Proof = [V::Proof; N]; + type Root = V::Root; + type Key = [V::Key; N]; + type Value = V::Value; + type Error = V::Error; + + fn verify( + &self, + proofs: &Self::Proof, + keys: &Self::Key, + value: &Self::Value, + ) -> Result { + let mut root = None; + let mut value = value.clone(); + + for (idx, verifier) in self.verifiers.iter().enumerate() { + let proof = &proofs[idx]; + let key = &keys[N - idx - 1]; + let sub_root = verifier.verify(proof, key, &value)?; + + value = sub_root.clone().into(); + root = Some(sub_root); + } + + Ok(root.expect("MultiVerifier cannot be instantiated with 0 verifiers")) + } +} + +#[derive(Clone, Debug)] +pub struct CwVerifier(MultiVerifier, Vec>, 2>); + +impl CwVerifier { + pub fn new() -> Self { + let mv = MultiVerifier::new([ + Ics23MembershipVerifier::new(ics23::iavl_spec()), + Ics23MembershipVerifier::new(ics23::tendermint_spec()), + ]); + Self(mv) + } + + pub fn verify( + &self, + proofs: &[CommitmentProof; 2], + root: &Vec, + keys: &[Vec; 2], + value: &Vec, + ) -> Result<(), ProofError> { + if root.is_empty() { + return Err(ProofError::empty_merkle_root()); + } + + let verified = self.0.verify_against_root(proofs, keys, value, root)?; + if !verified { + return Err(ProofError::verification_failure()); + } + Ok(()) + } +} + +pub trait CwProof { + type Key; + type Value; + type Proof; + + fn verify(self, root: Vec) -> Result<(), ProofError>; +} + +trait IntoKeys { + fn into_keys(self) -> Vec>; +} + +struct PrefixedKey { + key: K, + prefix: PhantomData

, +} + +impl PrefixedKey { + fn new(key: K) -> Self { + Self { + key, + prefix: PhantomData, + } + } +} + +impl IntoKeys for PrefixedKey +where + K: Into>, + P: ConstPrefix, +{ + fn into_keys(self) -> Vec> { + vec![P::PREFIX.to_string().into_bytes(), self.key.into()] + } +} + +struct PrefixWasm; + +trait ConstPrefix { + const PREFIX: &'static str; +} + +impl ConstPrefix for PrefixWasm { + const PREFIX: &'static str = "wasm"; +} + +impl IntoKeys for K +where + K: Into>, +{ + fn into_keys(self) -> Vec> { + vec![self.into()] + } +} + +pub struct QueryProof { + proof: ProofOps, + key: PrefixedKey, + value: V, +} + +pub type RawQueryProof = QueryProof, Vec>; + +#[derive(Clone, Debug)] +pub struct ErrorWithoutProof; + +impl TryFrom for RawQueryProof { + type Error = ErrorWithoutProof; + + fn try_from(query: AbciQuery) -> Result { + let AbciQuery { + key, value, proof, .. + } = query; + let Some(proof) = proof else { + return Err(ErrorWithoutProof); + }; + + Ok(Self { + proof, + key: PrefixedKey::new(key), + value, + }) + } +} + +impl CwProof for QueryProof +where + K: Into>, + V: Into>, +{ + type Key = K; + type Value = V; + type Proof = ProofOps; + + fn verify(self, root: Vec) -> Result<(), ProofError> { + fn as_array_2(v: Vec) -> Result<[T; 2], ProofError> { + let boxed_slice = v.into_boxed_slice(); + let boxed_array: Box<[T; 2]> = boxed_slice.try_into().expect("todo"); + Ok(*boxed_array) + } + + let Self { proof, key, value } = self; + let proofs = convert_tm_to_ics_merkle_proof(&proof)?; + + let cw_verifier = CwVerifier::new(); + cw_verifier.verify( + &as_array_2(proofs)?, + &root, + &as_array_2(key.into_keys())?, + &value.into(), + )?; + + Ok(()) + } }