Skip to main content

vid/utils/
blake3.rs

1//! BLAKE3-native plumbing for [`jf_merkle_tree::MerkleTree`].
2//!
3//! `jf_merkle_tree::hasher::GenericHasherMerkleTree<H, ...>` requires
4//! `H: digest::Digest` (the RustCrypto trait). The BLAKE3 crate only
5//! implements that trait via its `traits-preview` feature, which is pinned
6//! to a specific `digest` crate version — it lags behind blake3's release
7//! cadence and forces the rest of the workspace onto an old `blake3` line.
8//!
9//! These helpers let us drive `MerkleTree` directly with `blake3`'s native
10//! API.
11use ark_serialize::{
12    CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError, Valid, Validate,
13};
14use ark_std::io::{Read, Write};
15use jf_merkle_tree::{DigestAlgorithm, errors::MerkleTreeError};
16use tagged_base64::tagged;
17
18/// Domain separators copied from `jf_merkle_tree::prelude` (which keeps them
19/// `pub(crate)`).
20const LEAF_HASH_DOM_SEP: &[u8; 1] = b"1";
21const INTERNAL_HASH_DOM_SEP: &[u8; 1] = b"0";
22
23/// 32-byte BLAKE3 hash, suitable as a `jf_merkle_tree` `NodeValue`.
24#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd)]
25#[tagged("HASH")]
26#[repr(transparent)]
27pub struct Blake3Node([u8; 32]);
28
29impl Blake3Node {
30    /// Construct a node directly from its 32-byte payload.
31    pub fn new(bytes: [u8; 32]) -> Self {
32        Self(bytes)
33    }
34}
35
36impl From<blake3::Hash> for Blake3Node {
37    fn from(h: blake3::Hash) -> Self {
38        Self(*h.as_bytes())
39    }
40}
41
42impl AsRef<[u8]> for Blake3Node {
43    fn as_ref(&self) -> &[u8] {
44        &self.0
45    }
46}
47
48impl AsRef<[u8; 32]> for Blake3Node {
49    fn as_ref(&self) -> &[u8; 32] {
50        &self.0
51    }
52}
53
54impl CanonicalSerialize for Blake3Node {
55    fn serialize_with_mode<W: Write>(
56        &self,
57        mut writer: W,
58        _compress: Compress,
59    ) -> Result<(), SerializationError> {
60        writer.write_all(&self.0)?;
61        Ok(())
62    }
63
64    fn serialized_size(&self, _compress: Compress) -> usize {
65        32
66    }
67}
68
69impl CanonicalDeserialize for Blake3Node {
70    fn deserialize_with_mode<R: Read>(
71        mut reader: R,
72        _compress: Compress,
73        _validate: Validate,
74    ) -> Result<Self, SerializationError> {
75        let mut buf = [0u8; 32];
76        reader.read_exact(&mut buf)?;
77        Ok(Self(buf))
78    }
79}
80
81impl Valid for Blake3Node {
82    fn check(&self) -> Result<(), SerializationError> {
83        Ok(())
84    }
85}
86
87/// `DigestAlgorithm` for [`Blake3Node`] using `blake3`'s native API.
88pub struct Blake3DigestAlgorithm;
89
90impl<E, I> DigestAlgorithm<E, I, Blake3Node> for Blake3DigestAlgorithm
91where
92    E: jf_merkle_tree::Element + CanonicalSerialize,
93    I: jf_merkle_tree::Index + CanonicalSerialize,
94{
95    fn digest(data: &[Blake3Node]) -> Result<Blake3Node, MerkleTreeError> {
96        let mut hasher = blake3::Hasher::new();
97        hasher.update(INTERNAL_HASH_DOM_SEP);
98        for v in data {
99            hasher.update(&v.0);
100        }
101        Ok(Blake3Node::from(hasher.finalize()))
102    }
103
104    fn digest_leaf(pos: &I, elem: &E) -> Result<Blake3Node, MerkleTreeError> {
105        let mut hasher = blake3::Hasher::new();
106        hasher.update(LEAF_HASH_DOM_SEP);
107        let mut adapter = HasherWriter(&mut hasher);
108        pos.serialize_uncompressed(&mut adapter)
109            .map_err(|_| MerkleTreeError::DigestError("Failed serializing pos".into()))?;
110        elem.serialize_uncompressed(&mut adapter)
111            .map_err(|_| MerkleTreeError::DigestError("Failed serializing elem".into()))?;
112        Ok(Blake3Node::from(hasher.finalize()))
113    }
114}
115
116/// `ark_std::io::Write` adapter that forwards writes into a `blake3::Hasher`.
117///
118/// Used for `digest_leaf`, which serializes via `CanonicalSerialize` — that
119/// trait writes through `ark_std::io::Write`, which `blake3::Hasher` does
120/// not implement directly.
121struct HasherWriter<'a>(&'a mut blake3::Hasher);
122
123impl Write for HasherWriter<'_> {
124    fn write(&mut self, buf: &[u8]) -> ark_std::io::Result<usize> {
125        self.0.update(buf);
126        Ok(buf.len())
127    }
128
129    fn flush(&mut self) -> ark_std::io::Result<()> {
130        Ok(())
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use jf_merkle_tree::{MerkleTreeScheme, append_only::MerkleTree as JfMerkleTree};
137
138    use super::*;
139
140    type Mt = JfMerkleTree<Blake3Node, Blake3DigestAlgorithm, u64, 4, Blake3Node>;
141
142    fn leaf(i: u8) -> Blake3Node {
143        let mut buf = [0u8; 32];
144        buf[0] = i;
145        Blake3Node::new(buf)
146    }
147
148    /// Building the same leaves twice yields the same commitment.
149    #[test]
150    fn deterministic_commitment() {
151        let leaves: Vec<Blake3Node> = (0u8..16).map(leaf).collect();
152        let a = Mt::from_elems(None, &leaves).unwrap().commitment();
153        let b = Mt::from_elems(None, &leaves).unwrap().commitment();
154        assert_eq!(a, b);
155    }
156
157    /// Round-trip a generated proof through `MerkleTree::verify`.
158    #[test]
159    fn proof_round_trip() {
160        let leaves: Vec<Blake3Node> = (0u8..16).map(leaf).collect();
161        let mt = Mt::from_elems(None, &leaves).unwrap();
162        let commit = mt.commitment();
163        for k in 0u64..16 {
164            let (val, proof) = mt.lookup(k).expect_ok().unwrap();
165            assert_eq!(*val, leaves[k as usize]);
166            assert!(Mt::verify(commit, k, *val, &proof).unwrap().is_ok());
167        }
168    }
169
170    /// Tampering with a leaf rejects the proof.
171    #[test]
172    fn proof_rejects_tampered_leaf() {
173        let leaves: Vec<Blake3Node> = (0u8..16).map(leaf).collect();
174        let mt = Mt::from_elems(None, &leaves).unwrap();
175        let commit = mt.commitment();
176        let (_, proof) = mt.lookup(0).expect_ok().unwrap();
177        let bad = leaf(99);
178        assert!(Mt::verify(commit, 0u64, bad, &proof).unwrap().is_err());
179    }
180
181    /// CanonicalSerialize round-trip preserves bytes.
182    #[test]
183    fn canonical_serialize_round_trip() {
184        let n = Blake3Node::new([7u8; 32]);
185        let mut buf = Vec::new();
186        n.serialize_uncompressed(&mut buf).unwrap();
187        assert_eq!(buf.len(), 32);
188        assert_eq!(buf, vec![7u8; 32]);
189        let n2 = Blake3Node::deserialize_uncompressed(&buf[..]).unwrap();
190        assert_eq!(n, n2);
191    }
192}