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