Skip to content

Commit

Permalink
ref(merkle): move things around & add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfertel committed May 2, 2024
1 parent f31692d commit fc7e84d
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 48 deletions.
5 changes: 4 additions & 1 deletion examples/merkle-proofs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ extern crate alloc;
use alloc::vec::Vec;

use alloy_primitives::B256;
use crypto::merkle::{self, KeccakBuilder, Verifier};
use crypto::{
merkle::{self, Verifier},
KeccakBuilder,
};
use stylus_proc::SolidityError;
use stylus_sdk::{
alloy_sol_types::sol,
Expand Down
5 changes: 2 additions & 3 deletions lib/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ hex-literal = "0.4.1"
rand = "0.8.5"

[features]
default = []
std = []

merkle = []
multi-proof = ["merkle"]

std = []
76 changes: 70 additions & 6 deletions lib/crypto/src/merkle/hash.rs → lib/crypto/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ pub trait Hasher {
/// # Examples
///
/// ```rust
/// use crypto::merkle::KeccakBuilder;
/// use crypto::merkle::hash::{BuildHasher, Hash, Hasher};
/// use crypto::KeccakBuilder;
/// use crypto::hash::{BuildHasher, Hash, Hasher};
///
/// let b = KeccakBuilder;
/// let mut hasher_1 = b.build_hasher();
Expand All @@ -78,8 +78,8 @@ pub trait BuildHasher {
/// # Examples
///
/// ```rust
/// use crypto::merkle::KeccakBuilder;
/// use crypto::merkle::hash::BuildHasher;
/// use crypto::KeccakBuilder;
/// use crypto::hash::BuildHasher;
///
/// let b = KeccakBuilder;
/// let hasher = b.build_hasher();
Expand All @@ -100,8 +100,8 @@ pub trait BuildHasher {
/// # Examples
///
/// ```rust
/// use crypto::merkle::KeccakBuilder;
/// use crypto::merkle::hash::{BuildHasher, Hash};
/// use crypto::KeccakBuilder;
/// use crypto::hash::{BuildHasher, Hash};
///
/// let b = KeccakBuilder;
/// let hash_1 = b.hash_one([0u8; 32]);
Expand All @@ -125,3 +125,67 @@ pub trait BuildHasher {
hasher.finalize()
}
}

/// Hash the pair `(a, b)` with `state`.
#[inline]
pub fn hash_pair<S, H>(a: H, b: H, mut state: S) -> S::Output

Check warning on line 131 in lib/crypto/src/hash.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] lib/crypto/src/hash.rs#L131

warning: item name starts with its containing module's name --> lib/crypto/src/hash.rs:131:8 | 131 | pub fn hash_pair<S, H>(a: H, b: H, mut state: S) -> S::Output | ^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions note: the lint level is defined here --> lib/crypto/src/lib.rs:3:22 | 3 | #![warn(clippy::all, clippy::pedantic)] | ^^^^^^^^^^^^^^^^ = note: `#[warn(clippy::module_name_repetitions)]` implied by `#[warn(clippy::pedantic)]`
Raw output
lib/crypto/src/hash.rs:131:8:w:warning: item name starts with its containing module's name
   --> lib/crypto/src/hash.rs:131:8
    |
131 | pub fn hash_pair<S, H>(a: H, b: H, mut state: S) -> S::Output
    |        ^^^^^^^^^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
note: the lint level is defined here
   --> lib/crypto/src/lib.rs:3:22
    |
3   | #![warn(clippy::all, clippy::pedantic)]
    |                      ^^^^^^^^^^^^^^^^
    = note: `#[warn(clippy::module_name_repetitions)]` implied by `#[warn(clippy::pedantic)]`


__END__

Check warning on line 131 in lib/crypto/src/hash.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] lib/crypto/src/hash.rs#L131

warning: this argument is passed by value, but not consumed in the function body --> lib/crypto/src/hash.rs:131:27 | 131 | pub fn hash_pair<S, H>(a: H, b: H, mut state: S) -> S::Output | ^ help: consider taking a reference instead: `&H` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value = note: `#[warn(clippy::needless_pass_by_value)]` implied by `#[warn(clippy::pedantic)]`
Raw output
lib/crypto/src/hash.rs:131:27:w:warning: this argument is passed by value, but not consumed in the function body
   --> lib/crypto/src/hash.rs:131:27
    |
131 | pub fn hash_pair<S, H>(a: H, b: H, mut state: S) -> S::Output
    |                           ^ help: consider taking a reference instead: `&H`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
    = note: `#[warn(clippy::needless_pass_by_value)]` implied by `#[warn(clippy::pedantic)]`


__END__

Check warning on line 131 in lib/crypto/src/hash.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] lib/crypto/src/hash.rs#L131

warning: this argument is passed by value, but not consumed in the function body --> lib/crypto/src/hash.rs:131:33 | 131 | pub fn hash_pair<S, H>(a: H, b: H, mut state: S) -> S::Output | ^ help: consider taking a reference instead: `&H` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
Raw output
lib/crypto/src/hash.rs:131:33:w:warning: this argument is passed by value, but not consumed in the function body
   --> lib/crypto/src/hash.rs:131:33
    |
131 | pub fn hash_pair<S, H>(a: H, b: H, mut state: S) -> S::Output
    |                                 ^ help: consider taking a reference instead: `&H`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value


__END__
where
H: Hash,
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<S, H>(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<H: Hasher>(&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);
}
}
8 changes: 4 additions & 4 deletions lib/crypto/src/merkle/keccak.rs → lib/crypto/src/keccak.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! An interface to the default hashing algorithm used in this library's [merkle
//! proofs][super].
//! proofs][crate].
use tiny_keccak::{Hasher as TinyHasher, Keccak};

use super::hash::{BuildHasher, Hash, Hasher};
use crate::hash::{BuildHasher, Hash, Hasher};

/// The default [`Hasher`] builder used in this library's [merkle
/// proofs][super].
/// proofs][crate].
///
/// It instantiates a [`Keccak256`] hasher.
#[allow(clippy::module_name_repetitions)]
Expand All @@ -20,7 +20,7 @@ impl BuildHasher for KeccakBuilder {
}
}

/// The default [`Hasher`] used in this library's [merkle proofs][super].
/// 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.
Expand Down
4 changes: 4 additions & 0 deletions lib/crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
#![cfg_attr(not(feature = "std"), no_std, no_main)]
extern crate alloc;

pub mod hash;
#[cfg(any(feature = "std", feature = "merkle"))]
pub mod merkle;

pub mod keccak;
pub use keccak::KeccakBuilder;
49 changes: 15 additions & 34 deletions lib/crypto/src/merkle/mod.rs → lib/crypto/src/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,13 @@
use alloc::vec::Vec;
use core::marker::PhantomData;

use self::hash::{BuildHasher, Hash, Hasher};

pub mod hash;
pub mod keccak;
pub use keccak::KeccakBuilder;
use crate::{
hash::{commutative_hash_pair, BuildHasher, Hasher},
KeccakBuilder,
};

type Bytes32 = [u8; 32];

/// Sort the pair `(a, b)` and hash the result with `hasher`.
#[inline]
fn hash_sorted_pair<S>(
mut a: Bytes32,
mut b: Bytes32,
mut state: S,
) -> S::Output
where
S: Hasher,
{
if a >= b {
core::mem::swap(&mut a, &mut b);
}

a.hash(&mut state);
b.hash(&mut state);
state.finalize()
}

/// Verify merkle proofs.
pub struct Verifier<B = KeccakBuilder>(PhantomData<B>)
where
Expand Down Expand Up @@ -188,7 +168,7 @@ where
/// # Examples
///
/// ```
/// use crypto::merkle::{KeccakBuilder, Verifier};
/// use crypto::{merkle::Verifier, KeccakBuilder};
/// use hex_literal::hex;
///
/// let root = hex!("0000000000000000000000000000000000000000000000000000000000000000");
Expand All @@ -205,7 +185,7 @@ where
builder: &B,
) -> bool {
for &hash in proof {
leaf = hash_sorted_pair(leaf, hash, builder.build_hasher());
leaf = commutative_hash_pair(leaf, hash, builder.build_hasher());
}

leaf == root
Expand Down Expand Up @@ -263,7 +243,7 @@ where
/// # Examples
///
/// ```rust
/// use crypto::merkle::{KeccakBuilder, Verifier};
/// use crypto::{merkle::Verifier, KeccakBuilder};
/// use hex_literal::hex;
///
/// let root = hex!("6deb52b5da8fd108f79fab00341f38d2587896634c646ee52e49f845680a70c8");
Expand Down Expand Up @@ -328,7 +308,7 @@ where
proof_pos += 1;
};

let hash = hash_sorted_pair(a, *b, builder.build_hasher());
let hash = commutative_hash_pair(a, *b, builder.build_hasher());
hashes.push(hash);
}

Expand Down Expand Up @@ -382,10 +362,11 @@ mod tests {
use hex_literal::hex;
use rand::{thread_rng, RngCore};

use super::{hash_sorted_pair, Bytes32, KeccakBuilder, Verifier};
use crate::merkle::hash::BuildHasher;
use super::{Bytes32, KeccakBuilder, Verifier};
use crate::hash::{commutative_hash_pair, BuildHasher};

/// Shorthand for converting from a hex str to a fixed 32-bytes array.
/// Shorthand for declaring variables converted from a hex literal to a
/// fixed 32-byte slice.
macro_rules! bytes {
($($var:ident = $hex:literal);* $(;)?) => {
$(
Expand All @@ -395,8 +376,8 @@ mod tests {
};
}

/// Shorthand for converting from a string containing several addresses to
/// a fixed 32-bytes collection.
/// Shorthand for converting from an array of hex literals to an array of
/// fixed 32-bytes slices.
macro_rules! bytes_array {
($($s:literal),* $(,)?) => {
[
Expand Down Expand Up @@ -435,7 +416,7 @@ mod tests {
assert!(verification);

let builder = KeccakBuilder.build_hasher();
let no_such_leaf = hash_sorted_pair(leaf_a, leaf_b, builder);
let no_such_leaf = commutative_hash_pair(leaf_a, leaf_b, builder);
let proof = &proof[1..];
let verification = Verifier::verify(proof, root, no_such_leaf);
assert!(verification);
Expand Down

0 comments on commit fc7e84d

Please sign in to comment.