Improved API for verifiers
This commit is contained in:
parent
eaa45ca32f
commit
e071c4e75d
2 changed files with 294 additions and 117 deletions
|
@ -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<dyn Error>> {
|
|||
.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<Vec<u8>, Box<dyn Error>> {
|
||||
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<dyn Error>> {
|
||||
let file = File::create(proof_file)?;
|
||||
let mut writer = BufWriter::new(file);
|
||||
|
|
|
@ -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<CommitmentProof>,
|
||||
}
|
||||
|
||||
/// Convert to ics23::CommitmentProof
|
||||
impl From<RawMerkleProof> for MerkleProof {
|
||||
fn from(proof: RawMerkleProof) -> Self {
|
||||
Self {
|
||||
proofs: proof.proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MerkleProof> for RawMerkleProof {
|
||||
fn from(proof: MerkleProof) -> Self {
|
||||
Self {
|
||||
proofs: proof.proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MerkleProof {
|
||||
pub fn verify_membership(
|
||||
&self,
|
||||
specs: &ProofSpecs,
|
||||
root: MerkleRoot,
|
||||
keys: Vec<Vec<u8>>,
|
||||
value: Vec<u8>,
|
||||
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::<ProofSpec>::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::<ics23::HostFunctionsManager>(existence_proof)
|
||||
.map_err(|_| ProofError::invalid_merkle_proof())?;
|
||||
|
||||
if !verify_membership::<ics23::HostFunctionsManager>(
|
||||
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<MerkleProof, ProofError> {
|
||||
tm_proof: &ProofOps,
|
||||
) -> Result<Vec<CommitmentProof>, 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<Self::Root, Self::Error>;
|
||||
|
||||
fn verify_against_root(
|
||||
&self,
|
||||
proof: &Self::Proof,
|
||||
key: &Self::Key,
|
||||
value: &Self::Value,
|
||||
root: &Self::Root,
|
||||
) -> Result<bool, Self::Error> {
|
||||
let found_root = self.verify(proof, key, value)?;
|
||||
Ok(root == &found_root)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Ics23MembershipVerifier<K, V> {
|
||||
spec: ProofSpec,
|
||||
_phantom: PhantomData<(K, V)>,
|
||||
}
|
||||
|
||||
impl<K, V> Ics23MembershipVerifier<K, V> {
|
||||
fn new(spec: ProofSpec) -> Self {
|
||||
Self {
|
||||
spec,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Verifier for Ics23MembershipVerifier<K, V>
|
||||
where
|
||||
K: AsRef<[u8]>,
|
||||
V: AsRef<[u8]>,
|
||||
{
|
||||
type Proof = CommitmentProof;
|
||||
type Root = Vec<u8>;
|
||||
type Key = K;
|
||||
type Value = V;
|
||||
type Error = ProofError;
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
commitment_proof: &Self::Proof,
|
||||
key: &Self::Key,
|
||||
value: &Self::Value,
|
||||
) -> Result<Self::Root, Self::Error> {
|
||||
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::<ics23::HostFunctionsManager>(existence_proof)
|
||||
.map_err(|_| ProofError::invalid_merkle_proof())?;
|
||||
|
||||
if !verify_membership::<ics23::HostFunctionsManager>(
|
||||
commitment_proof,
|
||||
&self.spec,
|
||||
&root,
|
||||
key.as_ref(),
|
||||
value.as_ref(),
|
||||
) {
|
||||
return Err(ProofError::verification_failure());
|
||||
}
|
||||
|
||||
Ok(root)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct MultiVerifier<V, const N: usize> {
|
||||
verifiers: [V; N],
|
||||
}
|
||||
|
||||
impl<V, const N: usize> MultiVerifier<V, N> {
|
||||
fn new(verifiers: [V; N]) -> Self {
|
||||
assert!(N > 0, "cannot create empty multi-verifiers");
|
||||
Self { verifiers }
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, const N: usize> Verifier for MultiVerifier<V, N>
|
||||
where
|
||||
V: Verifier,
|
||||
V::Value: Clone,
|
||||
V::Root: Into<V::Value> + 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<Self::Root, Self::Error> {
|
||||
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<Ics23MembershipVerifier<Vec<u8>, Vec<u8>>, 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<u8>,
|
||||
keys: &[Vec<u8>; 2],
|
||||
value: &Vec<u8>,
|
||||
) -> 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<u8>) -> Result<(), ProofError>;
|
||||
}
|
||||
|
||||
trait IntoKeys {
|
||||
fn into_keys(self) -> Vec<Vec<u8>>;
|
||||
}
|
||||
|
||||
struct PrefixedKey<P, K> {
|
||||
key: K,
|
||||
prefix: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P, K> PrefixedKey<P, K> {
|
||||
fn new(key: K) -> Self {
|
||||
Self {
|
||||
key,
|
||||
prefix: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, K> IntoKeys for PrefixedKey<P, K>
|
||||
where
|
||||
K: Into<Vec<u8>>,
|
||||
P: ConstPrefix,
|
||||
{
|
||||
fn into_keys(self) -> Vec<Vec<u8>> {
|
||||
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<K> IntoKeys for K
|
||||
where
|
||||
K: Into<Vec<u8>>,
|
||||
{
|
||||
fn into_keys(self) -> Vec<Vec<u8>> {
|
||||
vec![self.into()]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QueryProof<K, V> {
|
||||
proof: ProofOps,
|
||||
key: PrefixedKey<PrefixWasm, K>,
|
||||
value: V,
|
||||
}
|
||||
|
||||
pub type RawQueryProof = QueryProof<Vec<u8>, Vec<u8>>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ErrorWithoutProof;
|
||||
|
||||
impl TryFrom<AbciQuery> for RawQueryProof {
|
||||
type Error = ErrorWithoutProof;
|
||||
|
||||
fn try_from(query: AbciQuery) -> Result<Self, Self::Error> {
|
||||
let AbciQuery {
|
||||
key, value, proof, ..
|
||||
} = query;
|
||||
let Some(proof) = proof else {
|
||||
return Err(ErrorWithoutProof);
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
proof,
|
||||
key: PrefixedKey::new(key),
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> CwProof for QueryProof<K, V>
|
||||
where
|
||||
K: Into<Vec<u8>>,
|
||||
V: Into<Vec<u8>>,
|
||||
{
|
||||
type Key = K;
|
||||
type Value = V;
|
||||
type Proof = ProofOps;
|
||||
|
||||
fn verify(self, root: Vec<u8>) -> Result<(), ProofError> {
|
||||
fn as_array_2<T: Debug>(v: Vec<T>) -> 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(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue