miden_objects/asset/vault/
asset_witness.rs

1use miden_crypto::merkle::{InnerNodeInfo, SmtLeaf, SmtProof};
2
3use super::vault_key::AssetVaultKey;
4use crate::AssetError;
5use crate::asset::Asset;
6
7/// A witness of an asset in an [`AssetVault`](super::AssetVault).
8///
9/// It proves inclusion of a certain asset in the vault.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct AssetWitness(SmtProof);
12
13impl AssetWitness {
14    // CONSTRUCTORS
15    // --------------------------------------------------------------------------------------------
16
17    /// Creates a new [`AssetWitness`] from an SMT proof.
18    ///
19    /// # Errors
20    ///
21    /// Returns an error if:
22    /// - any of the entries in the SMT leaf is not a valid asset.
23    /// - any of the entries' vault keys does not match the expected vault key of the asset.
24    pub fn new(smt_proof: SmtProof) -> Result<Self, AssetError> {
25        for (vault_key, asset) in smt_proof.leaf().entries() {
26            let asset = Asset::try_from(asset)?;
27            if *vault_key != asset.vault_key().into() {
28                return Err(AssetError::AssetVaultKeyMismatch {
29                    actual: *vault_key,
30                    expected: asset.vault_key().into(),
31                });
32            }
33        }
34
35        Ok(Self(smt_proof))
36    }
37
38    /// Creates a new [`AssetWitness`] from an SMT proof without checking that the proof contains
39    /// valid assets.
40    ///
41    /// Prefer [`AssetWitness::new`] whenever possible.
42    pub fn new_unchecked(smt_proof: SmtProof) -> Self {
43        Self(smt_proof)
44    }
45
46    // PUBLIC ACCESSORS
47    // --------------------------------------------------------------------------------------------
48
49    /// Searches for an [`Asset`] in the witness with the given `vault_key`.
50    pub fn find(&self, vault_key: AssetVaultKey) -> Option<Asset> {
51        self.assets().find(|asset| asset.vault_key() == vault_key)
52    }
53
54    /// Returns an iterator over the [`Asset`]s in this witness.
55    pub fn assets(&self) -> impl Iterator<Item = Asset> {
56        // TODO: Avoid cloning the vector by not calling SmtLeaf::entries.
57        // Once SmtLeaf::entries returns a slice (i.e. once
58        // https://github.com/0xMiden/crypto/pull/521 is available), replace this match statement.
59        let entries = match self.0.leaf() {
60            SmtLeaf::Empty(_) => &[],
61            SmtLeaf::Single(kv_pair) => core::slice::from_ref(kv_pair),
62            SmtLeaf::Multiple(kv_pairs) => kv_pairs,
63        };
64
65        entries.iter().map(|(_key, value)| {
66            Asset::try_from(value).expect("asset witness should track valid assets")
67        })
68    }
69
70    /// Returns an iterator over every inner node of this witness' merkle path.
71    pub fn authenticated_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
72        self.0
73            .path()
74            .authenticated_nodes(self.0.leaf().index().value(), self.0.leaf().hash())
75            .expect("leaf index is u64 and should be less than 2^SMT_DEPTH")
76    }
77}
78
79impl From<AssetWitness> for SmtProof {
80    fn from(witness: AssetWitness) -> Self {
81        witness.0
82    }
83}
84
85// TESTS
86// ================================================================================================
87
88#[cfg(test)]
89mod tests {
90    use assert_matches::assert_matches;
91    use miden_crypto::merkle::Smt;
92
93    use super::*;
94    use crate::Word;
95    use crate::asset::{FungibleAsset, NonFungibleAsset};
96
97    /// Tests that constructing an asset witness fails if any asset in the smt proof is invalid.
98    #[test]
99    fn create_asset_witness_fails_on_invalid_asset() -> anyhow::Result<()> {
100        let invalid_asset = Word::from([0, 0, 0, 5u32]);
101        let smt = Smt::with_entries([(invalid_asset, invalid_asset)])?;
102        let proof = smt.open(&invalid_asset);
103
104        let err = AssetWitness::new(proof).unwrap_err();
105
106        assert_matches!(err, AssetError::InvalidFaucetAccountId(_));
107
108        Ok(())
109    }
110
111    /// Tests that constructing an asset witness fails if the vault key is from a fungible asset and
112    /// the asset is a non-fungible one.
113    #[test]
114    fn create_asset_witness_fails_on_vault_key_mismatch() -> anyhow::Result<()> {
115        let fungible_asset = FungibleAsset::mock(500);
116        let non_fungible_asset = NonFungibleAsset::mock(&[1]);
117
118        let smt =
119            Smt::with_entries([(fungible_asset.vault_key().into(), non_fungible_asset.into())])?;
120        let proof = smt.open(&fungible_asset.vault_key().into());
121
122        let err = AssetWitness::new(proof).unwrap_err();
123
124        assert_matches!(err, AssetError::AssetVaultKeyMismatch { actual, expected } => {
125            assert_eq!(actual, fungible_asset.vault_key().into());
126            assert_eq!(expected, non_fungible_asset.vault_key().into());
127        });
128
129        Ok(())
130    }
131}