Skip to main content

miden_protocol/account/storage/map/
partial.rs

1use alloc::collections::BTreeMap;
2
3use miden_core::utils::{Deserializable, Serializable};
4use miden_crypto::Word;
5use miden_crypto::merkle::smt::{LeafIndex, PartialSmt, SMT_DEPTH, SmtLeaf, SmtProof};
6use miden_crypto::merkle::{InnerNodeInfo, MerkleError};
7
8use crate::account::{StorageMap, StorageMapKey, StorageMapWitness};
9use crate::utils::serde::{ByteReader, DeserializationError};
10
11/// A partial representation of a [`StorageMap`], containing only proofs for a subset of the
12/// key-value pairs.
13///
14/// A partial storage map carries only the Merkle authentication data a transaction will need.
15/// Every included entry pairs a value with its proof, letting the transaction kernel verify reads
16/// (and prepare writes) without needing the complete tree.
17///
18/// ## Guarantees
19///
20/// This type guarantees that the raw key-value pairs it contains are all present in the
21/// contained partial SMT. Note that the inverse is not necessarily true. The SMT may contain more
22/// entries than the map because to prove inclusion of a given raw key A an
23/// [`SmtLeaf::Multiple`] may be present that contains both keys hash(A) and hash(B). However, B may
24/// not be present in the key-value pairs and this is a valid state.
25#[derive(Clone, Debug, PartialEq, Eq, Default)]
26pub struct PartialStorageMap {
27    partial_smt: PartialSmt,
28    /// The entries of the map that retains the original unhashed keys (i.e. [`StorageMapKey`]).
29    ///
30    /// It is an invariant of this type that the map's entries are always consistent with the
31    /// partial SMT's entries and vice-versa.
32    entries: BTreeMap<StorageMapKey, Word>,
33}
34
35impl PartialStorageMap {
36    // CONSTRUCTORS
37    // --------------------------------------------------------------------------------------------
38
39    /// Constructs a [`PartialStorageMap`] from a [`StorageMap`] root.
40    ///
41    /// For conversion from a [`StorageMap`], prefer [`Self::new_minimal`] to be more explicit.
42    pub fn new(root: Word) -> Self {
43        PartialStorageMap {
44            partial_smt: PartialSmt::new(root),
45            entries: BTreeMap::new(),
46        }
47    }
48
49    /// Returns a new instance of a [`PartialStorageMap`] with all provided witnesses added to it.
50    pub fn with_witnesses(
51        witnesses: impl IntoIterator<Item = StorageMapWitness>,
52    ) -> Result<Self, MerkleError> {
53        let mut map = BTreeMap::new();
54
55        let partial_smt = PartialSmt::from_proofs(witnesses.into_iter().map(|witness| {
56            map.extend(witness.entries());
57            SmtProof::from(witness)
58        }))?;
59
60        Ok(PartialStorageMap { partial_smt, entries: map })
61    }
62
63    /// Converts a [`StorageMap`] into a partial storage representation.
64    ///
65    /// The resulting [`PartialStorageMap`] will contain the _full_ entries and merkle paths of the
66    /// original storage map.
67    pub fn new_full(storage_map: StorageMap) -> Self {
68        let partial_smt = PartialSmt::from(storage_map.smt);
69        let entries = storage_map.entries;
70
71        PartialStorageMap { partial_smt, entries }
72    }
73
74    /// Converts a [`StorageMap`] into a partial storage representation.
75    ///
76    /// The resulting [`PartialStorageMap`] will represent the root of the storage map, but not
77    /// track any key-value pairs, which means it is the most _minimal_ representation of the
78    /// storage map.
79    pub fn new_minimal(storage_map: &StorageMap) -> Self {
80        Self::new(storage_map.root())
81    }
82
83    // ACCESSORS
84    // --------------------------------------------------------------------------------------------
85
86    /// Returns a reference to the underlying [`PartialSmt`].
87    pub fn partial_smt(&self) -> &PartialSmt {
88        &self.partial_smt
89    }
90
91    /// Returns the root of the underlying [`PartialSmt`].
92    pub fn root(&self) -> Word {
93        self.partial_smt.root()
94    }
95
96    /// Looks up the provided key in this map and returns:
97    /// - a non-empty [`Word`] if the key is tracked by this map and exists in it,
98    /// - [`Word::empty`] if the key is tracked by this map and does not exist,
99    /// - `None` if the key is not tracked by this map.
100    pub fn get(&self, key: &StorageMapKey) -> Option<Word> {
101        let hash_word = key.hash().as_word();
102        // This returns an error if the key is not tracked which we map to a `None`.
103        self.partial_smt.get_value(&hash_word).ok()
104    }
105
106    /// Returns an opening of the leaf associated with the given key.
107    ///
108    /// Conceptually, an opening is a Merkle path to the leaf, as well as the leaf itself.
109    ///
110    /// # Errors
111    ///
112    /// Returns an error if:
113    /// - the key is not tracked by this partial storage map.
114    pub fn open(&self, key: &StorageMapKey) -> Result<StorageMapWitness, MerkleError> {
115        let smt_proof = self.partial_smt.open(&key.hash().as_word())?;
116        let value = self.entries.get(key).copied().unwrap_or_default();
117
118        // SAFETY: The key value pair is guaranteed to be present in the provided proof since we
119        // open its hashed version and because of the guarantees of the partial storage map.
120        Ok(StorageMapWitness::new_unchecked(smt_proof, [(*key, value)]))
121    }
122
123    // ITERATORS
124    // --------------------------------------------------------------------------------------------
125
126    /// Returns an iterator over the leaves of the underlying [`PartialSmt`].
127    pub fn leaves(&self) -> impl Iterator<Item = (LeafIndex<SMT_DEPTH>, &SmtLeaf)> {
128        self.partial_smt.leaves()
129    }
130
131    /// Returns an iterator over the key-value pairs in this storage map.
132    pub fn entries(&self) -> impl Iterator<Item = (&StorageMapKey, &Word)> {
133        self.entries.iter()
134    }
135
136    /// Returns an iterator over the inner nodes of the underlying [`PartialSmt`].
137    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
138        self.partial_smt.inner_nodes()
139    }
140
141    // MUTATORS
142    // --------------------------------------------------------------------------------------------
143
144    /// Adds a [`StorageMapWitness`] for the specific key-value pair to this [`PartialStorageMap`].
145    pub fn add(&mut self, witness: StorageMapWitness) -> Result<(), MerkleError> {
146        self.entries.extend(witness.entries().map(|(key, value)| (*key, *value)));
147        self.partial_smt.add_proof(SmtProof::from(witness))
148    }
149}
150
151impl Serializable for PartialStorageMap {
152    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
153        target.write(&self.partial_smt);
154        target.write_usize(self.entries.len());
155        target.write_many(self.entries.keys());
156    }
157}
158
159impl Deserializable for PartialStorageMap {
160    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
161        let mut map = BTreeMap::new();
162
163        let partial_smt: PartialSmt = source.read()?;
164        let num_entries: usize = source.read()?;
165
166        for _ in 0..num_entries {
167            let key: StorageMapKey = source.read()?;
168            let hashed_map_key: Word = key.hash().into();
169            let value = partial_smt.get_value(&hashed_map_key).map_err(|err| {
170                DeserializationError::InvalidValue(format!(
171                    "failed to find map key {key} in partial SMT: {err}"
172                ))
173            })?;
174            map.insert(key, value);
175        }
176
177        Ok(PartialStorageMap { partial_smt, entries: map })
178    }
179}