Skip to content

Commit ce68e7e

Browse files
committed
refactor: define hasher traits in nomt-core hasher module and feature-gate
1 parent 3d45b1e commit ce68e7e

File tree

24 files changed

+182
-176
lines changed

24 files changed

+182
-176
lines changed

benchtop/Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

benchtop/src/nomt.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::{backend::Transaction, timer::Timer, workload::Workload};
22
use fxhash::FxHashMap;
33
use nomt::{
4-
trie::KeyPath, Blake3Hasher, KeyReadWrite, Nomt, Options, Overlay, Session, SessionParams,
5-
WitnessMode,
4+
hasher::Blake3Hasher, trie::KeyPath, KeyReadWrite, Nomt, Options, Overlay, Session,
5+
SessionParams, WitnessMode,
66
};
77
use sha2::Digest;
88
use std::{

core/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
1616
ruint = { version = "1.12.1", default-features = false }
1717
arrayvec = { version = "0.7", default-features = false }
1818
borsh = { version = ">=1.4, <1.5.0", default-features = false, features = ["derive"], optional = true }
19+
blake3 = { version = "1.5.1", default-features = false, optional = true }
1920

2021
[dev-dependencies]
2122
blake3 = "1.5.1"
2223

2324
[features]
24-
default = ["std"]
25+
default = ["std", "blake3-hasher"]
2526
std = ["bitvec/std", "borsh/std"]
2627
borsh = ["dep:borsh"]
28+
blake3-hasher = ["dep:blake3"]

core/src/hasher.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//! Hashers (feature-gated) and utilities for implementing them.
2+
3+
use crate::trie::{InternalData, LeafData, Node, NodeKind, TERMINATOR};
4+
5+
/// A trie node hash function specialized for 64 bytes of data.
6+
///
7+
/// Note that it is illegal for the produced hash to equal [0; 32], as this value is reserved
8+
/// for the terminator node.
9+
///
10+
/// A node hasher should domain-separate internal and leaf nodes in some specific way. The
11+
/// recommended approach for binary hashes is to set the MSB to 0 or 1 depending on the node kind.
12+
/// However, for other kinds of hashes (e.g. Poseidon2 or other algebraic hashes), other labeling
13+
/// schemes may be required.
14+
pub trait NodeHasher {
15+
/// Hash a leaf. This should domain-separate the hash
16+
/// according to the node kind.
17+
fn hash_leaf(data: &LeafData) -> [u8; 32];
18+
19+
/// Hash an internal node. This should domain-separate
20+
/// the hash according to the node kind.
21+
fn hash_internal(data: &InternalData) -> [u8; 32];
22+
23+
/// Get the kind of the given node.
24+
fn node_kind(node: &Node) -> NodeKind;
25+
}
26+
27+
/// A hasher for arbitrary-length values.
28+
pub trait ValueHasher {
29+
/// Hash an arbitrary-length value.
30+
fn hash_value(value: &[u8]) -> [u8; 32];
31+
}
32+
33+
/// Get the node kind, according to a most-significant bit labeling scheme.
34+
///
35+
/// If the MSB is true, it's a leaf. If the node is empty, it's a [`TERMINATOR`]. Otherwise, it's
36+
/// an internal node.
37+
pub fn node_kind_by_msb(node: &Node) -> NodeKind {
38+
if node[0] >> 7 == 1 {
39+
NodeKind::Leaf
40+
} else if node == &TERMINATOR {
41+
NodeKind::Terminator
42+
} else {
43+
NodeKind::Internal
44+
}
45+
}
46+
47+
/// Set the most-significant bit of the node.
48+
pub fn set_msb(node: &mut Node) {
49+
node[0] |= 0b10000000;
50+
}
51+
52+
pub fn unset_msb(node: &mut Node) {
53+
node[0] &= 0b01111111;
54+
}
55+
56+
/// A simple trait for representing binary hash functions.
57+
pub trait BinaryHash {
58+
/// Given a bit-string, produce a 32-bit hash.
59+
fn hash(input: &[u8]) -> [u8; 32];
60+
61+
/// An optional specialization of `hash` where there are two 32-byte inputs, left and right.
62+
fn hash2_32_concat(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
63+
let mut buf = [0u8; 64];
64+
buf[0..32].copy_from_slice(left);
65+
buf[32..64].copy_from_slice(right);
66+
Self::hash(&buf)
67+
}
68+
}
69+
70+
/// A node and value hasher constructed from a simple binary hasher.
71+
///
72+
/// This implements a [`ValueHasher`] and [`NodeHasher`] where the node kind is tagged by setting
73+
/// or unsetting the MSB of the hash value.
74+
///
75+
/// The binary hash wrapped by this structure must behave approximately like a random oracle over
76+
/// the space 2^256, i.e. all 256 bit outputs are valid and inputs are uniformly distributed.
77+
///
78+
/// Functions like Sha2/Blake3/Keccak/Groestl all meet these criteria.
79+
pub struct BinaryHasher<H>(std::marker::PhantomData<H>);
80+
81+
impl<H: BinaryHash> ValueHasher for BinaryHasher<H> {
82+
fn hash_value(value: &[u8]) -> [u8; 32] {
83+
H::hash(value)
84+
}
85+
}
86+
87+
impl<H: BinaryHash> NodeHasher for BinaryHasher<H> {
88+
fn hash_leaf(data: &LeafData) -> [u8; 32] {
89+
let mut h = H::hash2_32_concat(&data.key_path, &data.value_hash);
90+
set_msb(&mut h);
91+
h
92+
}
93+
94+
fn hash_internal(data: &InternalData) -> [u8; 32] {
95+
let mut h = H::hash2_32_concat(&data.left, &data.right);
96+
unset_msb(&mut h);
97+
h
98+
}
99+
100+
fn node_kind(node: &Node) -> NodeKind {
101+
node_kind_by_msb(node)
102+
}
103+
}
104+
105+
#[cfg(any(feature = "blake3-hasher", test))]
106+
pub use blake3::Blake3Hasher;
107+
108+
/// A node hasher making use of blake3.
109+
#[cfg(any(feature = "blake3-hasher", test))]
110+
pub mod blake3 {
111+
use super::{BinaryHash, BinaryHasher};
112+
113+
/// A [`BinaryHash`] implementation for Blake3.
114+
pub struct Blake3BinaryHasher;
115+
116+
/// An implementation of a [`crate::trie::NodeHasher`] and [`crate::trie::ValueHasher`]
117+
/// using blake3.
118+
pub type Blake3Hasher = BinaryHasher<Blake3BinaryHasher>;
119+
120+
impl BinaryHash for Blake3BinaryHasher {
121+
fn hash(value: &[u8]) -> [u8; 32] {
122+
blake3::hash(value).into()
123+
}
124+
125+
fn hash2_32_concat(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
126+
let mut hasher = blake3::Hasher::new();
127+
hasher.update(left);
128+
hasher.update(right);
129+
hasher.finalize().into()
130+
}
131+
}
132+
}

core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
extern crate alloc;
1212

13+
pub mod hasher;
1314
pub mod page;
1415
pub mod page_id;
1516
pub mod proof;

core/src/proof/multi_proof.rs

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
//! the inclusion of all provided path proofs.
44
55
use crate::{
6+
hasher::NodeHasher,
67
proof::{
78
path_proof::{hash_path, shared_bits},
89
KeyOutOfScope, PathProof, PathProofTerminal,
910
},
10-
trie::{InternalData, KeyPath, LeafData, Node, NodeHasher, TERMINATOR},
11+
trie::{InternalData, KeyPath, LeafData, Node, TERMINATOR},
1112
};
1213

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

499500
use crate::{
501+
hasher::{Blake3Hasher, NodeHasher},
500502
proof::{PathProof, PathProofTerminal},
501-
trie::{self, InternalData, LeafData, Node, NodeHasher, NodeKind, TERMINATOR},
503+
trie::{InternalData, LeafData, TERMINATOR},
502504
trie_pos::TriePosition,
503505
};
504506

@@ -884,43 +886,6 @@ mod tests {
884886
);
885887
}
886888

887-
/// Hash nodes with blake3.
888-
pub struct Blake3Hasher;
889-
890-
impl NodeHasher for Blake3Hasher {
891-
fn hash_leaf(data: &trie::LeafData) -> [u8; 32] {
892-
let mut hasher = blake3::Hasher::new();
893-
hasher.update(&data.key_path);
894-
hasher.update(&data.value_hash);
895-
let mut hash: [u8; 32] = hasher.finalize().into();
896-
897-
// Label with MSB
898-
hash[0] |= 0b10000000;
899-
hash
900-
}
901-
902-
fn hash_internal(data: &trie::InternalData) -> [u8; 32] {
903-
let mut hasher = blake3::Hasher::new();
904-
hasher.update(&data.left);
905-
hasher.update(&data.right);
906-
let mut hash: [u8; 32] = hasher.finalize().into();
907-
908-
// Label with MSB
909-
hash[0] &= 0b01111111;
910-
hash
911-
}
912-
913-
fn node_kind(node: &Node) -> NodeKind {
914-
if node[0] >> 7 == 1 {
915-
NodeKind::Leaf
916-
} else if node == &TERMINATOR {
917-
NodeKind::Terminator
918-
} else {
919-
NodeKind::Internal
920-
}
921-
}
922-
}
923-
924889
#[test]
925890
pub fn test_verify_multiproof_two_leafs() {
926891
// root

core/src/proof/path_proof.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Proving and verifying inclusion, non-inclusion, and updates to the trie.
22
3-
use crate::trie::{self, InternalData, KeyPath, LeafData, Node, NodeHasher, NodeKind, TERMINATOR};
3+
use crate::hasher::NodeHasher;
4+
use crate::trie::{self, InternalData, KeyPath, LeafData, Node, NodeKind, TERMINATOR};
45
use crate::trie_pos::TriePosition;
56

67
use bitvec::prelude::*;

core/src/trie.rs

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
//!
1414
//! All node preimages are 512 bits.
1515
16+
use crate::hasher::NodeHasher;
17+
1618
/// A node in the binary trie. In this schema, it is always 256 bits and is the hash of either
1719
/// an [`LeafData`] or [`InternalData`], or zeroed if it's a [`TERMINATOR`].
1820
///
@@ -90,25 +92,3 @@ pub struct LeafData {
9092
/// The hash of the value carried in this leaf.
9193
pub value_hash: ValueHash,
9294
}
93-
94-
/// A trie node hash function specialized for 64 bytes of data.
95-
///
96-
/// Note that it is illegal for the produced hash to equal [0; 32], as this value is reserved
97-
/// for the terminator node.
98-
///
99-
/// A node hasher should domain-separate internal and leaf nodes in some specific way. The
100-
/// recommended approach for binary hashes is to set the MSB to 0 or 1 depending on the node kind.
101-
/// However, for other kinds of hashes (e.g. Poseidon2 or other algebraic hashes), other labeling
102-
/// schemes may be required.
103-
pub trait NodeHasher {
104-
/// Hash a leaf. This should domain-separate the hash
105-
/// according to the node kind.
106-
fn hash_leaf(data: &LeafData) -> [u8; 32];
107-
108-
/// Hash an internal node. This should domain-separate
109-
/// the hash according to the node kind.
110-
fn hash_internal(data: &InternalData) -> [u8; 32];
111-
112-
/// Get the kind of the given node.
113-
fn node_kind(node: &Node) -> NodeKind;
114-
}

core/src/update.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Trie update logic helpers.
22
3-
use crate::trie::{self, KeyPath, LeafData, Node, NodeHasher, ValueHash};
3+
use crate::hasher::NodeHasher;
4+
use crate::trie::{self, KeyPath, LeafData, Node, ValueHash};
45

56
use bitvec::prelude::*;
67

examples/commit_batch/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use anyhow::Result;
2-
use nomt::{Blake3Hasher, KeyReadWrite, Nomt, Options, Root, SessionParams, Witness, WitnessMode};
2+
use nomt::{
3+
hasher::Blake3Hasher, KeyReadWrite, Nomt, Options, Root, SessionParams, Witness, WitnessMode,
4+
};
35
use sha2::Digest;
46

57
const NOMT_DB_FOLDER: &str = "nomt_db";

examples/read_value/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::Result;
2-
use nomt::{Blake3Hasher, KeyReadWrite, Nomt, Options, SessionParams, WitnessMode};
2+
use nomt::{hasher::Blake3Hasher, KeyReadWrite, Nomt, Options, SessionParams, WitnessMode};
33
use sha2::Digest;
44

55
const NOMT_DB_FOLDER: &str = "nomt_db";

examples/witness_verification/src/main.rs

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,5 @@
11
use anyhow::Result;
2-
use nomt_core::{
3-
proof,
4-
trie::{self, LeafData, Node, NodeHasher, NodeKind, TERMINATOR},
5-
};
6-
7-
// Hash nodes using blake3. The Hasher below will be utilized in the witness
8-
// verification stage and must conform to the hash used in the commitment
9-
// phase and subsequently during witness creation
10-
pub struct Blake3Hasher;
11-
12-
impl NodeHasher for Blake3Hasher {
13-
fn hash_leaf(data: &trie::LeafData) -> [u8; 32] {
14-
let mut hasher = blake3::Hasher::new();
15-
hasher.update(&data.key_path);
16-
hasher.update(&data.value_hash);
17-
let mut hash: [u8; 32] = hasher.finalize().into();
18-
19-
// Label with MSB
20-
hash[0] |= 0b10000000;
21-
hash
22-
}
23-
24-
fn hash_internal(data: &trie::InternalData) -> [u8; 32] {
25-
let mut hasher = blake3::Hasher::new();
26-
hasher.update(&data.left);
27-
hasher.update(&data.right);
28-
let mut hash: [u8; 32] = hasher.finalize().into();
29-
30-
// Label with MSB
31-
hash[0] &= 0b01111111;
32-
hash
33-
}
34-
35-
fn node_kind(node: &Node) -> NodeKind {
36-
if node[0] >> 7 == 1 {
37-
NodeKind::Leaf
38-
} else if node == &TERMINATOR {
39-
NodeKind::Terminator
40-
} else {
41-
NodeKind::Internal
42-
}
43-
}
44-
}
2+
use nomt_core::{hasher::Blake3Hasher, proof, trie::LeafData};
453

464
fn main() -> Result<()> {
475
// The witness produced in the example `commit_batch` will be used

fuzz/fuzz_targets/api_surface.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use arbitrary::Arbitrary;
66
use libfuzzer_sys::fuzz_target;
77

88
use nomt::{
9-
trie::KeyPath, Blake3Hasher, KeyReadWrite, Nomt, Options, SessionParams, Value, WitnessMode,
9+
hasher::Blake3Hasher, trie::KeyPath, KeyReadWrite, Nomt, Options, SessionParams, Value,
10+
WitnessMode,
1011
};
1112

1213
fuzz_target!(|run: Run| {

nomt/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ license.workspace = true
1212

1313
[dependencies]
1414
anyhow = { version = "1.0.81", features = ["backtrace"] }
15-
nomt-core = { path = "../core", features = ["std"] }
15+
nomt-core = { path = "../core", default-features = false, features = ["std"] }
1616
parking_lot = { version = "0.12.3", features = ["arc_lock", "send_guard"] }
1717
threadpool = "1.8.1"
1818
bitvec = { version = "1" }
@@ -55,6 +55,8 @@ name = "beatree"
5555
harness = false
5656

5757
[features]
58+
default = ["blake3-hasher"]
5859
benchmarks = ["dep:criterion"]
5960
fuzz = []
6061
borsh = ["dep:borsh", "nomt-core/borsh"]
62+
blake3-hasher = ["nomt-core/blake3-hasher"]

0 commit comments

Comments
 (0)