Skip to content

refactor: define hasher traits in nomt-core hasher module and feature-gate #787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing rustdoc?

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