diff --git a/.gitignore b/.gitignore index 02771166..c6170f88 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ **/node_modules/ docs/build/ + +**/.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 8509dae4..0bb34824 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,22 +27,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "alloy-primitives" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600d34d8de81e23b6d909c094e23b3d357e01ca36b78a8c5424c501eedbe86f0" -dependencies = [ - "bytes", - "cfg-if 1.0.0", - "const-hex", - "derive_more", - "hex-literal", - "itoa", - "ruint", - "tiny-keccak", -] - [[package]] name = "alloy-sol-macro" version = "0.3.1" @@ -64,7 +48,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" dependencies = [ - "alloy-primitives 0.3.3", + "alloy-primitives", "alloy-sol-macro", "const-hex", ] @@ -125,7 +109,7 @@ dependencies = [ name = "contracts" version = "0.1.0" dependencies = [ - "alloy-primitives 0.3.3", + "alloy-primitives", "alloy-sol-types", "cfg-if 1.0.0", "derive_more", @@ -171,9 +155,10 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" name = "crypto" version = "0.1.0" dependencies = [ - "alloy-primitives 0.6.4", - "const-hex", + "hex-literal", + "mini-alloc", "rand", + "tiny-keccak", ] [[package]] @@ -230,7 +215,7 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" name = "erc20-example" version = "0.0.0" dependencies = [ - "alloy-primitives 0.3.3", + "alloy-primitives", "contracts", "mini-alloc", "stylus-proc", @@ -346,6 +331,18 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +[[package]] +name = "merkle-proofs-example" +version = "0.0.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "crypto", + "mini-alloc", + "stylus-proc", + "stylus-sdk", +] + [[package]] name = "mini-alloc" version = "0.4.2" @@ -549,7 +546,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "366c3323b03fc99c7d0124cec4eee10e37a226a14b08829a416feec9f2ebb641" dependencies = [ - "alloy-primitives 0.3.3", + "alloy-primitives", "alloy-sol-types", "cfg-if 1.0.0", "convert_case 0.6.0", @@ -568,7 +565,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74778bf11048b0155b937ee6d52440709d864aa488843238b62272e6c9bdbc1a" dependencies = [ - "alloy-primitives 0.3.3", + "alloy-primitives", "alloy-sol-types", "cfg-if 1.0.0", "derivative", diff --git a/Cargo.toml b/Cargo.toml index 5c0d0c9b..68132029 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "lib/grip", "lib/grip-proc", "examples/erc20", + "examples/merkle-proofs", ] # Explicitly set the resolver to version 2, which is the default for packages # with edition >= 2021. diff --git a/examples/merkle-proofs/Cargo.toml b/examples/merkle-proofs/Cargo.toml new file mode 100644 index 00000000..923bcba1 --- /dev/null +++ b/examples/merkle-proofs/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "merkle-proofs-example" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version = "0.0.0" + +[dependencies] +crypto = { path = "../../lib/crypto", features = ["merkle", "multi-proof"] } +alloy-primitives.workspace = true +alloy-sol-types.workspace = true +stylus-sdk.workspace = true +stylus-proc.workspace = true +mini-alloc.workspace = true + +[features] +default = [] + +# Enables using the standard library. This is not included in the default +# features, because this crate is meant to be used in a `no_std` environment. +# Currently, the std feature is only used for testing purposes. +std = [] + +[lib] +crate-type = ["lib", "cdylib"] diff --git a/examples/merkle-proofs/src/lib.rs b/examples/merkle-proofs/src/lib.rs new file mode 100644 index 00000000..19641b79 --- /dev/null +++ b/examples/merkle-proofs/src/lib.rs @@ -0,0 +1,91 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +extern crate alloc; + +use alloc::vec::Vec; + +use alloy_primitives::B256; +use crypto::{ + merkle::{self, Verifier}, + KeccakBuilder, +}; +use stylus_proc::SolidityError; +use stylus_sdk::{ + alloy_sol_types::sol, + prelude::{entrypoint, external, sol_storage}, +}; + +sol! { + error MerkleProofInvalidMultiProofLength(); + error MerkleProofInvalidRootChild(); + error MerkleProofInvalidTotalHashes(); +} + +#[global_allocator] +static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; + +#[cfg(not(any( + test, + feature = "std", + target_arch = "wasm32-unknown-unknown" +)))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[derive(SolidityError)] +pub enum VerifierError { + InvalidProofLength(MerkleProofInvalidMultiProofLength), + InvalidRootChild(MerkleProofInvalidRootChild), + InvalidTotalHashes(MerkleProofInvalidTotalHashes), +} + +impl core::convert::From for VerifierError { + fn from(value: merkle::MultiProofError) -> Self { + match value { + merkle::MultiProofError::InvalidProofLength => { + VerifierError::InvalidProofLength( + MerkleProofInvalidMultiProofLength {}, + ) + } + merkle::MultiProofError::InvalidRootChild => { + VerifierError::InvalidRootChild(MerkleProofInvalidRootChild {}) + } + merkle::MultiProofError::InvalidTotalHashes => { + VerifierError::InvalidTotalHashes( + MerkleProofInvalidTotalHashes {}, + ) + } + } + } +} + +sol_storage! { + #[entrypoint] + struct VerifierContract { } +} + +#[external] +impl VerifierContract { + pub fn verify(&self, proof: Vec, root: B256, leaf: B256) -> bool { + let proof: Vec<[u8; 32]> = proof.into_iter().map(|m| *m).collect(); + Verifier::::verify(&proof, *root, *leaf) + } + + pub fn verify_multi_proof( + &self, + proof: Vec, + proof_flags: Vec, + root: B256, + leaves: Vec, + ) -> Result { + let proof: Vec<[u8; 32]> = proof.into_iter().map(|m| *m).collect(); + let leaves: Vec<[u8; 32]> = leaves.into_iter().map(|m| *m).collect(); + Ok(Verifier::::verify_multi_proof( + &proof, + &proof_flags, + *root, + &leaves, + )?) + } +} diff --git a/lib/crypto/Cargo.toml b/lib/crypto/Cargo.toml index 451a7126..76029268 100644 --- a/lib/crypto/Cargo.toml +++ b/lib/crypto/Cargo.toml @@ -8,12 +8,16 @@ license.workspace = true repository.workspace = true version = "0.1.0" +[dependencies] +mini-alloc.workspace = true +tiny-keccak = { version = "2.0.2", features = ["keccak"] } + [dev-dependencies] -alloy-primitives = { version = "0.6.4", default-features = false } -const-hex = { version = "1.11.1", default-features = false } +hex-literal = "0.4.1" rand = "0.8.5" [features] -default = [] +std = [] + merkle = [] -multi_proof = ["merkle"] +multi-proof = ["merkle"] diff --git a/lib/crypto/src/hash.rs b/lib/crypto/src/hash.rs new file mode 100644 index 00000000..a22d147e --- /dev/null +++ b/lib/crypto/src/hash.rs @@ -0,0 +1,192 @@ +//! Generic hashing support. +//! +//! This module provides a generic way to compute the [hash] of a value. It is +//! intended to be used as a replacement for [`core::hash`], which we can't use +//! because [`core::hash::Hasher::finish`] returns a `u64`. +//! +//! [hash]: https://en.wikipedia.org/wiki/Hash_function + +/// A hashable type. +/// +/// Types implementing `Hash` are able to be [`Hash::hash`]ed with an instance +/// of [`Hasher`]. +pub trait Hash { + /// Feeds this value into the given [`Hasher`]. + fn hash(&self, state: &mut H); +} + +/// A trait for hashing an arbitrary stream of bytes. +/// +/// Instances of `Hasher` usually represent state that is changed while hashing +/// data. +/// +/// `Hasher` provides a fairly basic interface for retrieving the generated hash +/// (with [`Hasher::finalize`]), and absorbing an arbitrary number of bytes +/// (with [`Hasher::update`]). Most of the time, [`Hasher`] instances are used +/// in conjunction with the [`Hash`] trait. +pub trait Hasher { + /// The output type of this hasher. + /// + /// For [`core::hash`] types, it's `u64`. For [`tiny_keccak`], it's `[u8]`. + /// For this crate, it's `[u8; 32]`. + type Output; + + /// Absorb additional input. Can be called multiple times. + fn update(&mut self, input: impl AsRef<[u8]>); + + /// Output the hashing algorithm state. + fn finalize(self) -> Self::Output; +} + +/// A trait for creating instances of [`Hasher`]. +/// +/// A `BuildHasher` is typically used (e.g., by [`HashMap`]) to create +/// [`Hasher`]s for each key such that they are hashed independently of one +/// another, since [`Hasher`]s contain state. +/// +/// For each instance of `BuildHasher`, the [`Hasher`]s created by +/// [`build_hasher`] should be identical. That is, if the same stream of bytes +/// is fed into each hasher, the same output will also be generated. +/// +/// # Examples +/// +/// ```rust +/// use crypto::KeccakBuilder; +/// use crypto::hash::{BuildHasher, Hash, Hasher}; +/// +/// let b = KeccakBuilder; +/// let mut hasher_1 = b.build_hasher(); +/// let mut hasher_2 = b.build_hasher(); +/// +/// hasher_1.update([1]); +/// hasher_2.update([1]); +/// +/// assert_eq!(hasher_1.finalize(), hasher_2.finalize()); +/// ``` +/// +/// [`build_hasher`]: BuildHasher::build_hasher +/// [`HashMap`]: ../../std/collections/struct.HashMap.html +pub trait BuildHasher { + /// Type of the hasher that will be created. + type Hasher: Hasher; + + /// Creates a new hasher. + /// + /// Each call to `build_hasher` on the same instance should produce + /// identical [`Hasher`]s. + /// + /// # Examples + /// + /// ```rust + /// use crypto::KeccakBuilder; + /// use crypto::hash::BuildHasher; + /// + /// let b = KeccakBuilder; + /// let hasher = b.build_hasher(); + /// ``` + fn build_hasher(&self) -> Self::Hasher; + + /// Calculates the hash of a single value. + /// + /// This is intended as a convenience for code which *consumes* hashes, such + /// as the implementation of a hash table or in unit tests that check + /// whether a custom [`Hash`] implementation behaves as expected. + /// + /// This must not be used in any code which *creates* hashes, such as in an + /// implementation of [`Hash`]. The way to create a combined hash of + /// multiple values is to call [`Hash::hash`] multiple times using the same + /// [`Hasher`], not to call this method repeatedly and combine the results. + /// + /// # Examples + /// + /// ```rust + /// use crypto::KeccakBuilder; + /// use crypto::hash::{BuildHasher, Hash}; + /// + /// let b = KeccakBuilder; + /// let hash_1 = b.hash_one([0u8; 32]); + /// let hash_2 = b.hash_one([0u8; 32]); + /// assert_eq!(hash_1, hash_2); + /// + /// let hash_1 = b.hash_one([1u8; 32]); + /// assert_ne!(hash_1, hash_2); + /// ``` + fn hash_one( + &self, + h: Hashable, + ) -> ::Output + where + Hashable: Hash, + Self: Sized, + Self::Hasher: Hasher, + { + let mut hasher = self.build_hasher(); + h.hash(&mut hasher); + hasher.finalize() + } +} + +/// Hash the pair `(a, b)` with `state`. +#[allow(clippy::module_name_repetitions)] +#[inline] +pub fn hash_pair(a: &H, b: &H, mut state: S) -> S::Output +where + H: Hash + ?Sized, + S: Hasher, +{ + a.hash(&mut state); + b.hash(&mut state); + state.finalize() +} + +/// Sort the pair `(a, b)` and hash the result with `state`. Frequently used +/// when working with merkle proofs. +#[inline] +pub fn commutative_hash_pair(mut a: H, mut b: H, state: S) -> S::Output +where + H: Hash + PartialOrd, + S: Hasher, +{ + if a > b { + core::mem::swap(&mut a, &mut b); + } + + hash_pair(&a, &b, state) +} + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::{commutative_hash_pair, hash_pair, BuildHasher, Hash, Hasher}; + use crate::KeccakBuilder; + + impl Hash for &[u8] { + fn hash(&self, state: &mut H) { + state.update(self); + } + } + + #[test] + fn hashes_pairs() { + let builder = KeccakBuilder; + let a = [1u8].as_slice(); + let b = [2u8].as_slice(); + + let r1 = hash_pair(&a, &b, builder.build_hasher()); + let r2 = hash_pair(&a, &b, builder.build_hasher()); + assert_eq!(r1, r2); + + let r3 = hash_pair(&b, &a, builder.build_hasher()); + assert_ne!(r1, r3); + } + + #[test] + fn commutatively_hashes_pairs() { + let builder = KeccakBuilder; + let a = [1u8].as_slice(); + let b = [2u8].as_slice(); + + let r1 = commutative_hash_pair(a, b, builder.build_hasher()); + let r2 = commutative_hash_pair(b, a, builder.build_hasher()); + assert_eq!(r1, r2); + } +} diff --git a/lib/crypto/src/keccak.rs b/lib/crypto/src/keccak.rs new file mode 100644 index 00000000..136d4afb --- /dev/null +++ b/lib/crypto/src/keccak.rs @@ -0,0 +1,49 @@ +//! An interface to the default hashing algorithm used in this library's [merkle +//! proofs][crate]. +use tiny_keccak::{Hasher as TinyHasher, Keccak}; + +use crate::hash::{BuildHasher, Hash, Hasher}; + +/// The default [`Hasher`] builder used in this library's [merkle +/// proofs][crate]. +/// +/// It instantiates a [`Keccak256`] hasher. +#[allow(clippy::module_name_repetitions)] +pub struct KeccakBuilder; + +impl BuildHasher for KeccakBuilder { + type Hasher = Keccak256; + + #[inline] + fn build_hasher(&self) -> Self::Hasher { + Keccak256(Keccak::v256()) + } +} + +/// The default [`Hasher`] used in this library's [merkle proofs][crate]. +/// +/// The underlying implementation is guaranteed to match that of the +/// `keccak256` algorithm, commonly used in Ethereum. +#[allow(clippy::module_name_repetitions)] +pub struct Keccak256(Keccak); + +impl Hasher for Keccak256 { + type Output = [u8; 32]; + + fn update(&mut self, input: impl AsRef<[u8]>) { + self.0.update(input.as_ref()); + } + + fn finalize(self) -> Self::Output { + let mut buffer = [0u8; 32]; + self.0.finalize(&mut buffer); + buffer + } +} + +impl Hash for [u8; 32] { + #[inline] + fn hash(&self, state: &mut H) { + state.update(self); + } +} diff --git a/lib/crypto/src/lib.rs b/lib/crypto/src/lib.rs index 3203bf60..75fbad7b 100644 --- a/lib/crypto/src/lib.rs +++ b/lib/crypto/src/lib.rs @@ -1,8 +1,12 @@ #![doc = include_str!("../README.md")] #![warn(missing_docs, unreachable_pub, rust_2021_compatibility)] #![warn(clippy::all, clippy::pedantic)] -#![cfg_attr(not(test), no_main, no_std)] +#![cfg_attr(not(feature = "std"), no_std, no_main)] extern crate alloc; -#[cfg(feature = "merkle")] +pub mod hash; +#[cfg(any(feature = "std", feature = "merkle"))] pub mod merkle; + +pub mod keccak; +pub use keccak::KeccakBuilder; diff --git a/lib/crypto/src/merkle.rs b/lib/crypto/src/merkle.rs index 6fa915ba..b841d265 100644 --- a/lib/crypto/src/merkle.rs +++ b/lib/crypto/src/merkle.rs @@ -1,86 +1,321 @@ //! This module deals with verification of Merkle Tree proofs. //! -//! The tree and the proofs can be generated using OpenZeppelin's -//! . -//! You will find a quickstart guide in its README. +//! The tree and the proofs can be generated using `OpenZeppelin`'s [merkle +//! tree library][https://github.com/OpenZeppelin/merkle-tree]. You will find a +//! quickstart guide in its README. //! //! WARNING: You should avoid using leaf values that are 64 bytes long //! prior to hashing, or use a hash function other than keccak256 for //! hashing leaves. This is because the concatenation of a sorted pair //! of internal nodes in the Merkle tree could be reinterpreted as a -//! leaf value. OpenZeppelin's JavaScript library generates Merkle trees +//! leaf value. `OpenZeppelin`'s JavaScript library generates Merkle trees //! that are safe against this attack out of the box. use alloc::vec::Vec; +use core::marker::PhantomData; + +use crate::{ + hash::{commutative_hash_pair, BuildHasher, Hasher}, + KeccakBuilder, +}; type Bytes32 = [u8; 32]; -/// A common interface to represent an arbitrary hasher. -/// -/// `Hasher` serves as an adapter for consumers to use `verify` with the -/// hashing algorithm of their choice. -pub trait Hasher { - /// The type of a value resulting from hashing some data. - type Hash; - /// Hash arbitrary data resulting in a value of type `Self::Hash`. - fn hash>(&mut self, data: A) -> Self::Hash; +/// Verify merkle proofs. +pub struct Verifier(PhantomData) +where + B: BuildHasher; + +impl Verifier { + /// Verify that `leaf` is part of a Merkle tree defined by `root` by using + /// `proof` and the default `keccak256` hashing algorithm. + /// + /// A new root is rebuilt by traversing up the Merkle tree. The `proof` + /// provided must contain sibling hashes on the branch starting from the + /// leaf to the root of the tree. Each pair of leaves and each pair of + /// pre-images are assumed to be sorted. + /// + /// A `proof` is valid if and only if the rebuilt hash matches the root + /// of the tree. + /// + /// # Arguments + /// + /// * `proof` - A slice of hashes that constitute the merkle proof. + /// * `root` - The root of the merkle tree, in bytes. + /// * `leaf` - The leaf of the merkle tree to proof, in bytes. + /// + /// # Examples + /// + /// ``` + /// use crypto::merkle::Verifier; + /// use hex_literal::hex; + /// + /// let root = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// let leaf = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// let proof = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// + /// let verification = Verifier::verify(&[proof], root, leaf); + /// assert!(!verification); + /// ``` + #[must_use] + pub fn verify(proof: &[Bytes32], root: Bytes32, leaf: Bytes32) -> bool { + Verifier::verify_with_builder(proof, root, leaf, &KeccakBuilder) + } + + /// Verify multiple `leaves` can be simultaneously proven to be a part of + /// a Merkle tree defined by `root` by using a `proof` with `proof_flags` + /// and a `hasher`. + /// + /// The `proof` must contain the sibling hashes one would need to rebuild + /// the root starting from `leaves`. `proof_flags` represents whether a + /// hash must be computed using a `proof` member. A new root is rebuilt by + /// starting from the `leaves` and traversing up the Merkle tree. + /// + /// The procedure incrementally reconstructs all inner nodes by combining + /// a leaf/inner node with either another leaf/inner node or a `proof` + /// sibling node, depending on each proof flag being true or false + /// respectively, i.e., the `i`-th hash must be computed using the proof if + /// `proof_flags[i] == false`. + /// + /// CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, + /// it is sufficient to ensure that: + /// - The tree is complete (but not necessarily perfect). + /// - The leaves to be proven are in the opposite order they appear in + /// the tree (i.e., as seen from right to left starting at the deepest + /// layer and continuing at the next layer). + /// + /// NOTE: This implementation is *not* equivalent to it's Solidity + /// counterpart. In Rust, access to uninitialized memory panics, which + /// means we don't need to check that the whole proof array has been + /// processed. Both implementations will revert for the same inputs, but + /// for different reasons. See + /// + /// # Arguments + /// + /// * `proof` - A slice of hashes that constitute the merkle proof. + /// * `proof_flags` - A slice of booleans that determine whether to hash + /// leaves + /// or the proof. + /// * `root` - The root of the merkle tree, in bytes. + /// * `leaves` - A slice of hashes that constitute the leaves of the merkle + /// tree to be proven, each leaf in bytes. + /// + /// # Errors + /// + /// Will return `Err` if the arguments are well-formed, but invalid. + /// + /// # Panics + /// + /// Will panic with an out-of-bounds error if the proof is malicious. See + /// + /// + /// # Examples + /// + /// ```rust + /// use crypto::merkle::Verifier; + /// use hex_literal::hex; + /// + /// let root = hex!("6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"); + /// let leaves = [hex!("19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"), + /// hex!("c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848"), + /// hex!("eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b")]; + /// let proof = [hex!("9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"), + /// hex!("8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f")]; + /// + /// let proof_flags = [false, true, false, true]; + /// + /// let verification = + /// Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); + /// assert!(verification.unwrap()); + /// ``` + #[cfg(feature = "multi-proof")] + pub fn verify_multi_proof( + proof: &[Bytes32], + proof_flags: &[bool], + root: Bytes32, + leaves: &[Bytes32], + ) -> Result { + Verifier::verify_multi_proof_with_builder( + proof, + proof_flags, + root, + leaves, + &KeccakBuilder, + ) + } } -/// Verify that `leaf` is part of a Merkle tree defined by `root` by using -/// `proof` and a `hasher`. -/// -/// A new root is rebuilt by traversing up the Merkle tree. The `proof` -/// provided must contain sibling hashes on the branch starting from the -/// leaf to the root of the tree. Each pair of leaves and each pair of -/// pre-images are assumed to be sorted. -/// -/// A `proof` is valid if and only if the rebuilt hash matches the root -/// of the tree. -/// -/// # Arguments -/// -/// * `proof` - A slice of hashes that constitute the merkle proof. -/// * `root` - The root of the merkle tree, in bytes. -/// * `leaf` - The leaf of the merkle tree to proof, in bytes. -/// * `hasher` - The hashing algorithm to use. -/// -/// # Examples -/// -/// ``` -/// # use const_hex::FromHex; -/// # use alloy_primitives::keccak256; -/// # use crypto::merkle::verify; -/// # struct Keccak256; -/// # impl crypto::merkle::Hasher for Keccak256 { -/// # type Hash = Bytes32; -/// # fn hash>(&mut self, data: A) -> Self::Hash { -/// # let bytes = data.as_ref(); -/// # *keccak256(bytes) -/// # } -/// # } -/// type Bytes32 = [u8; 32]; -/// -/// const ROOT: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; -/// const LEAF: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; -/// const PROOF: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; -/// -/// let root = Bytes32::from_hex(ROOT).unwrap(); -/// let leaf = Bytes32::from_hex(LEAF).unwrap(); -/// let proof = Bytes32::from_hex(PROOF).unwrap(); -/// -/// let verification = verify(&[proof], root, leaf, Keccak256); -/// assert!(!verification); -/// ``` -pub fn verify>( - proof: &[Bytes32], - root: Bytes32, - mut leaf: Bytes32, - mut hasher: H, -) -> bool { - for &hash in proof { - leaf = sorted_hash(leaf, hash, &mut hasher); +impl Verifier +where + B: BuildHasher, + B::Hasher: Hasher, +{ + /// Verify that `leaf` is part of a Merkle tree defined by `root` by using + /// `proof` and a custom hashing algorithm defined by `builder`. See + /// [`BuildHasher`] for more information on how to construct a builder. + /// + /// WARNING: This is a lower-level function. For most use cases, + /// [`Verifier::verify`], which uses `keccak256` as a hashing algorithm, + /// should be enough. Using other hashing algorithm may have unexpected + /// results. + /// + /// # Arguments + /// + /// * `proof` - A slice of hashes that constitute the merkle proof. + /// * `root` - The root of the merkle tree, in bytes. + /// * `leaf` - The leaf of the merkle tree to proof, in bytes. + /// * `builder` - A [`BuildHasher`] that represents a hashing algorithm. + /// + /// # Examples + /// + /// ``` + /// use crypto::{merkle::Verifier, KeccakBuilder}; + /// use hex_literal::hex; + /// + /// let root = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// let leaf = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// let proof = hex!("0000000000000000000000000000000000000000000000000000000000000000"); + /// + /// let verification = Verifier::verify_with_builder(&[proof], root, leaf, &KeccakBuilder); + /// assert!(!verification); + /// ``` + pub fn verify_with_builder( + proof: &[Bytes32], + root: Bytes32, + mut leaf: Bytes32, + builder: &B, + ) -> bool { + for &hash in proof { + leaf = commutative_hash_pair(leaf, hash, builder.build_hasher()); + } + + leaf == root } - leaf == root + /// Verify multiple `leaves` can be simultaneously proven to be a part of + /// a Merkle tree defined by `root` by using a `proof` with `proof_flags` + /// and a custom hashing algorithm defined by `builder`. See + /// [`BuildHasher`] for more information on how to construct a builder. + /// + /// WARNING: This is a lower-level function. For most use cases, + /// [`Verifier::verify_multi_proof`], which uses `keccak256` as a hashing + /// algorithm, should be enough. Using other hashing algorithm may have + /// unexpected results. + /// + /// The `proof` must contain the sibling hashes one would need to rebuild + /// the root starting from `leaves`. `proof_flags` represents whether a + /// hash must be computed using a `proof` member. A new root is rebuilt by + /// starting from the `leaves` and traversing up the Merkle tree. + /// + /// The procedure incrementally reconstructs all inner nodes by combining + /// a leaf/inner node with either another leaf/inner node or a `proof` + /// sibling node, depending on each proof flag being true or false + /// respectively, i.e., the `i`-th hash must be computed using the proof if + /// `proof_flags[i] == false`. + /// + /// CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, + /// it is sufficient to ensure that: + /// - The tree is complete (but not necessarily perfect). + /// - The leaves to be proven are in the opposite order they appear in + /// the tree (i.e., as seen from right to left starting at the deepest + /// layer and continuing at the next layer). + /// + /// NOTE: This implementation is *not* equivalent to it's Solidity + /// counterpart. In Rust, access to uninitialized memory panics, which + /// means we don't need to check that the whole proof array has been + /// processed. Both implementations will revert for the same inputs, but + /// for different reasons. See + /// + /// # Arguments + /// + /// * `proof` - A slice of hashes that constitute the merkle proof. + /// * `proof_flags` - A slice of booleans that determine whether to hash + /// leaves + /// or the proof. + /// * `root` - The root of the merkle tree, in bytes. + /// * `leaves` - A slice of hashes that constitute the leaves of the merkle + /// tree to be proven, each leaf in bytes. + /// * `builder` - A [`BuildHasher`] that represents a hashing algorithm. + /// + /// # Errors + /// + /// Will return `Err` if the arguments are well-formed, but invalid. + /// + /// # Examples + /// + /// ```rust + /// use crypto::{merkle::Verifier, KeccakBuilder}; + /// use hex_literal::hex; + /// + /// let root = hex!("6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"); + /// let leaves = [hex!("19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"), + /// hex!("c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848"), + /// hex!("eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b")]; + /// let proof = [hex!("9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"), + /// hex!("8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f")]; + /// let proof_flags = [false, true, false, true]; + /// + /// let verification = + /// Verifier::verify_multi_proof_with_builder(&proof, &proof_flags, root, &leaves, &KeccakBuilder); + /// assert!(verification.unwrap()); + /// ``` + #[cfg(feature = "multi-proof")] + pub fn verify_multi_proof_with_builder( + proof: &[Bytes32], + proof_flags: &[bool], + root: Bytes32, + leaves: &[Bytes32], + builder: &B, + ) -> Result { + let total_hashes = proof_flags.len(); + if leaves.len() + proof.len() != total_hashes + 1 { + return Err(MultiProofError::InvalidTotalHashes); + } + if total_hashes == 0 { + // We can safely assume that either `leaves` or `proof` is not empty + // given the previous check. We use `unwrap_or_else` to avoid + // eagerly evaluating `proof[0]`, which may panic. + let rebuilt_root = *leaves.first().unwrap_or_else(|| &proof[0]); + return Ok(root == rebuilt_root); + } + + // `hashes` represents a queue of hashes, our "main queue". + let mut hashes = Vec::with_capacity(total_hashes + leaves.len()); + // Which initially gets populated with the leaves. + hashes.extend(leaves); + // The `xxx_pos` values are "pointers" to the next value to consume in + // each queue. We use them to mimic a queue's pop operation. + let mut proof_pos = 0; + let mut hashes_pos = 0; + // At each step, we compute the next hash using two values: + // - A value from the "main queue". Consume all the leaves, then all the + // hashes but the root. + // - A value from the "main queue" (merging branches) or a member of the + // `proof`, depending on `flag`. + for &flag in proof_flags { + let a = hashes[hashes_pos]; + hashes_pos += 1; + + let b; + if flag { + b = hashes + .get(hashes_pos) + .ok_or(MultiProofError::InvalidRootChild)?; + hashes_pos += 1; + } else { + b = proof + .get(proof_pos) + .ok_or(MultiProofError::InvalidProofLength)?; + proof_pos += 1; + }; + + let hash = commutative_hash_pair(a, *b, builder.build_hasher()); + hashes.push(hash); + } + + // We know that `total_hashes > 0`. + let rebuilt_root = hashes[total_hashes + leaves.len() - 1]; + Ok(root == rebuilt_root) + } } /// An error that occurred while verifying a multi-proof. @@ -89,212 +324,65 @@ pub fn verify>( /// we should derive `core::error::Error`. #[derive(core::fmt::Debug)] pub enum MultiProofError { - /// The number of leaves and proof members does not match the amount of - /// hashes necessary to complete the verification. + /// The proof length does not match the flags. InvalidProofLength, + /// Tried to access uninitialized memory. + /// + /// This happens when the proof is too long, which makes the verification + /// procedure try to access uninitialized memory, which may result in an + /// invalid root. + /// + /// For more information see [this vulnerability]. + /// + /// [this vulnerability]: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-wprv-93r4-jj2p + InvalidRootChild, + /// The number of leaves and proof members does not match the number of + /// hashes necessary to complete the verification. + InvalidTotalHashes, } impl core::fmt::Display for MultiProofError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let msg = match self { MultiProofError::InvalidProofLength => "invalid multi-proof length", + MultiProofError::InvalidRootChild => "invalid root child generated", + MultiProofError::InvalidTotalHashes => { + "leaves.len() + proof.len() != total_hashes + 1" + } }; write!(f, "{msg}") } } -/// Verify multiple `leaves` can be simultaneously proven to be a part of -/// a Merkle tree defined by `root` by using a `proof` with `proof_flags` -/// and a `hasher`. -/// -/// The `proof` must contain the sibling hashes one would need to rebuild -/// the root starting from `leaves`. `proof_flags` represents whether a -/// hash must be computed using a `proof` member. A new root is rebuilt by -/// starting from the `leaves` and traversing up the Merkle tree. -/// -/// The procedure incrementally reconstructs all inner nodes by combining -/// a leaf/inner node with either another leaf/inner node or a `proof` -/// sibling node, depending on each proof flag being true or false -/// respectively, i.e., the `i`-th hash must be computed using the proof if -/// `proof_flags[i] == false`. -/// -/// CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, -/// it is sufficient to ensure that: -/// - The tree is complete (but not necessarily perfect). -/// - The leaves to be proven are in the opposite order they appear in -/// the tree (i.e., as seen from right to left starting at the deepest -/// layer and continuing at the next layer). -/// -/// NOTE: This implementation is *not* equivalent to it's Solidity -/// counterpart. In Rust, access to uninitialized memory panics, which -/// means we don't need to check that the whole proof array has been -/// processed. Both implementations will revert for the same inputs, but -/// for different reasons. See -/// -/// # Arguments -/// -/// * `proof` - A slice of hashes that constitute the merkle proof. -/// * `proof_flags` - A slice of booleans that determine whether to hash leaves -/// or the proof. -/// * `root` - The root of the merkle tree, in bytes. -/// * `leaves` - A slice of hashes that constitute the leaves of the merkle -/// tree to be proven, each leaf in bytes. -/// * `hasher` - The hashing algorithm to use. -/// -/// # Errors -/// -/// Will return `Err` if the arguments are well-formed, but invalid. -/// -/// # Panics -/// -/// Will panic with an out-of-bounds error if the proof is malicious. See -/// -/// -/// # Examples -/// -/// ``` -/// # use const_hex::FromHex; -/// # use alloy_primitives::keccak256; -/// # use crypto::merkle::verify_multi_proof; -/// # struct Keccak256; -/// # impl crypto::merkle::Hasher for Keccak256 { -/// # type Hash = Bytes32; -/// # fn hash>(&mut self, data: A) -> Self::Hash { -/// # let bytes = data.as_ref(); -/// # *keccak256(bytes) -/// # } -/// # } -/// type Bytes32 = [u8; 32]; -/// -/// const ROOT: &str = "0x6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; -/// const LEAVES: &str = "0x19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681 -/// 0xc62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848 -/// 0xeba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b"; -/// const PROOF: &str = "0x9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e -/// 0x8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f"; -/// -/// let root = Bytes32::from_hex(ROOT).unwrap(); -/// let leaves: Vec<_> = LEAVES -/// .lines() -/// .map(|h| Bytes32::from_hex(h.trim()).unwrap()) -/// .collect(); -/// let proof: Vec<_> = PROOF -/// .lines() -/// .map(|h| Bytes32::from_hex(h.trim()).unwrap()) -/// .collect(); -/// let proof_flags = [false, true, false, true]; -/// -/// let verification = -/// verify_multi_proof(&proof, &proof_flags, root, &leaves, Keccak256); -/// assert!(verification.unwrap()); -/// ``` -#[cfg(feature = "multi_proof")] -pub fn verify_multi_proof>( - proof: &[Bytes32], - proof_flags: &[bool], - root: Bytes32, - leaves: &[Bytes32], - mut hasher: H, -) -> Result { - let total_hashes = proof_flags.len(); - if leaves.len() + proof.len() != total_hashes + 1 { - return Err(MultiProofError::InvalidProofLength); - } - if total_hashes == 0 { - // We can safely assume that either `leaves` or `proof` is not empty - // given the previous check. We use `unwrap_or_else` to avoid eagerly - // evaluating `proof[0]`, which may panic. - let rebuilt_root = *leaves.first().unwrap_or_else(|| &proof[0]); - return Ok(root == rebuilt_root); - } - - // `hashes` represents a queue of hashes, our "main queue". - let mut hashes = Vec::with_capacity(total_hashes + leaves.len()); - hashes.extend(leaves); - // The `xxx_pos` values are "pointers" to the next value to consume in each - // queue. We use them to mimic a queue's pop operation. - let mut proof_pos = 0; - let mut hashes_pos = 0; - // At each step, we compute the next hash using two values: - // - A value from the "main queue". Consume all the leaves, then all the - // hashes but the root. - // - A value from the "main queue" (merging branches) or a member of the - // `proof`, depending on `flag`. - for &flag in proof_flags { - let a = hashes[hashes_pos]; - hashes_pos += 1; - - let b; - if flag { - b = hashes[hashes_pos]; - hashes_pos += 1; - } else { - b = proof[proof_pos]; - proof_pos += 1; - }; - - hashes.push(sorted_hash(a, b, &mut hasher)); - } - - // We know that `total_hashes > 0`. - let rebuilt_root = hashes[total_hashes + leaves.len() - 1]; - Ok(root == rebuilt_root) -} - -/// Sort the pair `(a, b)` and hash the result with `hasher`. -fn sorted_hash>( - mut a: Bytes32, - mut b: Bytes32, - hasher: &mut H, -) -> Bytes32 { - if a >= b { - (a, b) = (b, a); - } - - let mut buffer = [0u8; 64]; - buffer[..32].copy_from_slice(&a); - buffer[32..].copy_from_slice(&b); - hasher.hash(buffer) -} - -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { //! NOTE: The values used as input for these tests were all generated using //! https://github.com/OpenZeppelin/merkle-tree. - use alloy_primitives::keccak256; - use const_hex::FromHex; + use hex_literal::hex; use rand::{thread_rng, RngCore}; - use super::{verify, Bytes32, Hasher}; - use crate::merkle::{sorted_hash, verify_multi_proof}; - - /// Forwards calls to `alloy_primitives::keccak256`. - struct Keccak256; - impl Hasher for Keccak256 { - type Hash = Bytes32; - - fn hash>(&mut self, data: A) -> Self::Hash { - let bytes = data.as_ref(); - *keccak256(bytes) - } - } - - /// Shorthand for converting from a hex str to a fixed 32-bytes array. - macro_rules! hex_to_bytes_32 { - ($($var:ident = $bytes:expr);* $(;)?) => { - $(let $var = Bytes32::from_hex($bytes).unwrap();)* + use super::{Bytes32, KeccakBuilder, Verifier}; + use crate::hash::{commutative_hash_pair, BuildHasher}; + + /// Shorthand for declaring variables converted from a hex literal to a + /// fixed 32-byte slice. + macro_rules! bytes { + ($($var:ident = $hex:literal);* $(;)?) => { + $( + #[allow(non_upper_case_globals)] + const $var: Bytes32 = hex!($hex); + )* }; } - /// Shorthand for converting from a string containing several address to - /// a fixed 32-bytes collection. - macro_rules! str_to_bytes_32 { - ($bytes:expr) => { - $bytes - .lines() - .map(|l| Bytes32::from_hex(l.trim()).unwrap()) - .collect() + /// Shorthand for converting from an array of hex literals to an array of + /// fixed 32-bytes slices. + macro_rules! bytes_array { + ($($s:literal),* $(,)?) => { + [ + $(hex!($s),)* + ] }; } @@ -310,27 +398,27 @@ mod tests { // const hash = merkleTree.leafHash(['A']); // const proof = merkleTree.getProof(['A']); // ``` - hex_to_bytes_32! { - root = "0xb89eb120147840e813a77109b44063488a346b4ca15686185cf314320560d3f3"; - leaf_a = "0x6efbf77e320741a027b50f02224545461f97cd83762d5fbfeb894b9eb3287c16"; - leaf_b = "0x7051e21dd45e25ed8c605a53da6f77de151dcbf47b0e3ced3c5d8b61f4a13dbc"; + bytes! { + root = "b89eb120147840e813a77109b44063488a346b4ca15686185cf314320560d3f3"; + leaf_a = "6efbf77e320741a027b50f02224545461f97cd83762d5fbfeb894b9eb3287c16"; + leaf_b = "7051e21dd45e25ed8c605a53da6f77de151dcbf47b0e3ced3c5d8b61f4a13dbc"; }; - let proof: Vec<_> = str_to_bytes_32! { - "0x7051e21dd45e25ed8c605a53da6f77de151dcbf47b0e3ced3c5d8b61f4a13dbc - 0x1629d3b5b09b30449d258e35bbd09dd5e8a3abb91425ef810dc27eef995f7490 - 0x633d21baee4bbe5ed5c51ac0c68f7946b8f28d2937f0ca7ef5e1ea9dbda52e7a - 0x8a65d3006581737a3bab46d9e4775dbc1821b1ea813d350a13fcd4f15a8942ec - 0xd6c3f3e36cd23ba32443f6a687ecea44ebfe2b8759a62cccf7759ec1fb563c76 - 0x276141cd72b9b81c67f7182ff8a550b76eb96de9248a3ec027ac048c79649115" + let proof = bytes_array! { + "7051e21dd45e25ed8c605a53da6f77de151dcbf47b0e3ced3c5d8b61f4a13dbc", + "1629d3b5b09b30449d258e35bbd09dd5e8a3abb91425ef810dc27eef995f7490", + "633d21baee4bbe5ed5c51ac0c68f7946b8f28d2937f0ca7ef5e1ea9dbda52e7a", + "8a65d3006581737a3bab46d9e4775dbc1821b1ea813d350a13fcd4f15a8942ec", + "d6c3f3e36cd23ba32443f6a687ecea44ebfe2b8759a62cccf7759ec1fb563c76", + "276141cd72b9b81c67f7182ff8a550b76eb96de9248a3ec027ac048c79649115", }; - let verification = verify(&proof, root, leaf_a, Keccak256); + let verification = Verifier::verify(&proof, root, leaf_a); assert!(verification); - let mut hasher = Keccak256; - let no_such_leaf = sorted_hash(leaf_a, leaf_b, &mut hasher); + let builder = KeccakBuilder.build_hasher(); + let no_such_leaf = commutative_hash_pair(leaf_a, leaf_b, builder); let proof = &proof[1..]; - let verification = verify(proof, root, no_such_leaf, hasher); + let verification = Verifier::verify(proof, root, no_such_leaf); assert!(verification); } @@ -344,13 +432,13 @@ mod tests { // const leaf = correctMerkleTree.leafHash(['a']); // const proof = otherMerkleTree.getProof(['d']); // ``` - hex_to_bytes_32! { - root = "0xf2129b5a697531ef818f644564a6552b35c549722385bc52aa7fe46c0b5f46b1"; - leaf = "0x9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; - proof = "0x7b0c6cd04b82bfc0e250030a5d2690c52585e0cc6a4f3bc7909d7723b0236ece"; + bytes! { + root = "f2129b5a697531ef818f644564a6552b35c549722385bc52aa7fe46c0b5f46b1"; + leaf = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; + proof = "7b0c6cd04b82bfc0e250030a5d2690c52585e0cc6a4f3bc7909d7723b0236ece"; }; - let verification = verify(&[proof], root, leaf, Keccak256); + let verification = Verifier::verify(&[proof], root, leaf); assert!(!verification); } @@ -363,17 +451,17 @@ mod tests { // const leaf = merkleTree.leafHash(['a']); // const proof = merkleTree.getProof(['a']); // ``` - hex_to_bytes_32! { - root = "0xf2129b5a697531ef818f644564a6552b35c549722385bc52aa7fe46c0b5f46b1"; - leaf = "0x9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; + bytes! { + root = "f2129b5a697531ef818f644564a6552b35c549722385bc52aa7fe46c0b5f46b1"; + leaf = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; }; - let proof: Vec<_> = str_to_bytes_32! { - "0x19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681 - 0x9cf5a63718145ba968a01c1d557020181c5b252f665cf7386d370eddb176517b" + let proof = bytes_array! { + "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681", + "9cf5a63718145ba968a01c1d557020181c5b252f665cf7386d370eddb176517b", }; let bad_proof = &proof[..1]; - let verification = verify(bad_proof, root, leaf, Keccak256); + let verification = Verifier::verify(bad_proof, root, leaf); assert!(!verification); } @@ -386,22 +474,22 @@ mod tests { // const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('bdf')); // const hashes = leaves.map(e => merkleTree.leafHash(e)); // ``` - hex_to_bytes_32! { - root = "0x6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; + bytes! { + root = "6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; }; - let leaves: Vec<_> = str_to_bytes_32! { - "0x19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681 - 0xc62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848 - 0xeba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b" + let leaves = bytes_array! { + "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681", + "c62a8cfa41edc0ef6f6ae27a2985b7d39c7fea770787d7e104696c6e81f64848", + "eba909cf4bb90c6922771d7f126ad0fd11dfde93f3937a196274e1ac20fd2f5b", }; - let proof: Vec<_> = str_to_bytes_32! { - "0x9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e - 0x8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f" + let proof = bytes_array! { + "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e", + "8076923e76cf01a7c048400a2304c9a9c23bbbdac3a98ea3946340fdafbba34f", }; let proof_flags = [false, true, false, true]; let verification = - verify_multi_proof(&proof, &proof_flags, root, &leaves, Keccak256); + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); assert!(verification.unwrap()); } @@ -415,19 +503,19 @@ mod tests { // const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toElements('ghi')); // const hashes = leaves.map(e => merkleTree.leafHash(e)); // ``` - hex_to_bytes_32! { - root = "0x6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; + bytes! { + root = "6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8"; }; - let leaves: Vec<_> = str_to_bytes_32! { - "0x34e6ce3d0d73f6bff2ee1e865833d58e283570976d70b05f45c989ef651ef742 - 0xaa28358fb75b314c899e16d7975e029d18b4457fd8fd831f2e6c17ffd17a1d7e - 0xe0fd7e6916ff95d933525adae392a17e247819ebecc2e63202dfec7005c60560" + let leaves = bytes_array! { + "34e6ce3d0d73f6bff2ee1e865833d58e283570976d70b05f45c989ef651ef742", + "aa28358fb75b314c899e16d7975e029d18b4457fd8fd831f2e6c17ffd17a1d7e", + "e0fd7e6916ff95d933525adae392a17e247819ebecc2e63202dfec7005c60560", }; let proof = []; let proof_flags = [true, true]; let verification = - verify_multi_proof(&proof, &proof_flags, root, &leaves, Keccak256); + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); assert!(!verification.unwrap()); } @@ -446,12 +534,12 @@ mod tests { // const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) // const fill = ethers.randomBytes(32); // ``` - hex_to_bytes_32! { - root = "0x8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"; - hash_a = "0x9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; - hash_b = "0x19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"; - hash_cd = "0x03707d7802a71ca56a8ad8028da98c4f1dbec55b31b4a25d536b5309cc20eda9"; - hash_e = "0x9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"; + bytes! { + root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"; + hash_a = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; + hash_b = "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"; + hash_cd = "03707d7802a71ca56a8ad8028da98c4f1dbec55b31b4a25d536b5309cc20eda9"; + hash_e = "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"; }; let mut random_bytes = [0u8; 32]; @@ -463,13 +551,12 @@ mod tests { let leaves = [hash_a, hash_e]; let verification = - verify_multi_proof(&proof, &proof_flags, root, &leaves, Keccak256); + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); assert!(verification.is_err()); } #[test] - #[should_panic] - fn panics_multi_proof_len_invalid() { + fn errors_multi_proof_len_invalid() { // ```js // const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']); // @@ -483,12 +570,12 @@ mod tests { // const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree) // const fill = ethers.randomBytes(32); // ``` - hex_to_bytes_32! { - root = "0x8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"; - hash_a = "0x9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; - hash_b = "0x19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"; - hash_cd = "0x03707d7802a71ca56a8ad8028da98c4f1dbec55b31b4a25d536b5309cc20eda9"; - hash_e = "0x9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"; + bytes! { + root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"; + hash_a = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"; + hash_b = "19ba6c6333e0e9a15bf67523e0676e2f23eb8e574092552d5e888c64a4bb3681"; + hash_cd = "03707d7802a71ca56a8ad8028da98c4f1dbec55b31b4a25d536b5309cc20eda9"; + hash_e = "9a4f64e953595df82d1b4f570d34c4f4f0cfaf729a61e9d60e83e579e1aa283e"; }; let mut random_bytes = [0u8; 32]; @@ -499,8 +586,9 @@ mod tests { let proof_flags = [false, false, false, false]; let leaves = [hash_e, hash_a]; - let _ = - verify_multi_proof(&proof, &proof_flags, root, &leaves, Keccak256); + let verification = + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); + assert!(verification.is_err()); } #[test] @@ -512,13 +600,17 @@ mod tests { // const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('a')); // const hashes = leaves.map(e => merkleTree.leafHash(e)); // ``` - hex_to_bytes_32!(root = "0x9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"); + bytes!(root = "9c15a6a0eaeed500fd9eed4cbeab71f797cefcc67bfd46683e4d2e6ff7f06d1c"); let proof = []; let proof_flags = []; let leaves = [root]; - let verification = - verify_multi_proof(&proof, &proof_flags, root, &leaves, Keccak256); + let verification = Verifier::::verify_multi_proof( + &proof, + &proof_flags, + root, + &leaves, + ); assert!(verification.unwrap()); } @@ -529,21 +621,20 @@ mod tests { // // const root = merkleTree.root; // ``` - hex_to_bytes_32!(root = "0x8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"); + bytes!(root = "8f7234e8cfe39c08ca84a3a3e3274f574af26fd15165fe29e09cbab742daccd9"); let proof = [root]; let proof_flags = []; let leaves = []; let verification = - verify_multi_proof(&proof, &proof_flags, root, &leaves, Keccak256); + Verifier::verify_multi_proof(&proof, &proof_flags, root, &leaves); assert!(verification.unwrap()); } #[test] - #[should_panic] - /// Panics when processing manipulated proofs with a zero-value node at + /// Errors when processing manipulated proofs with a zero-value node at /// depth 1. - fn panics_manipulated_multi_proof() { + fn errors_manipulated_multi_proof() { // ```js // // Create a merkle tree that contains a zero leaf at depth 1 // const leave = ethers.id('real leaf'); @@ -555,23 +646,23 @@ mod tests { // const maliciousProof = [leave, leave]; // const maliciousProofFlags = [true, true, false]; // ``` - hex_to_bytes_32! { - root = "0xf2d552e1e4c59d4f0fa2b80859febc9e4bdc915dff37c56c858550d8b64659a5"; - leaf = "0x5e941ddd8f313c0b39f92562c0eca709c3d91360965d396aaef584b3fa76889a"; + bytes! { + root = "f2d552e1e4c59d4f0fa2b80859febc9e4bdc915dff37c56c858550d8b64659a5"; + leaf = "5e941ddd8f313c0b39f92562c0eca709c3d91360965d396aaef584b3fa76889a"; }; - let malicious_leaves: Vec<_> = str_to_bytes_32! { - "0x1f23ad5fc0ee6ccbe2f3d30df856758f05ad9d03408a51a99c1c9f0854309db2 - 0x613994f4e324d0667c07857cd5d147994bc917da5d07ee63fc3f0a1fe8a18e34" + let malicious_leaves = bytes_array! { + "1f23ad5fc0ee6ccbe2f3d30df856758f05ad9d03408a51a99c1c9f0854309db2", + "613994f4e324d0667c07857cd5d147994bc917da5d07ee63fc3f0a1fe8a18e34", }; let malicious_proof = [leaf, leaf]; let malicious_proof_flags = [true, true, false]; - let _ = verify_multi_proof( + let verification = Verifier::verify_multi_proof( &malicious_proof, &malicious_proof_flags, root, &malicious_leaves, - Keccak256, ); + assert!(verification.is_err()); } }