Skip to content

Commit

Permalink
refactor: define hasher traits in nomt-core hasher module and feature…
Browse files Browse the repository at this point in the history
…-gate
  • Loading branch information
rphmeier committed Feb 6, 2025
1 parent bdc1a2d commit e1f3d23
Show file tree
Hide file tree
Showing 25 changed files with 217 additions and 176 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion benchtop/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions benchtop/src/nomt.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{backend::Transaction, timer::Timer, workload::Workload};
use fxhash::FxHashMap;
use nomt::{
trie::KeyPath, Blake3Hasher, KeyReadWrite, Nomt, Options, Overlay, Session, SessionParams,
WitnessMode,
hasher::Blake3Hasher, trie::KeyPath, KeyReadWrite, Nomt, Options, Overlay, Session,
SessionParams, WitnessMode,
};
use sha2::Digest;
use std::{
Expand Down
6 changes: 5 additions & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
ruint = { version = "1.12.1", default-features = false }
arrayvec = { version = "0.7", default-features = false }
borsh = { version = ">=1.4, <1.5.0", default-features = false, features = ["derive"], optional = true }
blake3 = { version = "1.5.1", default-features = false, optional = true }
sha2 = { version = "0.10.6" , default-features = false, optional = true }

[dev-dependencies]
blake3 = "1.5.1"

[features]
default = ["std"]
default = ["std", "blake3-hasher", "sha2-hasher"]
std = ["bitvec/std", "borsh/std"]
borsh = ["dep:borsh"]
blake3-hasher = ["dep:blake3"]
sha2-hasher = ["dep:sha2"]
162 changes: 162 additions & 0 deletions core/src/hasher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
//! Hashers (feature-gated) and utilities for implementing them.
use crate::trie::{InternalData, LeafData, Node, NodeKind, TERMINATOR};

/// A trie node hash function specialized for 64 bytes of data.
///
/// Note that it is illegal for the produced hash to equal [0; 32], as this value is reserved
/// for the terminator node.
///
/// A node hasher should domain-separate internal and leaf nodes in some specific way. The
/// recommended approach for binary hashes is to set the MSB to 0 or 1 depending on the node kind.
/// However, for other kinds of hashes (e.g. Poseidon2 or other algebraic hashes), other labeling
/// schemes may be required.
pub trait NodeHasher {
/// Hash a leaf. This should domain-separate the hash
/// according to the node kind.
fn hash_leaf(data: &LeafData) -> [u8; 32];

/// Hash an internal node. This should domain-separate
/// the hash according to the node kind.
fn hash_internal(data: &InternalData) -> [u8; 32];

/// Get the kind of the given node.
fn node_kind(node: &Node) -> NodeKind;
}

/// A hasher for arbitrary-length values.
pub trait ValueHasher {
/// Hash an arbitrary-length value.
fn hash_value(value: &[u8]) -> [u8; 32];
}

/// Get the node kind, according to a most-significant bit labeling scheme.
///
/// If the MSB is true, it's a leaf. If the node is empty, it's a [`TERMINATOR`]. Otherwise, it's
/// an internal node.
pub fn node_kind_by_msb(node: &Node) -> NodeKind {
if node[0] >> 7 == 1 {
NodeKind::Leaf
} else if node == &TERMINATOR {
NodeKind::Terminator
} else {
NodeKind::Internal
}
}

/// Set the most-significant bit of the node.
pub fn set_msb(node: &mut Node) {
node[0] |= 0b10000000;
}

pub fn unset_msb(node: &mut Node) {
node[0] &= 0b01111111;
}

/// A simple trait for representing binary hash functions.
pub trait BinaryHash {
/// Given a bit-string, produce a 32-bit hash.
fn hash(input: &[u8]) -> [u8; 32];

/// An optional specialization of `hash` where there are two 32-byte inputs, left and right.
fn hash2_32_concat(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
let mut buf = [0u8; 64];
buf[0..32].copy_from_slice(left);
buf[32..64].copy_from_slice(right);
Self::hash(&buf)
}
}

/// A node and value hasher constructed from a simple binary hasher.
///
/// This implements a [`ValueHasher`] and [`NodeHasher`] where the node kind is tagged by setting
/// or unsetting the MSB of the hash value.
///
/// The binary hash wrapped by this structure must behave approximately like a random oracle over
/// the space 2^256, i.e. all 256 bit outputs are valid and inputs are uniformly distributed.
///
/// Functions like Sha2/Blake3/Keccak/Groestl all meet these criteria.
pub struct BinaryHasher<H>(std::marker::PhantomData<H>);

impl<H: BinaryHash> ValueHasher for BinaryHasher<H> {
fn hash_value(value: &[u8]) -> [u8; 32] {
H::hash(value)
}
}

impl<H: BinaryHash> NodeHasher for BinaryHasher<H> {
fn hash_leaf(data: &LeafData) -> [u8; 32] {
let mut h = H::hash2_32_concat(&data.key_path, &data.value_hash);
set_msb(&mut h);
h
}

fn hash_internal(data: &InternalData) -> [u8; 32] {
let mut h = H::hash2_32_concat(&data.left, &data.right);
unset_msb(&mut h);
h
}

fn node_kind(node: &Node) -> NodeKind {
node_kind_by_msb(node)
}
}

#[cfg(any(feature = "blake3-hasher", test))]
pub use blake3::Blake3Hasher;

/// A node hasher making use of blake3.
#[cfg(any(feature = "blake3-hasher", test))]
pub mod blake3 {
use super::{BinaryHash, BinaryHasher};

/// A [`BinaryHash`] implementation for Blake3.
pub struct Blake3BinaryHasher;

/// A wrapper around Blake3 for use in NOMT.
pub type Blake3Hasher = BinaryHasher<Blake3BinaryHasher>;

impl BinaryHash for Blake3BinaryHasher {
fn hash(value: &[u8]) -> [u8; 32] {
blake3::hash(value).into()
}

fn hash2_32_concat(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(left);
hasher.update(right);
hasher.finalize().into()
}
}
}

#[cfg(feature = "sha2-hasher")]
pub use sha2::Sha2Hasher;

/// A node and value hasher making use of sha2-256.
#[cfg(feature = "sha2-hasher")]
pub mod sha2 {
use super::{BinaryHash, BinaryHasher};
use sha2::{Digest, Sha256};

/// A [`BinaryHash`] implementation for Sha2.
pub struct Sha2BinaryHasher;

/// A wrapper around sha2-256 for use in NOMT.
pub type Sha2Hasher = BinaryHasher<Sha2BinaryHasher>;

impl BinaryHash for Sha2BinaryHasher {
fn hash(value: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(value);
hasher.finalize().into()
}

fn hash2_32_concat(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(left);
hasher.update(right);
hasher.finalize().into()
}
}
}
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

extern crate alloc;

pub mod hasher;
pub mod page;
pub mod page_id;
pub mod proof;
Expand Down
43 changes: 4 additions & 39 deletions core/src/proof/multi_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
//! the inclusion of all provided path proofs.
use crate::{
hasher::NodeHasher,
proof::{
path_proof::{hash_path, shared_bits},
KeyOutOfScope, PathProof, PathProofTerminal,
},
trie::{InternalData, KeyPath, LeafData, Node, NodeHasher, TERMINATOR},
trie::{InternalData, KeyPath, LeafData, Node, TERMINATOR},
};

#[cfg(not(feature = "std"))]
Expand Down Expand Up @@ -497,8 +498,9 @@ mod tests {
use super::{verify, MultiProof};

use crate::{
hasher::{Blake3Hasher, NodeHasher},
proof::{PathProof, PathProofTerminal},
trie::{self, InternalData, LeafData, Node, NodeHasher, NodeKind, TERMINATOR},
trie::{InternalData, LeafData, TERMINATOR},
trie_pos::TriePosition,
};

Expand Down Expand Up @@ -884,43 +886,6 @@ mod tests {
);
}

/// Hash nodes with blake3.
pub struct Blake3Hasher;

impl NodeHasher for Blake3Hasher {
fn hash_leaf(data: &trie::LeafData) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(&data.key_path);
hasher.update(&data.value_hash);
let mut hash: [u8; 32] = hasher.finalize().into();

// Label with MSB
hash[0] |= 0b10000000;
hash
}

fn hash_internal(data: &trie::InternalData) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(&data.left);
hasher.update(&data.right);
let mut hash: [u8; 32] = hasher.finalize().into();

// Label with MSB
hash[0] &= 0b01111111;
hash
}

fn node_kind(node: &Node) -> NodeKind {
if node[0] >> 7 == 1 {
NodeKind::Leaf
} else if node == &TERMINATOR {
NodeKind::Terminator
} else {
NodeKind::Internal
}
}
}

#[test]
pub fn test_verify_multiproof_two_leafs() {
// root
Expand Down
3 changes: 2 additions & 1 deletion core/src/proof/path_proof.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Proving and verifying inclusion, non-inclusion, and updates to the trie.
use crate::trie::{self, InternalData, KeyPath, LeafData, Node, NodeHasher, NodeKind, TERMINATOR};
use crate::hasher::NodeHasher;
use crate::trie::{self, InternalData, KeyPath, LeafData, Node, NodeKind, TERMINATOR};
use crate::trie_pos::TriePosition;

use bitvec::prelude::*;
Expand Down
24 changes: 2 additions & 22 deletions core/src/trie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
//!
//! All node preimages are 512 bits.
use crate::hasher::NodeHasher;

/// A node in the binary trie. In this schema, it is always 256 bits and is the hash of either
/// an [`LeafData`] or [`InternalData`], or zeroed if it's a [`TERMINATOR`].
///
Expand Down Expand Up @@ -90,25 +92,3 @@ pub struct LeafData {
/// The hash of the value carried in this leaf.
pub value_hash: ValueHash,
}

/// A trie node hash function specialized for 64 bytes of data.
///
/// Note that it is illegal for the produced hash to equal [0; 32], as this value is reserved
/// for the terminator node.
///
/// A node hasher should domain-separate internal and leaf nodes in some specific way. The
/// recommended approach for binary hashes is to set the MSB to 0 or 1 depending on the node kind.
/// However, for other kinds of hashes (e.g. Poseidon2 or other algebraic hashes), other labeling
/// schemes may be required.
pub trait NodeHasher {
/// Hash a leaf. This should domain-separate the hash
/// according to the node kind.
fn hash_leaf(data: &LeafData) -> [u8; 32];

/// Hash an internal node. This should domain-separate
/// the hash according to the node kind.
fn hash_internal(data: &InternalData) -> [u8; 32];

/// Get the kind of the given node.
fn node_kind(node: &Node) -> NodeKind;
}
3 changes: 2 additions & 1 deletion core/src/update.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Trie update logic helpers.
use crate::trie::{self, KeyPath, LeafData, Node, NodeHasher, ValueHash};
use crate::hasher::NodeHasher;
use crate::trie::{self, KeyPath, LeafData, Node, ValueHash};

use bitvec::prelude::*;

Expand Down
4 changes: 3 additions & 1 deletion examples/commit_batch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use anyhow::Result;
use nomt::{Blake3Hasher, KeyReadWrite, Nomt, Options, Root, SessionParams, Witness, WitnessMode};
use nomt::{
hasher::Blake3Hasher, KeyReadWrite, Nomt, Options, Root, SessionParams, Witness, WitnessMode,
};
use sha2::Digest;

const NOMT_DB_FOLDER: &str = "nomt_db";
Expand Down
2 changes: 1 addition & 1 deletion examples/read_value/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Result;
use nomt::{Blake3Hasher, KeyReadWrite, Nomt, Options, SessionParams, WitnessMode};
use nomt::{hasher::Blake3Hasher, KeyReadWrite, Nomt, Options, SessionParams, WitnessMode};
use sha2::Digest;

const NOMT_DB_FOLDER: &str = "nomt_db";
Expand Down
Loading

0 comments on commit e1f3d23

Please sign in to comment.