From 45b242ae0b3df98ccc39b669b0bd8d8d203f6e8a Mon Sep 17 00:00:00 2001 From: Mac L Date: Sat, 27 Jul 2024 01:54:18 +1000 Subject: [PATCH 1/4] Implement TreeHash for bitfield --- tree_hash/Cargo.toml | 5 +-- tree_hash/src/impls.rs | 82 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/tree_hash/Cargo.toml b/tree_hash/Cargo.toml index d7d9165..0129ffe 100644 --- a/tree_hash/Cargo.toml +++ b/tree_hash/Cargo.toml @@ -13,13 +13,14 @@ categories = ["cryptography::cryptocurrencies"] [dependencies] alloy-primitives = "0.8.0" ethereum_hashing = "0.7.0" +ethereum_ssz = { git = "https://github.com/macladson/ethereum_ssz", branch = "bitvector" } smallvec = "1.6.1" +typenum = "1.12.0" [dev-dependencies] rand = "0.8.5" tree_hash_derive = { path = "../tree_hash_derive", version = "0.8.0" } -ethereum_ssz = "0.7" -ethereum_ssz_derive = "0.7" +ethereum_ssz_derive = { git = "https://github.com/macladson/ethereum_ssz", branch = "bitvector" } [features] arbitrary = ["alloy-primitives/arbitrary"] diff --git a/tree_hash/src/impls.rs b/tree_hash/src/impls.rs index 53d28f4..46cbc4e 100644 --- a/tree_hash/src/impls.rs +++ b/tree_hash/src/impls.rs @@ -1,6 +1,8 @@ use super::*; use alloy_primitives::{Address, B256, U128, U256}; +use ssz::{Bitfield, Fixed, Variable}; use std::sync::Arc; +use typenum::Unsigned; fn int_to_hash256(int: u64) -> Hash256 { let mut bytes = [0; HASHSIZE]; @@ -197,6 +199,86 @@ impl TreeHash for Arc { } } +impl TreeHash for Option { + fn tree_hash_type() -> TreeHashType { + T::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + match self { + Some(inner) => inner.tree_hash_packed_encoding(), + None => unreachable!(), + } + } + + fn tree_hash_packing_factor() -> usize { + T::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> Hash256 { + match self { + Some(inner) => inner.tree_hash_root(), + None => unreachable!(), + } + } +} + +/// A helper function providing common functionality for finding the Merkle root of some bytes that +/// represent a bitfield. +pub fn bitfield_bytes_tree_hash_root(bytes: &[u8]) -> Hash256 { + let byte_size = (N::to_usize() + 7) / 8; + let leaf_count = (byte_size + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK; + + let mut hasher = MerkleHasher::with_leaves(leaf_count); + + hasher + .write(bytes) + .expect("bitfield should not exceed tree hash leaf limit"); + + hasher + .finish() + .expect("bitfield tree hash buffer should not exceed leaf limit") +} + +impl TreeHash for Bitfield> { + fn tree_hash_type() -> TreeHashType { + TreeHashType::List + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + unreachable!("List should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("List should never be packed.") + } + + fn tree_hash_root(&self) -> Hash256 { + // Note: we use `as_slice` because it does _not_ have the length-delimiting bit set (or + // present). + let root = bitfield_bytes_tree_hash_root::(self.as_slice()); + mix_in_length(&root, self.len()) + } +} + +impl TreeHash for Bitfield> { + fn tree_hash_type() -> TreeHashType { + TreeHashType::Vector + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_root(&self) -> Hash256 { + bitfield_bytes_tree_hash_root::(self.as_slice()) + } +} + #[cfg(test)] mod test { use super::*; From 51b88a82f67a561ce24b15eeae08167699ccaf95 Mon Sep 17 00:00:00 2001 From: Mac L Date: Fri, 20 Sep 2024 20:36:52 +1000 Subject: [PATCH 2/4] Use new release of ethereum_ssz --- tree_hash/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree_hash/Cargo.toml b/tree_hash/Cargo.toml index 0129ffe..75a4434 100644 --- a/tree_hash/Cargo.toml +++ b/tree_hash/Cargo.toml @@ -13,14 +13,14 @@ categories = ["cryptography::cryptocurrencies"] [dependencies] alloy-primitives = "0.8.0" ethereum_hashing = "0.7.0" -ethereum_ssz = { git = "https://github.com/macladson/ethereum_ssz", branch = "bitvector" } +ethereum_ssz = "0.8.0" smallvec = "1.6.1" typenum = "1.12.0" [dev-dependencies] rand = "0.8.5" tree_hash_derive = { path = "../tree_hash_derive", version = "0.8.0" } -ethereum_ssz_derive = { git = "https://github.com/macladson/ethereum_ssz", branch = "bitvector" } +ethereum_ssz_derive = "0.8.0" [features] arbitrary = ["alloy-primitives/arbitrary"] From f7171326f6242eefe0ac3c41aeb03fc4d74828e9 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 20 Dec 2024 10:57:21 +1100 Subject: [PATCH 3/4] Delete stray impl for Option --- tree_hash/src/impls.rs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/tree_hash/src/impls.rs b/tree_hash/src/impls.rs index 46cbc4e..9a47e28 100644 --- a/tree_hash/src/impls.rs +++ b/tree_hash/src/impls.rs @@ -199,30 +199,6 @@ impl TreeHash for Arc { } } -impl TreeHash for Option { - fn tree_hash_type() -> TreeHashType { - T::tree_hash_type() - } - - fn tree_hash_packed_encoding(&self) -> PackedEncoding { - match self { - Some(inner) => inner.tree_hash_packed_encoding(), - None => unreachable!(), - } - } - - fn tree_hash_packing_factor() -> usize { - T::tree_hash_packing_factor() - } - - fn tree_hash_root(&self) -> Hash256 { - match self { - Some(inner) => inner.tree_hash_root(), - None => unreachable!(), - } - } -} - /// A helper function providing common functionality for finding the Merkle root of some bytes that /// represent a bitfield. pub fn bitfield_bytes_tree_hash_root(bytes: &[u8]) -> Hash256 { From 2536b815ecec6491f05ff12ed7c06846a7f4215b Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Fri, 20 Dec 2024 11:12:56 +1100 Subject: [PATCH 4/4] Add some basic tests --- tree_hash/src/impls.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tree_hash/src/impls.rs b/tree_hash/src/impls.rs index 9a47e28..54ea302 100644 --- a/tree_hash/src/impls.rs +++ b/tree_hash/src/impls.rs @@ -258,6 +258,9 @@ impl TreeHash for Bitfield> { #[cfg(test)] mod test { use super::*; + use ssz::{BitList, BitVector}; + use std::str::FromStr; + use typenum::{U32, U8}; #[test] fn bool() { @@ -295,4 +298,36 @@ mod test { ] ); } + + #[test] + fn bitvector() { + let empty_bitvector = BitVector::::new(); + assert_eq!(empty_bitvector.tree_hash_root(), Hash256::ZERO); + + let small_bitvector_bytes = vec![0xff_u8, 0xee, 0xdd, 0xcc]; + let small_bitvector = + BitVector::::from_bytes(small_bitvector_bytes.clone().into()).unwrap(); + assert_eq!( + small_bitvector.tree_hash_root().as_slice()[..4], + small_bitvector_bytes + ); + } + + #[test] + fn bitlist() { + let empty_bitlist = BitList::::with_capacity(8).unwrap(); + assert_eq!( + empty_bitlist.tree_hash_root(), + Hash256::from_str("0x5ac78d953211aa822c3ae6e9b0058e42394dd32e5992f29f9c12da3681985130") + .unwrap() + ); + + let mut small_bitlist = BitList::::with_capacity(4).unwrap(); + small_bitlist.set(1, true).unwrap(); + assert_eq!( + small_bitlist.tree_hash_root(), + Hash256::from_str("0x7eb03d394d83a389980b79897207be3a6512d964cb08978bb7f3cfc0db8cfb8a") + .unwrap() + ); + } }