commonware_storage/mmr/
hasher.rs

1//! Decorator for a cryptographic hasher that implements the MMR-specific hashing logic.
2
3use super::Position;
4use commonware_cryptography::Hasher as CHasher;
5
6/// A trait for computing the various digests of an MMR.
7pub trait Hasher<H: CHasher>: Send + Sync {
8    /// Computes the digest for a leaf given its position and the element it represents.
9    fn leaf_digest(&mut self, pos: Position, element: &[u8]) -> H::Digest;
10
11    /// Computes the digest for a node given its position and the digests of its children.
12    fn node_digest(&mut self, pos: Position, left: &H::Digest, right: &H::Digest) -> H::Digest;
13
14    /// Computes the root for an MMR given its size and an iterator over the digests of its peaks in
15    /// decreasing order of height.
16    fn root<'a>(
17        &mut self,
18        size: Position,
19        peak_digests: impl Iterator<Item = &'a H::Digest>,
20    ) -> H::Digest;
21
22    /// Compute the digest of a byte slice.
23    fn digest(&mut self, data: &[u8]) -> H::Digest;
24
25    /// Access the inner [CHasher] hasher.
26    fn inner(&mut self) -> &mut H;
27
28    /// Fork the hasher to provide equivalent functionality in another thread. This is different
29    /// than [Clone::clone] because the forked hasher need not be a deep copy, and may share non-mutable
30    /// state with the hasher from which it was forked.
31    fn fork(&self) -> impl Hasher<H>;
32}
33
34/// The standard hasher to use with an MMR for computing leaf, node and root digests. Leverages no
35/// external data.
36pub struct Standard<H: CHasher> {
37    hasher: H,
38}
39
40impl<H: CHasher> Standard<H> {
41    /// Creates a new [Standard] hasher.
42    pub fn new() -> Self {
43        Self { hasher: H::new() }
44    }
45
46    pub fn update_with_pos(&mut self, pos: Position) {
47        let pos = *pos;
48        self.hasher.update(&pos.to_be_bytes());
49    }
50
51    pub fn update_with_digest(&mut self, digest: &H::Digest) {
52        self.hasher.update(digest.as_ref());
53    }
54
55    pub fn update_with_element(&mut self, element: &[u8]) {
56        self.hasher.update(element);
57    }
58
59    pub fn finalize(&mut self) -> H::Digest {
60        self.hasher.finalize()
61    }
62}
63
64impl<H: CHasher> Default for Standard<H> {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl<H: CHasher> Hasher<H> for Standard<H> {
71    fn inner(&mut self) -> &mut H {
72        &mut self.hasher
73    }
74
75    fn fork(&self) -> impl Hasher<H> {
76        Standard { hasher: H::new() }
77    }
78
79    fn leaf_digest(&mut self, pos: Position, element: &[u8]) -> H::Digest {
80        self.update_with_pos(pos);
81        self.update_with_element(element);
82        self.finalize()
83    }
84
85    fn node_digest(&mut self, pos: Position, left: &H::Digest, right: &H::Digest) -> H::Digest {
86        self.update_with_pos(pos);
87        self.update_with_digest(left);
88        self.update_with_digest(right);
89        self.finalize()
90    }
91
92    fn root<'a>(
93        &mut self,
94        size: Position,
95        peak_digests: impl Iterator<Item = &'a H::Digest>,
96    ) -> H::Digest {
97        self.hasher.update(&size.to_be_bytes());
98        for digest in peak_digests {
99            self.update_with_digest(digest);
100        }
101        self.finalize()
102    }
103
104    fn digest(&mut self, data: &[u8]) -> H::Digest {
105        self.hasher.update(data);
106        self.finalize()
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::mmr::{mem::Mmr, Position};
114    use alloc::vec::Vec;
115    use commonware_cryptography::{Hasher as CHasher, Sha256};
116
117    #[test]
118    fn test_leaf_digest_sha256() {
119        test_leaf_digest::<Sha256>();
120    }
121
122    #[test]
123    fn test_node_digest_sha256() {
124        test_node_digest::<Sha256>();
125    }
126
127    #[test]
128    fn test_root_sha256() {
129        test_root::<Sha256>();
130    }
131
132    fn test_digest<H: CHasher>(value: u8) -> H::Digest {
133        let mut hasher = H::new();
134        hasher.update(&[value]);
135        hasher.finalize()
136    }
137
138    fn test_leaf_digest<H: CHasher>() {
139        let mut mmr_hasher: Standard<H> = Standard::new();
140        // input hashes to use
141        let digest1 = test_digest::<H>(1);
142        let digest2 = test_digest::<H>(2);
143
144        let out = mmr_hasher.leaf_digest(Position::new(0), &digest1);
145        assert_ne!(out, test_digest::<H>(0), "hash should be non-zero");
146
147        let mut out2 = mmr_hasher.leaf_digest(Position::new(0), &digest1);
148        assert_eq!(out, out2, "hash should be re-computed consistently");
149
150        out2 = mmr_hasher.leaf_digest(Position::new(1), &digest1);
151        assert_ne!(out, out2, "hash should change with different pos");
152
153        out2 = mmr_hasher.leaf_digest(Position::new(0), &digest2);
154        assert_ne!(out, out2, "hash should change with different input digest");
155    }
156
157    fn test_node_digest<H: CHasher>() {
158        let mut mmr_hasher: Standard<H> = Standard::new();
159        // input hashes to use
160
161        let d1 = test_digest::<H>(1);
162        let d2 = test_digest::<H>(2);
163        let d3 = test_digest::<H>(3);
164
165        let out = mmr_hasher.node_digest(Position::new(0), &d1, &d2);
166        assert_ne!(out, test_digest::<H>(0), "hash should be non-zero");
167
168        let mut out2 = mmr_hasher.node_digest(Position::new(0), &d1, &d2);
169        assert_eq!(out, out2, "hash should be re-computed consistently");
170
171        out2 = mmr_hasher.node_digest(Position::new(1), &d1, &d2);
172        assert_ne!(out, out2, "hash should change with different pos");
173
174        out2 = mmr_hasher.node_digest(Position::new(0), &d3, &d2);
175        assert_ne!(
176            out, out2,
177            "hash should change with different first input hash"
178        );
179
180        out2 = mmr_hasher.node_digest(Position::new(0), &d1, &d3);
181        assert_ne!(
182            out, out2,
183            "hash should change with different second input hash"
184        );
185
186        out2 = mmr_hasher.node_digest(Position::new(0), &d2, &d1);
187        assert_ne!(
188            out, out2,
189            "hash should change when swapping order of inputs"
190        );
191    }
192
193    fn test_root<H: CHasher>() {
194        let mut mmr_hasher: Standard<H> = Standard::new();
195        // input digests to use
196        let d1 = test_digest::<H>(1);
197        let d2 = test_digest::<H>(2);
198        let d3 = test_digest::<H>(3);
199        let d4 = test_digest::<H>(4);
200
201        let empty_vec: Vec<H::Digest> = Vec::new();
202        let empty_out = mmr_hasher.root(Position::new(0), empty_vec.iter());
203        assert_ne!(
204            empty_out,
205            test_digest::<H>(0),
206            "root of empty MMR should be non-zero"
207        );
208        assert_eq!(empty_out, Mmr::empty_mmr_root(mmr_hasher.inner()));
209
210        let digests = [d1, d2, d3, d4];
211        let out = mmr_hasher.root(Position::new(10), digests.iter());
212        assert_ne!(out, test_digest::<H>(0), "root should be non-zero");
213        assert_ne!(out, empty_out, "root should differ from empty MMR");
214
215        let mut out2 = mmr_hasher.root(Position::new(10), digests.iter());
216        assert_eq!(out, out2, "root should be computed consistently");
217
218        out2 = mmr_hasher.root(Position::new(11), digests.iter());
219        assert_ne!(out, out2, "root should change with different position");
220
221        let digests = [d1, d2, d4, d3];
222        out2 = mmr_hasher.root(Position::new(10), digests.iter());
223        assert_ne!(out, out2, "root should change with different digest order");
224
225        let digests = [d1, d2, d3];
226        out2 = mmr_hasher.root(Position::new(10), digests.iter());
227        assert_ne!(
228            out, out2,
229            "root should change with different number of hashes"
230        );
231    }
232}