Skip to main content

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}