warg_transparency/map/
map.rs

1use core::fmt::{Debug, Formatter};
2use std::marker::PhantomData;
3
4use warg_crypto::hash::{Hash, SupportedDigest};
5use warg_crypto::VisitBytes;
6
7use super::link::Link;
8use super::node::Node;
9use super::path::Path;
10use super::proof::Proof;
11
12/// Immutable Map w/ Inclusion Proofs
13///
14/// # Usage
15///
16/// Using a [`Map`] should feel similar to using a `HashMap`. All maps begin
17/// by creating an empty map and then populating it with items. Each time you
18/// insert to a map, it creates a new map derived from the prior map. Once
19/// items are inserted, you can get an inclusion proof that demonstrates the
20/// presence of a key/value in a tree.
21///
22/// ```rust
23/// use warg_transparency::map::Map;
24/// use sha2::Sha256;
25///
26/// let a = Map::<Sha256, &str, &str>::default();
27/// let b = a.insert("foo", "bar");
28/// let c = b.extend([("baz", "bat"), ("foo", "qux")]);
29///
30/// let proof = c.prove(&"foo").unwrap();
31/// assert_eq!(c.root().clone(), proof.evaluate(&"foo", &"qux"));
32/// ```
33///
34/// # Design
35///
36/// A [`Map`] is a key/value store backed by a Merkle tree. Its values are
37/// stored in a binary tree. A cryptographic hash function is applied to the
38/// key and its resulting bits are interpreted as a path to descend the tree.
39/// This implies that the depth of the tree is `n` where `n` is the number of
40/// bits in the cryptographic hash function. Because this would cause the tree
41/// to have `2^(n+1) - 1` entries (far too many!), the tree is sparse and
42/// only creates nodes as necessary to represent the contents in the tree.
43///
44/// ## Hashing Strategy
45///
46/// ### Leaf Nodes
47///
48/// Leaf node hashes are calculated using the one-byte `0` prefix to prevent collisions with branch hashes.
49///
50/// ```hash
51/// hash(Leaf(value)) = hash(0x00 || <value>)
52/// ```
53///
54/// For leaf nodes that semantically "contain no value", the `<value>` provided is empty i.e. the zero-length byte sequence.
55///
56///
57/// ### Branch Nodes
58///
59/// Branch node hashes are calculated using the one-byte `1` prefix to prevent collisions with branch hashes.
60/// ```hash
61/// hash(Branch(left, right)) = hash(0x01 || hash(<left>) || hash(<right>))
62/// ```
63pub struct Map<D, K, V>
64where
65    D: SupportedDigest,
66    K: VisitBytes + Clone,
67    V: VisitBytes + Clone,
68{
69    link: Link<D>,
70    len: usize,
71    _key: PhantomData<K>,
72    _value: PhantomData<V>,
73}
74
75impl<D, K, V> Map<D, K, V>
76where
77    D: SupportedDigest,
78    K: VisitBytes + Clone,
79    V: VisitBytes + Clone,
80{
81    pub(crate) fn new(link: Link<D>, len: usize) -> Self {
82        Self {
83            link,
84            len,
85            _key: PhantomData,
86            _value: PhantomData,
87        }
88    }
89}
90
91impl<D, K, V> Clone for Map<D, K, V>
92where
93    D: SupportedDigest,
94    K: VisitBytes + Clone,
95    V: VisitBytes + Clone,
96{
97    fn clone(&self) -> Self {
98        Self {
99            link: self.link.clone(),
100            len: self.len,
101            _key: PhantomData,
102            _value: PhantomData,
103        }
104    }
105}
106
107impl<D, K, V> Default for Map<D, K, V>
108where
109    D: SupportedDigest,
110    K: VisitBytes + Clone,
111    V: VisitBytes + Clone,
112{
113    fn default() -> Self {
114        Self {
115            link: Link::new(Node::Empty(256)),
116            len: 0,
117            _key: PhantomData,
118            _value: PhantomData,
119        }
120    }
121}
122
123impl<D, K, V> Eq for Map<D, K, V>
124where
125    D: SupportedDigest,
126    K: VisitBytes + Clone,
127    V: VisitBytes + Clone,
128{
129}
130
131impl<D, K, V> PartialEq for Map<D, K, V>
132where
133    D: SupportedDigest,
134    K: VisitBytes + Clone,
135    V: VisitBytes + Clone,
136{
137    fn eq(&self, other: &Self) -> bool {
138        self.link.hash() == other.link.hash()
139    }
140}
141
142impl<D, K, V> core::hash::Hash for Map<D, K, V>
143where
144    D: SupportedDigest,
145    K: VisitBytes + Clone,
146    V: VisitBytes + Clone,
147{
148    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
149        self.link.hash().hash(state);
150    }
151}
152
153impl<D, K, V> Debug for Map<D, K, V>
154where
155    D: SupportedDigest,
156    K: VisitBytes + Clone,
157    V: VisitBytes + Clone,
158{
159    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
160        f.write_fmt(format_args!("Map({:?})", self.link.hash()))
161    }
162}
163
164impl<D, K, V> Map<D, K, V>
165where
166    D: SupportedDigest,
167    K: VisitBytes + Clone + ?Sized,
168    V: VisitBytes + Clone,
169{
170    /// The hash of the root of the tree.
171    ///
172    /// This uniquely identifies the map and its contents.
173    pub fn root(&self) -> &Hash<D> {
174        self.link.hash()
175    }
176
177    /// The number of items in the map.
178    pub fn len(&self) -> usize {
179        self.len
180    }
181
182    /// Whether or not the map is empty.
183    pub fn is_empty(&self) -> bool {
184        self.len == 0
185    }
186
187    /// Gets the value for a given key and a proof of its presence in this map.
188    pub fn prove(&self, key: K) -> Option<Proof<D, K, V>>
189where {
190        self.link.node().prove(Path::new(&Hash::of(key)))
191    }
192
193    /// Insert a value into the map, creating a new map.
194    ///
195    /// This replaces any existing items with the same key.
196    pub fn insert(&self, key: K, val: V) -> Self {
197        let key_hash = Hash::<D>::of(&key);
198        let mut path: Path<'_, D> = Path::new(&key_hash);
199        let (node, new) = self.link.node().insert(&mut path, hash_leaf(val));
200        Self::new(Link::new(node), self.len + usize::from(new))
201    }
202
203    /// Inserts all key/value pairs into the map, creating a new map.
204    pub fn extend(&self, iter: impl IntoIterator<Item = (K, V)>) -> Self {
205        let mut here = self.clone();
206
207        for (key, val) in iter {
208            let key_hash = Hash::<D>::of(&key);
209            let mut path: Path<'_, D> = Path::new(&key_hash);
210            let (node, new) = here.link.node().insert(&mut path, hash_leaf(val));
211            here = Self::new(Link::new(node), here.len + usize::from(new));
212        }
213
214        here
215    }
216}
217
218// If updating this function, also update `hash_empty` in crypto crate
219/// Compute the hash for an empty leaf using a given Digest algorithm.
220#[allow(dead_code)]
221pub(crate) fn hash_empty<D: SupportedDigest>() -> Hash<D> {
222    hash_leaf(())
223}
224
225// No associated function exists in crypto crate today, but in the event that one exists
226// update this function if updating the other
227/// Hashes a leaf node. See [Map] docs for more detail.
228pub(crate) fn hash_leaf<D, V>(value: V) -> Hash<D>
229where
230    D: SupportedDigest,
231    V: VisitBytes,
232{
233    Hash::of(&(0b0, value))
234}
235
236// If updating this function, also update `hash_branch` in crypto crate
237/// Hashes a branch node. See [Map] docs for more detail.
238pub(crate) fn hash_branch<D>(lhs: &Hash<D>, rhs: &Hash<D>) -> Hash<D>
239where
240    D: SupportedDigest,
241{
242    Hash::of((0b1, lhs, rhs))
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use warg_crypto::hash::Sha256;
249    #[test]
250    fn empty_map() {
251        let map: Map<Sha256, &str, &str> = Map::default();
252        assert_eq!(Sha256::empty_tree_hash(256), map.link.hash());
253    }
254}