Verify merkle proofs (#32)

This commit is contained in:
Shoaib Ahmed 2024-01-02 19:32:34 +05:30 committed by GitHub
commit 991b45a210
26 changed files with 1419 additions and 48 deletions

2
utils/cw-proof/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
Cargo.lock

18
utils/cw-proof/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "cw-proof"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.1.8", features = ["derive"] }
cosmrs = { version = "0.15.0", default-features = false }
displaydoc = { version = "0.2.4", default-features = false }
ics23 = { version = "0.11.0", default-features = false, features = ["host-functions"] }
prost = { version = "0.12.3", default-features = false }
serde = { version = "1.0.189", features = ["derive"] }
serde_with = { version = "3.4.0", default-features = false, features = ["hex", "macros"] }
tendermint = { version = "0.34.0", default-features = false }
tendermint-rpc = { version = "0.34.0", default-features = false, features = ["http-client"] }
[dev-dependencies]
hex = { version = "0.4.3", default-features = false }

View file

@ -0,0 +1,15 @@
use displaydoc::Display;
#[derive(Clone, Debug, Display)]
pub enum ProofError {
/// failed to decode commitment proof
CommitmentProofDecodingFailed,
/// empty merkle root
EmptyMerkleRoot,
/// empty verified value
EmptyVerifiedValue,
/// invalid merkle proof
InvalidMerkleProof,
/// proof verification failed
VerificationFailure,
}

20
utils/cw-proof/src/lib.rs Normal file
View file

@ -0,0 +1,20 @@
#![no_std]
#![forbid(unsafe_code)]
#![warn(
clippy::checked_conversions,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
trivial_casts,
trivial_numeric_casts,
rust_2018_idioms,
unused_lifetimes,
unused_import_braces,
unused_qualifications
)]
extern crate alloc;
pub mod error;
pub mod proof;
pub mod verifier;

View file

@ -0,0 +1,115 @@
use alloc::{boxed::Box, vec::Vec};
use core::fmt::Debug;
use displaydoc::Display;
use serde::{Deserialize, Serialize};
use serde_with::{hex::Hex, serde_as};
use tendermint::merkle::proof::ProofOps;
use tendermint_rpc::endpoint::abci_query::AbciQuery;
use crate::{
error::ProofError,
proof::{
convert_tm_to_ics_merkle_proof,
key::{IntoKeys, PrefixedKey},
prefix::PrefixWasm,
Proof,
},
verifier::cw::CwVerifier,
};
#[derive(Clone, Debug)]
pub struct CwProof<K = Vec<u8>, V = Vec<u8>> {
proof: ProofOps,
// TODO(hu55a1n1): replace `K` with `CwAbciKey`
key: PrefixedKey<PrefixWasm, K>,
value: V,
}
/// ABCI query response doesn't contain proof
#[derive(Clone, Debug, Display)]
pub struct ErrorWithoutProof;
impl TryFrom<AbciQuery> for CwProof {
type Error = ErrorWithoutProof;
fn try_from(query: AbciQuery) -> Result<Self, Self::Error> {
RawCwProof::try_from(query).map(Into::into)
}
}
impl<K, V> Proof for CwProof<K, V>
where
K: Clone + Into<Vec<u8>>,
V: AsRef<[u8]>,
{
type Key = K;
type Value = V;
type ProofOps = ProofOps;
fn verify(&self, root: Vec<u8>) -> Result<(), ProofError> {
fn into_array_of_size_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::default();
cw_verifier.verify(
&into_array_of_size_2(proofs)?,
&root,
&into_array_of_size_2(key.clone().into_keys())?,
value.as_ref(),
)?;
Ok(())
}
}
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawCwProof {
#[serde_as(as = "Hex")]
key: Vec<u8>,
#[serde_as(as = "Hex")]
value: Vec<u8>,
proof: ProofOps,
}
impl From<RawCwProof> for CwProof {
fn from(RawCwProof { key, value, proof }: RawCwProof) -> Self {
Self {
proof,
key: PrefixedKey::new(key),
value,
}
}
}
impl From<CwProof> for RawCwProof {
fn from(CwProof { proof, key, value }: CwProof) -> Self {
Self {
key: key.into_keys().pop().expect("empty key"),
value,
proof,
}
}
}
impl TryFrom<AbciQuery> for RawCwProof {
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, value })
}
}

View file

@ -0,0 +1,125 @@
use alloc::{
string::{String, ToString},
vec,
vec::Vec,
};
use core::marker::PhantomData;
use cosmrs::AccountId;
use crate::proof::prefix::ConstPrefix;
const CONTRACT_STORE_PREFIX: u8 = 0x03;
pub trait IntoKeys {
fn into_keys(self) -> Vec<Vec<u8>>;
}
impl<K> IntoKeys for K
where
K: Into<Vec<u8>>,
{
fn into_keys(self) -> Vec<Vec<u8>> {
vec![self.into()]
}
}
#[derive(Clone, Debug)]
pub struct PrefixedKey<P, K> {
key: K,
prefix: PhantomData<P>,
}
impl<P, K> PrefixedKey<P, K> {
pub 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()]
}
}
#[derive(Clone, Debug)]
pub enum CwAbciKey {
Item {
contract_address: AccountId,
storage_key: String,
},
Map {
contract_address: AccountId,
storage_key: String,
storage_namespace: String,
},
}
impl CwAbciKey {
pub fn new(
contract_address: AccountId,
storage_key: String,
storage_namespace: Option<String>,
) -> Self {
if let Some(storage_namespace) = storage_namespace {
Self::Map {
contract_address,
storage_key,
storage_namespace,
}
} else {
Self::Item {
contract_address,
storage_key,
}
}
}
fn into_tuple(self) -> (AccountId, String, Option<String>) {
match self {
CwAbciKey::Item {
contract_address,
storage_key,
} => (contract_address, storage_key, None),
CwAbciKey::Map {
contract_address,
storage_key,
storage_namespace,
} => (contract_address, storage_key, Some(storage_namespace)),
}
}
// Copied from cw-storage-plus
fn encode_length(namespace: &[u8]) -> [u8; 2] {
assert!(
namespace.len() <= 0xFFFF,
"only supports namespaces up to length 0xFFFF"
);
let length_bytes = (namespace.len() as u32).to_be_bytes();
[length_bytes[2], length_bytes[3]]
}
}
impl From<CwAbciKey> for Vec<u8> {
fn from(value: CwAbciKey) -> Self {
let (contract_address, storage_key, storage_namespace) = value.into_tuple();
let mut data = vec![CONTRACT_STORE_PREFIX];
data.append(&mut contract_address.to_bytes());
if let Some(namespace) = storage_namespace {
data.extend_from_slice(&CwAbciKey::encode_length(namespace.as_bytes()));
data.append(&mut namespace.into_bytes());
}
data.append(&mut storage_key.into_bytes());
data
}
}

View file

@ -0,0 +1,36 @@
use alloc::vec::Vec;
use ics23::CommitmentProof;
use tendermint::merkle::proof::ProofOps;
use crate::error::ProofError;
pub mod cw;
pub mod key;
pub mod prefix;
// Copied from hermes
pub fn convert_tm_to_ics_merkle_proof(
tm_proof: &ProofOps,
) -> Result<Vec<CommitmentProof>, ProofError> {
let mut proofs = Vec::new();
for op in &tm_proof.ops {
let mut parsed = CommitmentProof { proof: None };
prost::Message::merge(&mut parsed, op.data.as_slice())
.map_err(|_| ProofError::CommitmentProofDecodingFailed)?;
proofs.push(parsed);
}
Ok(proofs)
}
pub trait Proof {
type Key;
type Value;
type ProofOps;
fn verify(&self, root: Vec<u8>) -> Result<(), ProofError>;
}

View file

@ -0,0 +1,10 @@
pub trait ConstPrefix {
const PREFIX: &'static str;
}
#[derive(Clone, Debug)]
pub struct PrefixWasm;
impl ConstPrefix for PrefixWasm {
const PREFIX: &'static str = "wasm";
}

View file

@ -0,0 +1,42 @@
use alloc::borrow::Cow;
use alloc::vec::Vec;
use ics23::CommitmentProof;
use crate::error::ProofError;
use crate::verifier::{ics23::Ics23MembershipVerifier, multi::MultiVerifier, Verifier};
type Key = Vec<u8>;
type Value<'a> = Cow<'a, [u8]>;
#[derive(Clone, Debug)]
pub struct CwVerifier<'a>(MultiVerifier<Ics23MembershipVerifier<Key, Value<'a>>, 2>);
impl CwVerifier<'_> {
pub fn verify(
&self,
proofs: &[CommitmentProof; 2],
root: &Vec<u8>,
keys: &[Vec<u8>; 2],
value: &[u8],
) -> Result<(), ProofError> {
if root.is_empty() {
return Err(ProofError::EmptyMerkleRoot);
}
self.0
.verify_against_root(proofs, keys, &Cow::Borrowed(value), root)?
.then_some(())
.ok_or(ProofError::VerificationFailure)
}
}
impl Default for CwVerifier<'_> {
fn default() -> Self {
let mv = MultiVerifier::new([
Ics23MembershipVerifier::new(ics23::iavl_spec()),
Ics23MembershipVerifier::new(ics23::tendermint_spec()),
]);
Self(mv)
}
}

View file

@ -0,0 +1,67 @@
use alloc::vec::Vec;
use core::marker::PhantomData;
use ics23::{
calculate_existence_root, commitment_proof::Proof, verify_membership, CommitmentProof,
ProofSpec,
};
use crate::error::ProofError;
use crate::verifier::Verifier;
#[derive(Clone, Debug)]
pub struct Ics23MembershipVerifier<K, V> {
spec: ProofSpec,
_phantom: PhantomData<(K, V)>,
}
impl<K, V> Ics23MembershipVerifier<K, V> {
pub 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::EmptyVerifiedValue);
}
let Some(Proof::Exist(existence_proof)) = &commitment_proof.proof else {
return Err(ProofError::InvalidMerkleProof);
};
let root = calculate_existence_root::<ics23::HostFunctionsManager>(existence_proof)
.map_err(|_| ProofError::InvalidMerkleProof)?;
if !verify_membership::<ics23::HostFunctionsManager>(
commitment_proof,
&self.spec,
&root,
key.as_ref(),
value.as_ref(),
) {
return Err(ProofError::VerificationFailure);
}
Ok(root)
}
}

View file

@ -0,0 +1,29 @@
pub mod cw;
pub mod ics23;
pub mod multi;
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)
}
}

View file

@ -0,0 +1,47 @@
use crate::verifier::Verifier;
#[derive(Clone, Debug)]
pub struct MultiVerifier<V, const N: usize> {
verifiers: [V; N],
}
impl<V, const N: usize> MultiVerifier<V, N> {
pub 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::Root: Into<V::Value> + Clone,
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: V::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"))
}
}

View file

@ -206,6 +206,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"num-traits",
"serde",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.4.11" version = "4.4.11"
@ -349,17 +359,70 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "cw-proof"
version = "0.1.0"
dependencies = [
"clap",
"cosmrs",
"displaydoc",
"ics23",
"prost",
"serde",
"serde_with",
"tendermint",
"tendermint-rpc",
]
[[package]] [[package]]
name = "cw-prover" name = "cw-prover"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"cosmrs", "cosmrs",
"cw-proof",
"hex",
"serde_json", "serde_json",
"tendermint",
"tendermint-rpc", "tendermint-rpc",
"tokio", "tokio",
] ]
[[package]]
name = "darling"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.41",
]
[[package]]
name = "darling_macro"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.41",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.8" version = "0.7.8"
@ -400,6 +463,17 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "displaydoc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.41",
]
[[package]] [[package]]
name = "ecdsa" name = "ecdsa"
version = "0.16.9" version = "0.16.9"
@ -748,6 +822,27 @@ dependencies = [
"tokio-rustls", "tokio-rustls",
] ]
[[package]]
name = "ics23"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "661e2d6f79952a65bc92b1c81f639ebd37228dae6ff412a5aba7d474bdc4b957"
dependencies = [
"anyhow",
"bytes",
"hex",
"prost",
"ripemd",
"sha2 0.10.8",
"sha3",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.5.0" version = "0.5.0"
@ -816,6 +911,15 @@ dependencies = [
"sha2 0.10.8", "sha2 0.10.8",
] ]
[[package]]
name = "keccak"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940"
dependencies = [
"cpufeatures",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.151" version = "0.2.151"
@ -1373,6 +1477,33 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_with"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
dependencies = [
"base64",
"chrono",
"hex",
"serde",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.41",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.9.9" version = "0.9.9"
@ -1397,6 +1528,16 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
] ]
[[package]]
name = "sha3"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
"digest 0.10.7",
"keccak",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"

View file

@ -8,6 +8,11 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.1.8", features = ["derive"] } clap = { version = "4.1.8", features = ["derive"] }
cosmrs = "0.15.0" cosmrs = "0.15.0"
cw-proof = {path = "../cw-proof"}
serde_json = "1.0.108"
tendermint = "0.34.0"
tendermint-rpc = { version = "0.34.0", features = ["http-client"] } tendermint-rpc = { version = "0.34.0", features = ["http-client"] }
tokio = { version = "1.26.0", features = ["full"] } tokio = { version = "1.26.0", features = ["full"] }
serde_json = "1.0.108"
[dev-dependencies]
hex = "0.4.3"

View file

@ -14,11 +14,22 @@
unused_qualifications unused_qualifications
)] )]
use std::error::Error; use std::{
error::Error,
fmt::Debug,
fs::File,
io::{BufWriter, Write},
path::PathBuf,
};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use cosmrs::AccountId; use cosmrs::AccountId;
use tendermint_rpc::{client::HttpClient as TmRpcClient, Client, HttpClientUrl}; use cw_proof::proof::cw::RawCwProof;
use cw_proof::proof::{cw::CwProof, key::CwAbciKey, Proof};
use tendermint::{block::Height, AppHash};
use tendermint_rpc::{
client::HttpClient as TmRpcClient, endpoint::status::Response, Client, HttpClientUrl,
};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
@ -42,11 +53,19 @@ enum Command {
/// Storage key of the state item for which proofs must be retrieved /// Storage key of the state item for which proofs must be retrieved
#[clap(long)] #[clap(long)]
storage_key: String, storage_key: String,
/// Storage namespace of the state item for which proofs must be retrieved
/// (only makes sense when dealing with maps)
#[clap(long)]
storage_namespace: Option<String>,
/// Output file to store merkle proof
#[clap(long)]
proof_file: Option<PathBuf>,
}, },
} }
const WASM_STORE_KEY: &str = "/store/wasm/key"; const WASM_STORE_KEY: &str = "/store/wasm/key";
const CONTRACT_STORE_PREFIX: u8 = 0x03;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {
@ -57,27 +76,141 @@ async fn main() -> Result<(), Box<dyn Error>> {
rpc_url, rpc_url,
contract_address, contract_address,
storage_key, storage_key,
storage_namespace,
proof_file,
} => { } => {
let path = WASM_STORE_KEY.to_owned();
let data = {
let mut data = vec![CONTRACT_STORE_PREFIX];
data.append(&mut contract_address.to_bytes());
data.append(&mut storage_key.into_bytes());
data
};
let client = TmRpcClient::builder(rpc_url).build()?; let client = TmRpcClient::builder(rpc_url).build()?;
let latest_height = client.status().await?.sync_info.latest_block_height; let status = client.status().await?;
let (proof_height, latest_app_hash) = latest_proof_height_hash(status);
let path = WASM_STORE_KEY.to_owned();
let data = CwAbciKey::new(contract_address, storage_key, storage_namespace);
let result = client let result = client
.abci_query(Some(path), data, Some(latest_height), true) .abci_query(Some(path), data, Some(proof_height), true)
.await?; .await?;
println!( let proof: CwProof = result.clone().try_into().map_err(into_string)?;
"{}", proof
serde_json::to_string(&result).expect("infallible serializer") .verify(latest_app_hash.clone().into())
); .map_err(into_string)?;
println!("{}", String::from_utf8(result.value.clone())?);
if let Some(proof_file) = proof_file {
write_proof_to_file(proof_file, proof.into())?;
}
} }
}; };
Ok(()) Ok(())
} }
fn into_string<E: ToString>(e: E) -> String {
e.to_string()
}
fn latest_proof_height_hash(status: Response) -> (Height, AppHash) {
let proof_height = {
let latest_height = status.sync_info.latest_block_height;
(latest_height.value() - 1)
.try_into()
.expect("infallible conversion")
};
let latest_app_hash = status.sync_info.latest_app_hash;
(proof_height, latest_app_hash)
}
fn write_proof_to_file(proof_file: PathBuf, proof: RawCwProof) -> Result<(), Box<dyn Error>> {
let file = File::create(proof_file)?;
let mut writer = BufWriter::new(file);
serde_json::to_writer(&mut writer, &proof)?;
writer.flush()?;
Ok(())
}
#[cfg(test)]
mod tests {
use cw_proof::{proof::cw::RawCwProof, proof::Proof};
use tendermint_rpc::endpoint::abci_query::AbciQuery;
#[test]
fn test_query_item() {
let abci_query_response = r#"{
"code": 0,
"log": "",
"info": "",
"index": "0",
"key": "A63kpfWAOkOYNcY2OVqNZI3uV7L8kNmNwX+ohxWbaWOLc2d4c3RhdGU=",
"value": "eyJjb21wdXRlX21yZW5jbGF2ZSI6ImRjNDNmOGM0MmQ4ZTVmNTJjOGJiZDY4ZjQyNjI0MjE1M2YwYmUxMDYzMGZmOGNjYTI1NTEyOWEzY2EwM2QyNzMiLCJrZXlfbWFuYWdlcl9tcmVuY2xhdmUiOiIxY2YyZTUyOTExNDEwZmJmM2YxOTkwNTZhOThkNTg3OTVhNTU5YTJlODAwOTMzZjdmY2QxM2QwNDg0NjIyNzFjIiwidGNiX2luZm8iOiIzMTIzODc2In0=",
"proof": {
"ops": [
{
"field_type": "ics23:iavl",
"key": "A63kpfWAOkOYNcY2OVqNZI3uV7L8kNmNwX+ohxWbaWOLc2d4c3RhdGU=",
"data": "CrgDCikDreSl9YA6Q5g1xjY5Wo1kje5XsvyQ2Y3Bf6iHFZtpY4tzZ3hzdGF0ZRLIAXsiY29tcHV0ZV9tcmVuY2xhdmUiOiJkYzQzZjhjNDJkOGU1ZjUyYzhiYmQ2OGY0MjYyNDIxNTNmMGJlMTA2MzBmZjhjY2EyNTUxMjlhM2NhMDNkMjczIiwia2V5X21hbmFnZXJfbXJlbmNsYXZlIjoiMWNmMmU1MjkxMTQxMGZiZjNmMTk5MDU2YTk4ZDU4Nzk1YTU1OWEyZTgwMDkzM2Y3ZmNkMTNkMDQ4NDYyMjcxYyIsInRjYl9pbmZvIjoiMzEyMzg3NiJ9GgwIARgBIAEqBAACmAEiKggBEiYCBJgBIFclzyzP2y2LTcBhP0IxBhvnlMJiEFCsDEMUQ9dM5dvYICIsCAESBQQGmAEgGiEgfUSWe0VMFTsxkzDuMQNE05aSzdRTTvkWzZXkfplWUbEiKggBEiYGDJBnIEkK+nmGmXpOfREXvfonLrK4mEZx1XF4DgJp86QIVF1EICIsCAESBQgakGcgGiEgBl/NSR16eG1vDenJA6GEEJ9xcQv9Bwxv8wyhAL5JLwE="
},
{
"field_type": "ics23:simple",
"key": "d2FzbQ==",
"data": "CqgBCgR3YXNtEiDYWxn2B9M/eGP18Gwl3zgWZkT7Yn/iFlcS0THfmfcfDBoJCAEYASABKgEAIiUIARIhAWLU8PgnJ/EMp4BYvtTN9MX/rS70dNQ3ZAzrJLssrLjRIiUIARIhAcFEiiCwgvh2CwGJrnfnBCvuNl9u4BgngCVVKihSxYahIiUIARIhAVPQq6npMIxTVF19htERZGPpp0TZZaNLGho3+Y1oBFLg"
}
]
},
"height": "7355",
"codespace": ""
}
"#;
let abci_query: AbciQuery = serde_json::from_str(abci_query_response)
.expect("deserialization failure for hardcoded response");
let proof: RawCwProof = abci_query
.try_into()
.expect("hardcoded response does not include proof");
let root = "25a8b485e0ff095f7b60a1aab837d65756c9a4cdc216bae7ba9c59b3fb28fbec";
proof
.verify(hex::decode(root).expect("invalid hex"))
.expect("");
}
#[test]
fn test_query_map() {
let abci_query_response = r#"{
"code": 0,
"log": "",
"info": "",
"index": "0",
"key": "A63kpfWAOkOYNcY2OVqNZI3uV7L8kNmNwX+ohxWbaWOLAAhyZXF1ZXN0czQyNWQ4N2Y4NjIwZTFkZWRlZWU3MDU5MGNjNTViMTY0YjhmMDE0ODBlZTU5ZTBiMWRhMzU0MzZhMmY3YzI3Nzc=",
"value": "eyJqb2luX2NvbXB1dGVfbm9kZSI6WyIwM0U2N0VGMDkyMTM2MzMwNzRGQjRGQkYzMzg2NDNGNEYwQzU3NEVENjBFRjExRDAzNDIyRUVCMDZGQTM4QzhGM0YiLCJ3YXNtMTBuNGRzbGp5eWZwMmsyaHk2ZTh2dWM5cnkzMnB4MmVnd3Q1ZTBtIl19",
"proof": {
"ops": [
{
"field_type": "ics23:iavl",
"key": "A63kpfWAOkOYNcY2OVqNZI3uV7L8kNmNwX+ohxWbaWOLAAhyZXF1ZXN0czQyNWQ4N2Y4NjIwZTFkZWRlZWU3MDU5MGNjNTViMTY0YjhmMDE0ODBlZTU5ZTBiMWRhMzU0MzZhMmY3YzI3Nzc=",
"data": "CrwDCmsDreSl9YA6Q5g1xjY5Wo1kje5XsvyQ2Y3Bf6iHFZtpY4sACHJlcXVlc3RzNDI1ZDg3Zjg2MjBlMWRlZGVlZTcwNTkwY2M1NWIxNjRiOGYwMTQ4MGVlNTllMGIxZGEzNTQzNmEyZjdjMjc3NxKKAXsiam9pbl9jb21wdXRlX25vZGUiOlsiMDNFNjdFRjA5MjEzNjMzMDc0RkI0RkJGMzM4NjQzRjRGMEM1NzRFRDYwRUYxMUQwMzQyMkVFQjA2RkEzOEM4RjNGIiwid2FzbTEwbjRkc2xqeXlmcDJrMmh5NmU4dnVjOXJ5MzJweDJlZ3d0NWUwbSJdfRoMCAEYASABKgQAApBnIioIARImAgSQZyDcejBg60yYaDEvKvExWQf9XKfIaNU/Amt6hqCn7y+CSiAiKggBEiYEBpBnICanihuey/DZHbttCL13YV1SMnCD6D6J2zssxb7sqwrlICIsCAESBQYMkGcgGiEg+89wQcyopgtcMvQ2ceLVOsi6b3IcMYCR2UZrrqAV1xsiLAgBEgUIGpBnIBohIAZfzUkdenhtbw3pyQOhhBCfcXEL/QcMb/MMoQC+SS8B"
},
{
"field_type": "ics23:simple",
"key": "d2FzbQ==",
"data": "CqgBCgR3YXNtEiDYWxn2B9M/eGP18Gwl3zgWZkT7Yn/iFlcS0THfmfcfDBoJCAEYASABKgEAIiUIARIhAWLU8PgnJ/EMp4BYvtTN9MX/rS70dNQ3ZAzrJLssrLjRIiUIARIhARcbvq+IA7uFZQ37EHO4TUVW33UPw2gl4PnFAPf/w+LDIiUIARIhAZIp1f1XIqpz3QSNX3F9i7IGdc8DHeSpBJ/Qhg3httiR"
}
]
},
"height": "7589",
"codespace": ""
}
"#;
let abci_query: AbciQuery = serde_json::from_str(abci_query_response)
.expect("deserialization failure for hardcoded response");
let proof: RawCwProof = abci_query
.try_into()
.expect("hardcoded response does not include proof");
let root = "632612de75657f50bbb769157bf0ef8dd417409b367b0204bbda4529ab2b2d4f";
proof
.verify(hex::decode(root).expect("invalid hex"))
.expect("");
}
}

View file

@ -112,6 +112,12 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.5" version = "0.21.5"
@ -124,6 +130,22 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bip32"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164"
dependencies = [
"bs58",
"hmac",
"k256",
"rand_core",
"ripemd",
"sha2 0.10.8",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -148,6 +170,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "bs58"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896"
dependencies = [
"sha2 0.10.8",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.14.0" version = "3.14.0"
@ -190,6 +221,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"num-traits",
"serde",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.4.7" version = "4.4.7"
@ -296,6 +337,37 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cosmos-sdk-proto"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237"
dependencies = [
"prost",
"prost-types",
"tendermint-proto",
]
[[package]]
name = "cosmrs"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea"
dependencies = [
"bip32",
"cosmos-sdk-proto",
"ecdsa",
"eyre",
"k256",
"rand_core",
"serde",
"serde_json",
"signature",
"subtle-encoding",
"tendermint",
"thiserror",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.11" version = "0.2.11"
@ -326,6 +398,18 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "crypto-bigint"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
"rand_core",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -349,6 +433,56 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "cw-proof"
version = "0.1.0"
dependencies = [
"clap",
"cosmrs",
"displaydoc",
"ics23",
"prost",
"serde",
"serde_with",
"tendermint",
"tendermint-rpc",
]
[[package]]
name = "darling"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.39",
]
[[package]]
name = "darling_macro"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.39",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.8" version = "0.7.8"
@ -395,7 +529,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer 0.10.4", "block-buffer 0.10.4",
"const-oid",
"crypto-common", "crypto-common",
"subtle",
]
[[package]]
name = "displaydoc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
]
[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest 0.10.7",
"elliptic-curve",
"rfc6979",
"signature",
"spki",
] ]
[[package]] [[package]]
@ -427,6 +588,25 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "elliptic-curve"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
"digest 0.10.7",
"ff",
"generic-array",
"group",
"pkcs8",
"rand_core",
"sec1",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.33" version = "0.8.33"
@ -446,6 +626,16 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "ff"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
"rand_core",
"subtle",
]
[[package]] [[package]]
name = "flex-error" name = "flex-error"
version = "0.4.4" version = "0.4.4"
@ -568,6 +758,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
"zeroize",
] ]
[[package]] [[package]]
@ -589,6 +780,17 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core",
"subtle",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.21" version = "0.3.21"
@ -638,6 +840,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest 0.10.7",
]
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.9" version = "0.2.9"
@ -710,6 +921,27 @@ dependencies = [
"tokio-rustls", "tokio-rustls",
] ]
[[package]]
name = "ics23"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "661e2d6f79952a65bc92b1c81f639ebd37228dae6ff412a5aba7d474bdc4b957"
dependencies = [
"anyhow",
"bytes",
"hex",
"prost",
"ripemd",
"sha2 0.10.8",
"sha3",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.4.0" version = "0.4.0"
@ -766,6 +998,27 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "k256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b"
dependencies = [
"cfg-if 1.0.0",
"ecdsa",
"elliptic-curve",
"sha2 0.10.8",
]
[[package]]
name = "keccak"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940"
dependencies = [
"cpufeatures",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -1041,9 +1294,9 @@ dependencies = [
[[package]] [[package]]
name = "prost" name = "prost"
version = "0.12.1" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a"
dependencies = [ dependencies = [
"bytes", "bytes",
"prost-derive", "prost-derive",
@ -1051,9 +1304,9 @@ dependencies = [
[[package]] [[package]]
name = "prost-derive" name = "prost-derive"
version = "0.12.1" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools", "itertools",
@ -1085,6 +1338,9 @@ name = "rand_core"
version = "0.6.4" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
@ -1179,6 +1435,16 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "rfc6979"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [
"hmac",
"subtle",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.5" version = "0.17.5"
@ -1193,6 +1459,15 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "ripemd"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
dependencies = [
"digest 0.10.7",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -1282,6 +1557,20 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"generic-array",
"pkcs8",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.9.2" version = "2.9.2"
@ -1384,6 +1673,33 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_with"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
dependencies = [
"base64",
"chrono",
"hex",
"serde",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.39",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.9.9" version = "0.9.9"
@ -1408,6 +1724,16 @@ dependencies = [
"digest 0.10.7", "digest 0.10.7",
] ]
[[package]]
name = "sha3"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
dependencies = [
"digest 0.10.7",
"keccak",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@ -1431,6 +1757,10 @@ name = "signature"
version = "2.1.0" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
dependencies = [
"digest 0.10.7",
"rand_core",
]
[[package]] [[package]]
name = "slab" name = "slab"
@ -1571,10 +1901,12 @@ dependencies = [
"ed25519-consensus", "ed25519-consensus",
"flex-error", "flex-error",
"futures", "futures",
"k256",
"num-traits", "num-traits",
"once_cell", "once_cell",
"prost", "prost",
"prost-types", "prost-types",
"ripemd",
"serde", "serde",
"serde_bytes", "serde_bytes",
"serde_json", "serde_json",
@ -1793,8 +2125,12 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"color-eyre", "color-eyre",
"cosmrs",
"cw-proof",
"futures", "futures",
"serde",
"serde_json", "serde_json",
"serde_with",
"tendermint", "tendermint",
"tendermint-light-client", "tendermint-light-client",
"tendermint-light-client-detector", "tendermint-light-client-detector",
@ -2228,9 +2564,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.6.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
dependencies = [ dependencies = [
"zeroize_derive", "zeroize_derive",
] ]

View file

@ -6,15 +6,17 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
clap = { version = "4.1.8", features = ["derive"] }
color-eyre = "0.6.2"
cosmrs = "0.15.0"
cw-proof = { path = "../cw-proof" }
futures = "0.3.27"
serde = { version = "1.0.189", features = ["derive"] }
serde_json = "1.0.94"
tendermint = "0.34.0" tendermint = "0.34.0"
tendermint-rpc = { version = "0.34.0", features = ["http-client"] } tendermint-rpc = { version = "0.34.0", features = ["http-client"] }
tendermint-light-client = "0.34.0" tendermint-light-client = "0.34.0"
tendermint-light-client-detector = "0.34.0" tendermint-light-client-detector = "0.34.0"
clap = { version = "4.1.8", features = ["derive"] }
color-eyre = "0.6.2"
futures = "0.3.27"
serde_json = "1.0.94"
tokio = { version = "1.26.0", features = ["full"] } tokio = { version = "1.26.0", features = ["full"] }
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }

View file

@ -7,10 +7,11 @@ block height and trusted height/hash.
```bash ```bash
cargo run -- --chain-id osmosis-1 \ cargo run -- --chain-id osmosis-1 \
--primary https://rpc.osmosis.zone \ --primary "http://127.0.0.1:26657" \
--witnesses https://rpc.osmosis.zone \ --witnesses "http://127.0.0.1:26657" \
--trusted-height 12230413 \ --trusted-height 400 \
--trusted-hash D3742DD1573436AF972472135A24B31D5ACE9A2C04791A76196F875955B90F1D \ --trusted-hash "DEA1738C2AEE72E935E39CE6EB8765B8782B791038789AC2FEA446526FDE8638" \
--height 12230423 \ --contract-address "wasm17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgsm0v070" \
--storage-key "requests" \
--trace-file light-client-proof.json --trace-file light-client-proof.json
``` ```

File diff suppressed because one or more lines are too long

View file

@ -20,7 +20,12 @@ use color_eyre::{
eyre::{eyre, Result}, eyre::{eyre, Result},
Report, Report,
}; };
use cosmrs::AccountId;
use cw_proof::error::ProofError;
use cw_proof::proof::cw::RawCwProof;
use cw_proof::proof::{cw::CwProof, key::CwAbciKey, Proof};
use futures::future::join_all; use futures::future::join_all;
use serde::{Deserialize, Serialize};
use tendermint::{crypto::default::Sha256, evidence::Evidence}; use tendermint::{crypto::default::Sha256, evidence::Evidence};
use tendermint_light_client::{ use tendermint_light_client::{
builder::LightClientBuilder, builder::LightClientBuilder,
@ -29,10 +34,12 @@ use tendermint_light_client::{
types::{Hash, Height, LightBlock, TrustThreshold}, types::{Hash, Height, LightBlock, TrustThreshold},
}; };
use tendermint_light_client_detector::{detect_divergence, Error, Provider, Trace}; use tendermint_light_client_detector::{detect_divergence, Error, Provider, Trace};
use tendermint_rpc::{Client, HttpClient, HttpClientUrl}; use tendermint_rpc::{client::HttpClient, Client, HttpClientUrl};
use tracing::{error, info, metadata::LevelFilter}; use tracing::{error, info, metadata::LevelFilter};
use tracing_subscriber::{util::SubscriberInitExt, EnvFilter}; use tracing_subscriber::{util::SubscriberInitExt, EnvFilter};
const WASM_STORE_KEY: &str = "/store/wasm/key";
fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> { fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> {
if let Some((l, r)) = s.split_once('/') { if let Some((l, r)) = s.split_once('/') {
TrustThreshold::new(l.parse()?, r.parse()?).map_err(Into::into) TrustThreshold::new(l.parse()?, r.parse()?).map_err(Into::into)
@ -74,6 +81,12 @@ impl Verbosity {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
struct ProofOutput {
light_client_proof: Vec<LightBlock>,
merkle_proof: RawCwProof,
}
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
struct Cli { struct Cli {
@ -82,7 +95,7 @@ struct Cli {
chain_id: String, chain_id: String,
/// Primary RPC address /// Primary RPC address
#[clap(long)] #[clap(long, default_value = "http://127.0.0.1:26657")]
primary: HttpClientUrl, primary: HttpClientUrl,
/// Comma-separated list of witnesses RPC addresses /// Comma-separated list of witnesses RPC addresses
@ -97,10 +110,6 @@ struct Cli {
#[clap(long)] #[clap(long)]
trusted_hash: Hash, trusted_hash: Hash,
/// Height of the header to verify
#[clap(long)]
height: Option<Height>,
/// Trust threshold /// Trust threshold
#[clap(long, value_parser = parse_trust_threshold, default_value_t = TrustThreshold::TWO_THIRDS)] #[clap(long, value_parser = parse_trust_threshold, default_value_t = TrustThreshold::TWO_THIRDS)]
trust_threshold: TrustThreshold, trust_threshold: TrustThreshold,
@ -124,6 +133,14 @@ struct Cli {
/// Increase verbosity /// Increase verbosity
#[clap(flatten)] #[clap(flatten)]
verbose: Verbosity, verbose: Verbosity,
/// Address of the CosmWasm contract
#[clap(long)]
contract_address: AccountId,
/// Storage key of the state item for which proofs must be retrieved
#[clap(long)]
storage_key: String,
} }
#[tokio::main] #[tokio::main]
@ -150,23 +167,31 @@ async fn main() -> Result<()> {
let mut primary = make_provider( let mut primary = make_provider(
&args.chain_id, &args.chain_id,
args.primary, args.primary.clone(),
args.trusted_height, args.trusted_height,
args.trusted_hash, args.trusted_hash,
options, options,
) )
.await?; .await?;
let client = HttpClient::builder(args.primary.clone()).build()?;
let trusted_block = primary let trusted_block = primary
.latest_trusted() .latest_trusted()
.ok_or_else(|| eyre!("No trusted state found for primary"))?; .ok_or_else(|| eyre!("No trusted state found for primary"))?;
let primary_block = if let Some(target_height) = args.height { let primary_block = {
info!("Verifying to height {} on primary...", target_height);
primary.verify_to_height(target_height)
} else {
info!("Verifying to latest height on primary..."); info!("Verifying to latest height on primary...");
primary.verify_to_highest()
let status = client.status().await?;
let proof_height = {
let latest_height = status.sync_info.latest_block_height;
(latest_height.value() - 1)
.try_into()
.expect("infallible conversion")
};
primary.verify_to_height(proof_height)
}?; }?;
info!("Verified to height {} on primary", primary_block.height()); info!("Verified to height {} on primary", primary_block.height());
@ -197,14 +222,36 @@ async fn main() -> Result<()> {
) )
.await?; .await?;
let status = client.status().await?;
let (proof_height, latest_app_hash) =
(primary_block.height(), status.sync_info.latest_app_hash);
let path = WASM_STORE_KEY.to_owned();
let data = CwAbciKey::new(args.contract_address, args.storage_key, None);
let result = client
.abci_query(Some(path), data, Some(proof_height), true)
.await?;
let proof: CwProof = result
.clone()
.try_into()
.expect("result should contain proof");
proof
.verify(latest_app_hash.clone().into())
.map_err(|e: ProofError| eyre!(e))?;
if let Some(trace_file) = args.trace_file { if let Some(trace_file) = args.trace_file {
write_trace_to_file(trace_file, primary_trace).await?; let output = ProofOutput {
light_client_proof: primary_trace,
merkle_proof: proof.into(),
};
write_proof_to_file(trace_file, output).await?;
}; };
Ok(()) Ok(())
} }
async fn write_trace_to_file(trace_file: PathBuf, output: Vec<LightBlock>) -> Result<()> { async fn write_proof_to_file(trace_file: PathBuf, output: ProofOutput) -> Result<()> {
info!("Writing proof to output file ({})", trace_file.display()); info!("Writing proof to output file ({})", trace_file.display());
let file = File::create(trace_file)?; let file = File::create(trace_file)?;

View file

@ -0,0 +1,2 @@
Cargo.lock

View file

@ -0,0 +1,9 @@
[package]
name = "tm-stateless-verifier"
version = "0.1.0"
edition = "2021"
[dependencies]
displaydoc = { version = "0.2.4", default-features = false }
tendermint = { version = "=0.34.0", default-features = false }
tendermint-light-client = { version = "=0.34.0", default-features = false, features = ["rust-crypto"] }

View file

@ -0,0 +1,34 @@
use alloc::boxed::Box;
use displaydoc::Display;
use tendermint::{block::Height, Hash};
use tendermint_light_client::{
builder::error::Error as TmBuilderError, errors::Error as LightClientError,
};
#[derive(Debug, Display)]
pub enum Error {
/// empty trace
EmptyTrace,
/// first block in trace does not match trusted (expected {expected:?}, found {found:?})
FirstTraceBlockNotTrusted {
expected: (Height, Hash),
found: (Height, Hash),
},
/// verification failure (`{0}`)
VerificationFailure(Box<LightClientError>),
/// failed to build light client (`{0}`)
LightClientBuildFailure(Box<TmBuilderError>),
}
impl From<LightClientError> for Error {
fn from(e: LightClientError) -> Self {
Error::VerificationFailure(Box::new(e))
}
}
impl From<TmBuilderError> for Error {
fn from(e: TmBuilderError) -> Self {
Error::LightClientBuildFailure(Box::new(e))
}
}

View file

@ -0,0 +1,24 @@
#![no_std]
#![forbid(unsafe_code)]
#![warn(
clippy::checked_conversions,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
trivial_casts,
trivial_numeric_casts,
rust_2018_idioms,
unused_lifetimes,
unused_import_braces,
unused_qualifications
)]
extern crate alloc;
mod error;
mod null_io;
mod provider;
pub use error::Error;
pub use provider::make_provider;
pub use provider::StatelessProvider;

View file

@ -0,0 +1,13 @@
use tendermint_light_client::{
components::io::{AtHeight, Io, IoError},
types::LightBlock,
};
#[derive(Clone, Debug)]
pub struct NullIo;
impl Io for NullIo {
fn fetch_light_block(&self, _height: AtHeight) -> Result<LightBlock, IoError> {
unimplemented!("stateless verification does NOT need access to Io")
}
}

View file

@ -0,0 +1,97 @@
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use tendermint::Hash;
use tendermint_light_client::{
builder::LightClientBuilder,
components::{clock::SystemClock, scheduler},
instance::Instance,
light_client::Options,
predicates::ProdPredicates,
store::{memory::MemoryStore, LightStore},
types::{Height, LightBlock, Status},
verifier::ProdVerifier,
};
use crate::error::Error;
use crate::null_io::NullIo;
/// A interface over a stateless light client instance.
#[derive(Debug)]
pub struct StatelessProvider {
#[allow(unused)]
chain_id: String,
instance: Instance,
}
impl StatelessProvider {
pub fn new(chain_id: String, instance: Instance) -> Self {
Self { chain_id, instance }
}
pub fn verify_to_height(&mut self, height: Height) -> Result<LightBlock, Error> {
self.instance
.light_client
.verify_to_target(height, &mut self.instance.state)
.map_err(Into::<Error>::into)
}
}
pub fn make_provider(
chain_id: &str,
trusted_height: Height,
trusted_hash: Hash,
trace: Vec<LightBlock>,
options: Options,
) -> Result<StatelessProvider, Error> {
// Make sure the trace is not empty and that the first light block corresponds to trusted
verify_trace_against_trusted(&trace, trusted_height, trusted_hash)?;
let mut light_store = Box::new(MemoryStore::new());
for light_block in &trace {
light_store.insert(light_block.clone(), Status::Unverified);
}
let node_id = trace[0].provider;
let instance = LightClientBuilder::custom(
node_id,
options,
light_store,
Box::new(NullIo {}),
Box::new(SystemClock),
#[allow(clippy::box_default)]
Box::new(ProdVerifier::default()),
Box::new(scheduler::basic_bisecting_schedule),
Box::new(ProdPredicates),
)
.trust_light_block(trace[0].clone())
.map_err(Into::<Error>::into)?
.build();
Ok(StatelessProvider::new(chain_id.to_string(), instance))
}
fn verify_trace_against_trusted(
trace: &[LightBlock],
trusted_height: Height,
trusted_hash: Hash,
) -> Result<(), Error> {
let Some(first_block) = trace.first() else {
return Err(Error::EmptyTrace);
};
let first_height = first_block.signed_header.header.height;
let first_hash = first_block.signed_header.header.hash();
if first_height != trusted_height || first_hash != trusted_hash {
return Err(Error::FirstTraceBlockNotTrusted {
expected: (first_height, first_hash),
found: (trusted_height, trusted_hash),
});
}
Ok(())
}