miden_objects/asset/vault/
partial.rs

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