ic_stable_memory/utils/
certification.rs

1use candid::{encode_one, CandidType};
2use serde::{ser::SerializeSeq, Serialize, Serializer};
3use serde_bytes::Bytes;
4use sha2::{Digest, Sha256};
5use std::mem;
6
7/// Handy alias to [u8; 32]
8pub type Hash = [u8; 32];
9
10/// Constant for zeroed [Hash].
11///
12/// **Different from [empty()]**
13pub const EMPTY_HASH: Hash = [0u8; 32];
14
15/// Same as [Dfinity's HashTree](https://sdk.dfinity.org/docs/interface-spec/index.html#_certificate),
16/// but works with owned values, instead of references.
17#[derive(Debug, Clone)]
18pub enum HashTree {
19    #[doc(hidden)]
20    Empty,
21    #[doc(hidden)]
22    Fork(Box<(HashTree, HashTree)>),
23    #[doc(hidden)]
24    Labeled(Vec<u8>, Box<HashTree>),
25    #[doc(hidden)]
26    Leaf(Vec<u8>),
27    #[doc(hidden)]
28    Pruned(Hash),
29}
30
31/// Merges two [HashTree]s together. Useful when you need a proof like "has A, but not B".
32pub fn merge_hash_trees(lhs: HashTree, rhs: HashTree) -> HashTree {
33    use HashTree::{Empty, Fork, Labeled, Leaf, Pruned};
34
35    match (lhs, rhs) {
36        (Pruned(l), Pruned(r)) => {
37            if l != r {
38                panic!("merge_hash_trees: inconsistent hashes");
39            }
40            Pruned(l)
41        }
42        (Pruned(_), r) => r,
43        (l, Pruned(_)) => l,
44        (Fork(l), Fork(r)) => Fork(Box::new((
45            merge_hash_trees(l.0, r.0),
46            merge_hash_trees(l.1, r.1),
47        ))),
48        (Labeled(l_label, l), Labeled(r_label, r)) => {
49            if l_label != r_label {
50                panic!("merge_hash_trees: inconsistent hash tree labels");
51            }
52            Labeled(l_label, Box::new(merge_hash_trees(*l, *r)))
53        }
54        (Empty, Empty) => Empty,
55        (Leaf(l), Leaf(r)) => {
56            if l != r {
57                panic!("merge_hash_trees: inconsistent leaves");
58            }
59            Leaf(l)
60        }
61        (_l, _r) => {
62            panic!("merge_hash_trees: inconsistent tree structure");
63        }
64    }
65}
66
67/// Performs left-to-right tree traversal of a [HashTree], executing a custom lambda on each node.
68pub fn traverse_hashtree<Fn: FnMut(&HashTree)>(tree: &HashTree, f: &mut Fn) {
69    f(tree);
70
71    match tree {
72        HashTree::Empty => {}
73        HashTree::Pruned(_) => {}
74        HashTree::Fork(x) => {
75            let a = &x.0;
76            let b = &x.1;
77
78            traverse_hashtree(a, f);
79            traverse_hashtree(b, f);
80        }
81        HashTree::Labeled(_, x) => {
82            traverse_hashtree(x, f);
83        }
84        HashTree::Leaf(_) => {}
85    }
86}
87
88#[doc(hidden)]
89pub fn empty() -> HashTree {
90    HashTree::Empty
91}
92#[doc(hidden)]
93pub fn fork(l: HashTree, r: HashTree) -> HashTree {
94    HashTree::Fork(Box::new((l, r)))
95}
96#[doc(hidden)]
97pub fn labeled(l: Vec<u8>, t: HashTree) -> HashTree {
98    HashTree::Labeled(l, Box::new(t))
99}
100#[doc(hidden)]
101pub fn leaf(val: Vec<u8>) -> HashTree {
102    HashTree::Leaf(val)
103}
104#[doc(hidden)]
105pub fn pruned(h: Hash) -> HashTree {
106    HashTree::Pruned(h)
107}
108
109/// Allows prettier forking at a small runtime cost
110///
111/// See also [HashForker]
112///
113/// # Example
114/// ```rust
115/// # use ic_stable_memory::utils::certification::{empty, WitnessForker};
116/// # let subtree_1 = empty();
117/// # let subtree_2 = empty();
118///
119/// let mut witness = WitnessForker::default();
120/// witness.fork_with(subtree_1);
121/// witness.fork_with(subtree_2);
122///
123/// let hash_tree = witness.finish();
124/// ```
125pub struct WitnessForker(HashTree);
126
127impl Default for WitnessForker {
128    fn default() -> Self {
129        Self(HashTree::Empty)
130    }
131}
132
133impl WitnessForker {
134    #[doc(hidden)]
135    #[inline]
136    pub fn fork_with(&mut self, rh: HashTree) {
137        match &mut self.0 {
138            HashTree::Empty => {
139                self.0 = rh;
140            }
141            it => {
142                let lh = mem::replace(it, HashTree::Empty);
143                *it = fork(lh, rh);
144            }
145        }
146    }
147
148    #[doc(hidden)]
149    #[inline]
150    pub fn finish(self) -> HashTree {
151        self.0
152    }
153}
154
155/// Same as [WitnessForker], but for hashes.
156pub struct HashForker(Hash);
157
158impl Default for HashForker {
159    fn default() -> Self {
160        Self(EMPTY_HASH)
161    }
162}
163
164impl HashForker {
165    #[doc(hidden)]
166    #[inline]
167    pub fn fork_with(&mut self, rh: Hash) {
168        if self.0 == EMPTY_HASH {
169            self.0 = rh;
170        } else {
171            self.0 = fork_hash(&self.0, &rh);
172        }
173    }
174
175    #[doc(hidden)]
176    #[inline]
177    pub fn finish(self) -> Hash {
178        if self.0 == EMPTY_HASH {
179            empty_hash()
180        } else {
181            self.0
182        }
183    }
184}
185
186#[doc(hidden)]
187pub fn fork_hash(l: &Hash, r: &Hash) -> Hash {
188    let mut h = domain_sep("ic-hashtree-fork");
189    h.update(&l[..]);
190    h.update(&r[..]);
191    h.finalize().into()
192}
193
194#[doc(hidden)]
195pub fn leaf_hash(data: &[u8]) -> Hash {
196    let mut h = domain_sep("ic-hashtree-leaf");
197    h.update(data);
198    h.finalize().into()
199}
200
201#[doc(hidden)]
202pub fn labeled_hash(label: &[u8], content_hash: &Hash) -> Hash {
203    let mut h = domain_sep("ic-hashtree-labeled");
204    h.update(label);
205    h.update(&content_hash[..]);
206    h.finalize().into()
207}
208
209#[doc(hidden)]
210pub fn empty_hash() -> Hash {
211    domain_sep("ic-hashtree-empty").finalize().into()
212}
213
214impl HashTree {
215    /// Recalculates the root hash of this [HashTree]
216    pub fn reconstruct(&self) -> Hash {
217        match self {
218            Self::Empty => empty_hash(),
219            Self::Fork(f) => fork_hash(&f.0.reconstruct(), &f.1.reconstruct()),
220            Self::Labeled(l, t) => {
221                let thash = t.reconstruct();
222                labeled_hash(l, &thash)
223            }
224            Self::Leaf(data) => leaf_hash(data),
225            Self::Pruned(h) => *h,
226        }
227    }
228}
229
230impl Serialize for HashTree {
231    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
232    where
233        S: Serializer,
234    {
235        match self {
236            HashTree::Empty => {
237                let mut seq = serializer.serialize_seq(Some(1))?;
238                seq.serialize_element(&0u8)?;
239                seq.end()
240            }
241            HashTree::Fork(p) => {
242                let mut seq = serializer.serialize_seq(Some(3))?;
243                seq.serialize_element(&1u8)?;
244                seq.serialize_element(&p.0)?;
245                seq.serialize_element(&p.1)?;
246                seq.end()
247            }
248            HashTree::Labeled(label, tree) => {
249                let mut seq = serializer.serialize_seq(Some(3))?;
250                seq.serialize_element(&2u8)?;
251                seq.serialize_element(Bytes::new(label))?;
252                seq.serialize_element(&tree)?;
253                seq.end()
254            }
255            HashTree::Leaf(leaf_bytes) => {
256                let mut seq = serializer.serialize_seq(Some(2))?;
257                seq.serialize_element(&3u8)?;
258                seq.serialize_element(Bytes::new(leaf_bytes.as_ref()))?;
259                seq.end()
260            }
261            HashTree::Pruned(digest) => {
262                let mut seq = serializer.serialize_seq(Some(2))?;
263                seq.serialize_element(&4u8)?;
264                seq.serialize_element(Bytes::new(&digest[..]))?;
265                seq.end()
266            }
267        }
268    }
269}
270
271fn domain_sep(s: &str) -> Sha256 {
272    let buf: [u8; 1] = [s.len() as u8];
273    let mut h = Sha256::new();
274    h.update(&buf[..]);
275    h.update(s.as_bytes());
276
277    h
278}
279
280/// Trait that is used to serialize labels of a [HashTree] into bytes.
281///
282/// See also [SCertifiedBTreeMap](crate::collections::SCertifiedBTreeMap)
283pub trait AsHashableBytes {
284    #[doc(hidden)]
285    fn as_hashable_bytes(&self) -> Vec<u8>;
286}
287
288impl AsHashableBytes for Hash {
289    #[inline]
290    fn as_hashable_bytes(&self) -> Vec<u8> {
291        self.to_vec()
292    }
293}
294
295impl AsHashableBytes for () {
296    #[inline]
297    fn as_hashable_bytes(&self) -> Vec<u8> {
298        Vec::new()
299    }
300}
301
302/// Trait that is used to hash a leaf value of a [HashTree].
303///
304/// This trait should **always** be implemented on user-side.
305///
306/// See also [SCertifiedBTreeMap](crate::collections::SCertifiedBTreeMap)
307pub trait AsHashTree {
308    /// Returns the root hash of the tree without constructing it.
309    /// Must be equivalent to [HashTree::reconstruct].
310    fn root_hash(&self) -> Hash;
311
312    /// Returns a [HashTree] of this value. Must be equivalent to [AsHashTree::root_hash].
313    fn hash_tree(&self) -> HashTree;
314}
315
316impl AsHashTree for () {
317    fn root_hash(&self) -> Hash {
318        empty_hash()
319    }
320
321    fn hash_tree(&self) -> HashTree {
322        empty()
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use crate::utils::certification::{
329        domain_sep, empty, fork, fork_hash, labeled, labeled_hash, leaf, leaf_hash, pruned, Hash,
330        EMPTY_HASH,
331    };
332    use serde_test::{assert_ser_tokens, Token};
333    use sha2::Digest;
334
335    #[test]
336    fn test() {
337        let k1 = 1u64;
338        let v1 = 10u64;
339
340        let k2 = 2u64;
341        let v2 = 20u64;
342
343        let wit = fork(
344            pruned(labeled_hash(
345                &k1.to_le_bytes(),
346                &leaf_hash(&v1.to_le_bytes()),
347            )),
348            labeled(k2.to_le_bytes().to_vec(), leaf(v2.to_le_bytes().to_vec())),
349        );
350
351        let root_hash = fork_hash(
352            &labeled_hash(&k1.to_le_bytes(), &leaf_hash(&v1.to_le_bytes())),
353            &labeled_hash(&k2.to_le_bytes(), &leaf_hash(&v2.to_le_bytes())),
354        );
355
356        assert_eq!(wit.reconstruct(), root_hash);
357
358        let wit = fork(
359            labeled(k1.to_le_bytes().to_vec(), leaf(v1.to_le_bytes().to_vec())),
360            pruned(labeled_hash(
361                &k2.to_le_bytes(),
362                &leaf_hash(&v2.to_le_bytes()),
363            )),
364        );
365
366        assert_eq!(wit.reconstruct(), root_hash);
367    }
368
369    #[test]
370    fn works_fine() {
371        let e: Hash = domain_sep("ic-hashtree-empty").finalize().into();
372        assert_eq!(empty().reconstruct(), e);
373    }
374
375    const c: [u8; 10] = [0u8; 10];
376
377    #[test]
378    fn ser_works_fine() {
379        let w1 = empty();
380        let w2 = fork(empty(), empty());
381        let w3 = labeled(vec![0u8; 10], empty());
382        let w4 = leaf(vec![0u8; 10]);
383        let w5 = pruned(EMPTY_HASH);
384
385        assert_ser_tokens(
386            &w1,
387            &[Token::Seq { len: Some(1) }, Token::U8(0), Token::SeqEnd],
388        );
389
390        assert_ser_tokens(
391            &w2,
392            &[
393                Token::Seq { len: Some(3) },
394                Token::U8(1),
395                Token::Seq { len: Some(1) },
396                Token::U8(0),
397                Token::SeqEnd,
398                Token::Seq { len: Some(1) },
399                Token::U8(0),
400                Token::SeqEnd,
401                Token::SeqEnd,
402            ],
403        );
404
405        assert_ser_tokens(
406            &w3,
407            &[
408                Token::Seq { len: Some(3) },
409                Token::U8(2),
410                Token::Bytes(&c),
411                Token::Seq { len: Some(1) },
412                Token::U8(0),
413                Token::SeqEnd,
414                Token::SeqEnd,
415            ],
416        );
417
418        assert_ser_tokens(
419            &w4,
420            &[
421                Token::Seq { len: Some(2) },
422                Token::U8(3),
423                Token::Bytes(&c),
424                Token::SeqEnd,
425            ],
426        );
427
428        assert_ser_tokens(
429            &w5,
430            &[
431                Token::Seq { len: Some(2) },
432                Token::U8(4),
433                Token::Bytes(&EMPTY_HASH),
434                Token::SeqEnd,
435            ],
436        );
437    }
438}