miden_protocol/asset/vault/partial.rs
1use alloc::string::ToString;
2
3use miden_crypto::merkle::smt::{PartialSmt, SmtLeaf, SmtProof};
4use miden_crypto::merkle::{InnerNodeInfo, MerkleError};
5
6use super::{AssetVault, AssetVaultKey};
7use crate::Word;
8use crate::asset::{Asset, AssetWitness};
9use crate::errors::PartialAssetVaultError;
10use crate::utils::serde::{
11 ByteReader,
12 ByteWriter,
13 Deserializable,
14 DeserializationError,
15 Serializable,
16};
17
18/// A partial representation of an [`AssetVault`], containing only proofs for a subset of assets.
19///
20/// Partial vault is used to provide verifiable access to specific assets in a vault
21/// without the need to provide the full vault data. It contains all required data for loading
22/// vault data into the transaction kernel for transaction execution.
23#[derive(Clone, Debug, PartialEq, Eq, Default)]
24pub struct PartialVault {
25 /// An SMT with a partial view into an account's full [`AssetVault`].
26 partial_smt: PartialSmt,
27}
28
29impl PartialVault {
30 // CONSTRUCTORS
31 // --------------------------------------------------------------------------------------------
32
33 /// Constructs a [`PartialVault`] from an [`AssetVault`] root.
34 ///
35 /// For conversion from an [`AssetVault`], prefer [`Self::new_minimal`] to be more explicit.
36 pub fn new(root: Word) -> Self {
37 PartialVault { partial_smt: PartialSmt::new(root) }
38 }
39
40 /// Converts an [`AssetVault`] into a partial vault representation.
41 ///
42 /// The resulting [`PartialVault`] will contain the _full_ merkle paths of the original asset
43 /// vault.
44 pub fn new_full(vault: AssetVault) -> Self {
45 let partial_smt = PartialSmt::from(vault.asset_tree);
46
47 PartialVault { partial_smt }
48 }
49
50 /// Converts an [`AssetVault`] into a partial vault representation.
51 ///
52 /// The resulting [`PartialVault`] will represent the root of the asset vault, but not track any
53 /// key-value pairs, which means it is the most _minimal_ representation of the asset vault.
54 pub fn new_minimal(vault: &AssetVault) -> Self {
55 PartialVault::new(vault.root())
56 }
57
58 // ACCESSORS
59 // --------------------------------------------------------------------------------------------
60
61 /// Returns the root of the partial vault.
62 pub fn root(&self) -> Word {
63 self.partial_smt.root()
64 }
65
66 /// Returns an iterator over all inner nodes in the Sparse Merkle Tree proofs.
67 ///
68 /// This is useful for reconstructing parts of the Sparse Merkle Tree or for
69 /// verification purposes.
70 pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
71 self.partial_smt.inner_nodes()
72 }
73
74 /// Returns an iterator over all leaves in the Sparse Merkle Tree proofs.
75 ///
76 /// Each item returned is a tuple containing the leaf index and a reference to the leaf.
77 pub fn leaves(&self) -> impl Iterator<Item = &SmtLeaf> {
78 self.partial_smt.leaves().map(|(_, leaf)| leaf)
79 }
80
81 /// Returns an opening of the leaf associated with `vault_key`.
82 ///
83 /// The `vault_key` can be obtained with [`Asset::vault_key`].
84 ///
85 /// # Errors
86 ///
87 /// Returns an error if:
88 /// - the key is not tracked by this partial vault.
89 pub fn open(&self, vault_key: AssetVaultKey) -> Result<AssetWitness, PartialAssetVaultError> {
90 let smt_proof = self
91 .partial_smt
92 .open(&vault_key.into())
93 .map_err(PartialAssetVaultError::UntrackedAsset)?;
94 // SAFETY: The partial vault should only contain valid assets.
95 Ok(AssetWitness::new_unchecked(smt_proof))
96 }
97
98 /// Returns the [`Asset`] associated with the given `vault_key`.
99 ///
100 /// The return value is `None` if the asset does not exist in the vault.
101 ///
102 /// # Errors
103 ///
104 /// Returns an error if:
105 /// - the key is not tracked by this partial SMT.
106 pub fn get(&self, vault_key: AssetVaultKey) -> Result<Option<Asset>, MerkleError> {
107 self.partial_smt.get_value(&vault_key.into()).map(|asset_value| {
108 if asset_value.is_empty() {
109 None
110 } else {
111 // SAFETY: If this returned a non-empty word, then it should be a valid asset,
112 // because the vault should only track valid ones.
113 Some(
114 Asset::from_key_value(vault_key, asset_value)
115 .expect("partial vault should only track valid assets"),
116 )
117 }
118 })
119 }
120
121 // MUTATORS
122 // --------------------------------------------------------------------------------------------
123
124 /// Adds an [`AssetWitness`] to this [`PartialVault`].
125 ///
126 /// # Errors
127 ///
128 /// Returns an error if:
129 /// - the new root after the insertion of the leaf and the path does not match the existing root
130 /// (except when the first leaf is added).
131 pub fn add(&mut self, witness: AssetWitness) -> Result<(), PartialAssetVaultError> {
132 let proof = SmtProof::from(witness);
133 self.partial_smt
134 .add_proof(proof)
135 .map_err(PartialAssetVaultError::FailedToAddProof)
136 }
137
138 // HELPER FUNCTIONS
139 // --------------------------------------------------------------------------------------------
140
141 /// Validates that the provided entries are valid vault keys and assets.
142 ///
143 /// For brevity, the error conditions are only mentioned on the public methods that use this
144 /// function.
145 fn validate_entries<'a>(
146 entries: impl IntoIterator<Item = &'a (Word, Word)>,
147 ) -> Result<(), PartialAssetVaultError> {
148 for (vault_key, asset_value) in entries {
149 // This ensures that vault key and value are consistent.
150 Asset::from_key_value_words(*vault_key, *asset_value).map_err(|source| {
151 PartialAssetVaultError::InvalidAssetInSmt { entry: *asset_value, source }
152 })?;
153 }
154
155 Ok(())
156 }
157}
158
159impl TryFrom<PartialSmt> for PartialVault {
160 type Error = PartialAssetVaultError;
161
162 /// Returns a new instance of a partial vault from the provided partial SMT.
163 ///
164 /// # Errors
165 ///
166 /// Returns an error if:
167 /// - the provided SMT does not track only valid [`Asset`]s.
168 /// - the vault key at which the asset is stored does not match the vault key derived from the
169 /// asset.
170 fn try_from(partial_smt: PartialSmt) -> Result<Self, Self::Error> {
171 Self::validate_entries(partial_smt.entries())?;
172
173 Ok(PartialVault { partial_smt })
174 }
175}
176
177impl Serializable for PartialVault {
178 fn write_into<W: ByteWriter>(&self, target: &mut W) {
179 target.write(&self.partial_smt)
180 }
181}
182
183impl Deserializable for PartialVault {
184 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
185 let partial_smt: PartialSmt = source.read()?;
186
187 PartialVault::try_from(partial_smt)
188 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
189 }
190}
191
192// TESTS
193// ================================================================================================
194
195#[cfg(test)]
196mod tests {
197 use assert_matches::assert_matches;
198 use miden_crypto::merkle::smt::Smt;
199
200 use super::*;
201 use crate::asset::FungibleAsset;
202
203 #[test]
204 fn partial_vault_ensures_asset_validity() -> anyhow::Result<()> {
205 let invalid_asset = Word::from([0, 0, 0, 5u32]);
206 let smt = Smt::with_entries([(invalid_asset, invalid_asset)])?;
207 let proof = smt.open(&invalid_asset);
208 let partial_smt = PartialSmt::from_proofs([proof.clone()])?;
209
210 let err = PartialVault::try_from(partial_smt).unwrap_err();
211 assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { entry, .. } => {
212 assert_eq!(entry, invalid_asset);
213 });
214
215 Ok(())
216 }
217
218 #[test]
219 fn partial_vault_ensures_asset_vault_key_matches() -> anyhow::Result<()> {
220 let asset = FungibleAsset::mock(500);
221 let invalid_vault_key = Word::from([0, 1, 2, 3u32]);
222 let smt = Smt::with_entries([(invalid_vault_key, asset.to_value_word())])?;
223 let proof = smt.open(&invalid_vault_key);
224 let partial_smt = PartialSmt::from_proofs([proof.clone()])?;
225
226 let err = PartialVault::try_from(partial_smt).unwrap_err();
227 assert_matches!(err, PartialAssetVaultError::InvalidAssetInSmt { .. });
228
229 Ok(())
230 }
231}