1use crate::merkle::{Bagging, Error, Family, Location, Position};
4use alloc::vec::Vec;
5use commonware_cryptography::{Digest, Hasher as CHasher};
6use core::marker::PhantomData;
7
8pub trait Hasher<F: Family>: Clone + Send + Sync {
14 type Digest: Digest;
15
16 fn hash<'a>(&self, parts: impl IntoIterator<Item = &'a [u8]>) -> Self::Digest;
21
22 fn root_bagging(&self) -> Bagging;
25
26 fn node_digest(
28 &self,
29 pos: Position<F>,
30 left: &Self::Digest,
31 right: &Self::Digest,
32 ) -> Self::Digest {
33 self.hash([
34 (*pos).to_be_bytes().as_slice(),
35 left.as_ref(),
36 right.as_ref(),
37 ])
38 }
39
40 fn leaf_digest(&self, pos: Position<F>, element: &[u8]) -> Self::Digest {
42 self.hash([(*pos).to_be_bytes().as_slice(), element])
43 }
44
45 fn digest(&self, data: &[u8]) -> Self::Digest {
47 self.hash(core::iter::once(data))
48 }
49
50 fn fold(&self, acc: &Self::Digest, peak: &Self::Digest) -> Self::Digest {
52 self.hash([acc.as_ref(), peak.as_ref()])
53 }
54
55 fn root<'a, I>(
62 &self,
63 leaves: Location<F>,
64 inactive_peaks: usize,
65 peak_digests: I,
66 ) -> Result<Self::Digest, Error<F>>
67 where
68 I: IntoIterator<Item = &'a Self::Digest>,
69 I::IntoIter: ExactSizeIterator,
70 {
71 let iter = peak_digests.into_iter();
72 let peaks = iter.len();
73 self.root_with_folded_peaks(leaves, inactive_peaks, inactive_peaks, iter)
74 .ok_or(Error::InvalidInactivePeaks {
75 requested: inactive_peaks,
76 peaks,
77 })
78 }
79
80 fn root_with_folded_peaks<'a>(
93 &self,
94 leaves: Location<F>,
95 inactive_peaks_to_fold: usize,
96 committed_inactive_peaks: usize,
97 peak_digests: impl IntoIterator<Item = &'a Self::Digest>,
98 ) -> Option<Self::Digest> {
99 let mut peak_digests = peak_digests.into_iter();
100 let Some(first) = peak_digests.next() else {
101 return (inactive_peaks_to_fold == 0 && committed_inactive_peaks == 0)
102 .then(|| self.digest(&(*leaves).to_be_bytes()));
103 };
104
105 let mut acc = *first;
106 for _ in 0..inactive_peaks_to_fold.saturating_sub(1) {
107 let peak = peak_digests.next()?;
108 acc = self.fold(&acc, peak);
109 }
110
111 let folded_peaks = match self.root_bagging() {
112 Bagging::ForwardFold => {
113 for peak in peak_digests {
114 acc = self.fold(&acc, peak);
115 }
116 acc
117 }
118 Bagging::BackwardFold => {
119 let (lower, upper) = peak_digests.size_hint();
120 let mut active_peaks = Vec::with_capacity(1 + upper.unwrap_or(lower));
121 active_peaks.push(acc);
122 active_peaks.extend(peak_digests.copied());
123
124 let mut acc = *active_peaks.last().unwrap();
125 for peak in active_peaks.iter().rev().skip(1) {
126 acc = self.fold(peak, &acc);
127 }
128 acc
129 }
130 };
131
132 if committed_inactive_peaks == 0 {
133 Some(self.hash([(*leaves).to_be_bytes().as_slice(), folded_peaks.as_ref()]))
134 } else {
135 Some(self.hash([
136 (*leaves).to_be_bytes().as_slice(),
137 (committed_inactive_peaks as u64).to_be_bytes().as_slice(),
138 folded_peaks.as_ref(),
139 ]))
140 }
141 }
142}
143
144#[derive(Clone)]
151pub struct Standard<H: CHasher> {
152 _hasher: PhantomData<H>,
153 bagging: Bagging,
154}
155
156impl<H: CHasher> Standard<H> {
157 pub const fn new(bagging: Bagging) -> Self {
159 Self {
160 _hasher: PhantomData,
161 bagging,
162 }
163 }
164
165 pub const fn root_bagging(&self) -> Bagging {
167 self.bagging
168 }
169
170 pub fn hash<'a>(&self, parts: impl IntoIterator<Item = &'a [u8]>) -> H::Digest {
172 let mut h = H::new();
173 for part in parts {
174 h.update(part);
175 }
176 h.finalize()
177 }
178
179 pub fn digest(&self, data: &[u8]) -> H::Digest {
181 self.hash(core::iter::once(data))
182 }
183}
184
185impl<F: Family, H: CHasher> Hasher<F> for Standard<H> {
186 type Digest = H::Digest;
187
188 fn hash<'a>(&self, parts: impl IntoIterator<Item = &'a [u8]>) -> H::Digest {
189 Self::hash(self, parts)
190 }
191
192 fn root_bagging(&self) -> Bagging {
193 Self::root_bagging(self)
194 }
195}
196
197impl<F: Family, T: Hasher<F>> Hasher<F> for &T {
200 type Digest = T::Digest;
201
202 fn hash<'a>(&self, parts: impl IntoIterator<Item = &'a [u8]>) -> Self::Digest {
203 (**self).hash(parts)
204 }
205
206 fn root_bagging(&self) -> Bagging {
207 (**self).root_bagging()
208 }
209
210 fn node_digest(
211 &self,
212 pos: Position<F>,
213 left: &Self::Digest,
214 right: &Self::Digest,
215 ) -> Self::Digest {
216 (**self).node_digest(pos, left, right)
217 }
218
219 fn leaf_digest(&self, pos: Position<F>, element: &[u8]) -> Self::Digest {
220 (**self).leaf_digest(pos, element)
221 }
222
223 fn digest(&self, data: &[u8]) -> Self::Digest {
224 (**self).digest(data)
225 }
226
227 fn fold(&self, acc: &Self::Digest, peak: &Self::Digest) -> Self::Digest {
228 (**self).fold(acc, peak)
229 }
230
231 fn root<'a, I>(
232 &self,
233 leaves: Location<F>,
234 inactive_peaks: usize,
235 peak_digests: I,
236 ) -> Result<Self::Digest, Error<F>>
237 where
238 I: IntoIterator<Item = &'a Self::Digest>,
239 I::IntoIter: ExactSizeIterator,
240 {
241 (**self).root(leaves, inactive_peaks, peak_digests)
242 }
243
244 fn root_with_folded_peaks<'a>(
245 &self,
246 leaves: Location<F>,
247 inactive_peaks_to_fold: usize,
248 committed_inactive_peaks: usize,
249 peak_digests: impl IntoIterator<Item = &'a Self::Digest>,
250 ) -> Option<Self::Digest> {
251 (**self).root_with_folded_peaks(
252 leaves,
253 inactive_peaks_to_fold,
254 committed_inactive_peaks,
255 peak_digests,
256 )
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::merkle::{
264 mmr::{Location, Position, StandardHasher as Standard},
265 Bagging::{BackwardFold, ForwardFold},
266 };
267 use alloc::vec::Vec;
268 use commonware_cryptography::{sha256, Hasher as CHasher, Sha256};
269
270 #[test]
271 fn test_leaf_digest_sha256() {
272 test_leaf_digest::<Sha256>();
273 }
274
275 #[test]
276 fn test_node_digest_sha256() {
277 test_node_digest::<Sha256>();
278 }
279
280 #[test]
281 fn test_root_sha256() {
282 test_root::<Sha256>();
283 }
284
285 #[test]
286 fn test_invalid_inactive_prefix_returns_err() {
287 let mmr_hasher: Standard<Sha256> = Standard::new(BackwardFold);
288 let d1 = test_digest::<Sha256>(1);
289 let d2 = test_digest::<Sha256>(2);
290 let digests = [d1, d2];
291
292 assert!(matches!(
293 <Standard<Sha256> as Hasher<crate::merkle::mmr::Family>>::root(
294 &mmr_hasher,
295 Location::new(2),
296 3,
297 digests.iter()
298 ),
299 Err(crate::merkle::Error::InvalidInactivePeaks {
300 requested: 3,
301 peaks: 2
302 })
303 ));
304 assert!(
305 <Standard<Sha256> as Hasher<crate::merkle::mmr::Family>>::root_with_folded_peaks(
306 &mmr_hasher,
307 Location::new(2),
308 3,
309 3,
310 digests.iter()
311 )
312 .is_none()
313 );
314 assert!(matches!(
315 <Standard<Sha256> as Hasher<crate::merkle::mmr::Family>>::root(
316 &mmr_hasher,
317 Location::new(0),
318 1,
319 Vec::<sha256::Digest>::new().iter()
320 ),
321 Err(crate::merkle::Error::InvalidInactivePeaks {
322 requested: 1,
323 peaks: 0
324 })
325 ));
326 }
327
328 fn test_digest<H: CHasher>(value: u8) -> H::Digest {
329 H::hash(&[value])
330 }
331
332 fn test_leaf_digest<H: CHasher>() {
333 let mmr_hasher: Standard<H> = Standard::new(ForwardFold);
334 let digest1 = test_digest::<H>(1);
335 let digest2 = test_digest::<H>(2);
336
337 let out = mmr_hasher.leaf_digest(Position::new(0), &digest1);
338 assert_ne!(out, test_digest::<H>(0), "hash should be non-zero");
339
340 let mut out2 = mmr_hasher.leaf_digest(Position::new(0), &digest1);
341 assert_eq!(out, out2, "hash should be re-computed consistently");
342
343 out2 = mmr_hasher.leaf_digest(Position::new(1), &digest1);
344 assert_ne!(out, out2, "hash should change with different pos");
345
346 out2 = mmr_hasher.leaf_digest(Position::new(0), &digest2);
347 assert_ne!(out, out2, "hash should change with different input digest");
348 }
349
350 fn test_node_digest<H: CHasher>() {
351 let mmr_hasher: Standard<H> = Standard::new(ForwardFold);
352
353 let d1 = test_digest::<H>(1);
354 let d2 = test_digest::<H>(2);
355 let d3 = test_digest::<H>(3);
356
357 let out = mmr_hasher.node_digest(Position::new(0), &d1, &d2);
358 assert_ne!(out, test_digest::<H>(0), "hash should be non-zero");
359
360 let mut out2 = mmr_hasher.node_digest(Position::new(0), &d1, &d2);
361 assert_eq!(out, out2, "hash should be re-computed consistently");
362
363 out2 = mmr_hasher.node_digest(Position::new(1), &d1, &d2);
364 assert_ne!(out, out2, "hash should change with different pos");
365
366 out2 = mmr_hasher.node_digest(Position::new(0), &d3, &d2);
367 assert_ne!(
368 out, out2,
369 "hash should change with different first input hash"
370 );
371
372 out2 = mmr_hasher.node_digest(Position::new(0), &d1, &d3);
373 assert_ne!(
374 out, out2,
375 "hash should change with different second input hash"
376 );
377
378 out2 = mmr_hasher.node_digest(Position::new(0), &d2, &d1);
379 assert_ne!(
380 out, out2,
381 "hash should change when swapping order of inputs"
382 );
383 }
384
385 fn test_root<H: CHasher>() {
386 let mmr_hasher: Standard<H> = Standard::new(ForwardFold);
387 let d1 = test_digest::<H>(1);
388 let d2 = test_digest::<H>(2);
389 let d3 = test_digest::<H>(3);
390 let d4 = test_digest::<H>(4);
391
392 let empty_vec: Vec<H::Digest> = Vec::new();
393 let empty_out = mmr_hasher
394 .root(Location::new(0), 0, empty_vec.iter())
395 .expect("zero inactive peaks is always valid");
396 assert_ne!(
397 empty_out,
398 test_digest::<H>(0),
399 "root of empty MMR should be non-zero"
400 );
401 assert_eq!(
403 empty_out,
404 mmr_hasher
405 .root(Location::new(0), 0, empty_vec.iter())
406 .expect("zero inactive peaks is always valid")
407 );
408
409 let digests = [d1, d2, d3, d4];
410 let out = mmr_hasher
411 .root(Location::new(10), 0, digests.iter())
412 .expect("zero inactive peaks is always valid");
413 assert_ne!(out, test_digest::<H>(0), "root should be non-zero");
414 assert_ne!(out, empty_out, "root should differ from empty MMR");
415
416 let mut out2 = mmr_hasher
417 .root(Location::new(10), 0, digests.iter())
418 .expect("zero inactive peaks is always valid");
419 assert_eq!(out, out2, "root should be computed consistently");
420
421 out2 = mmr_hasher
422 .root(Location::new(11), 0, digests.iter())
423 .expect("zero inactive peaks is always valid");
424 assert_ne!(out, out2, "root should change with different position");
425
426 let digests = [d1, d2, d4, d3];
427 out2 = mmr_hasher
428 .root(Location::new(10), 0, digests.iter())
429 .expect("zero inactive peaks is always valid");
430 assert_ne!(out, out2, "root should change with different digest order");
431
432 let digests = [d1, d2, d3];
433 out2 = mmr_hasher
434 .root(Location::new(10), 0, digests.iter())
435 .expect("zero inactive peaks is always valid");
436 assert_ne!(
437 out, out2,
438 "root should change with different number of hashes"
439 );
440 }
441}