Skip to main content

miden_protocol/asset/vault/
asset_witness.rs

1use alloc::boxed::Box;
2use alloc::string::ToString;
3
4use miden_crypto::merkle::InnerNodeInfo;
5use miden_crypto::merkle::smt::{SmtLeaf, SmtProof};
6
7use super::vault_key::AssetVaultKey;
8use crate::asset::Asset;
9use crate::errors::AssetError;
10use crate::utils::serde::{
11    ByteReader,
12    ByteWriter,
13    Deserializable,
14    DeserializationError,
15    Serializable,
16};
17
18/// A witness of an asset in an [`AssetVault`](super::AssetVault).
19///
20/// It proves inclusion of a certain asset in the vault.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct AssetWitness(SmtProof);
23
24impl AssetWitness {
25    // CONSTRUCTORS
26    // --------------------------------------------------------------------------------------------
27
28    /// Creates a new [`AssetWitness`] from an SMT proof.
29    ///
30    /// # Errors
31    ///
32    /// Returns an error if:
33    /// - any of the key value pairs in the SMT leaf do not form a valid asset.
34    pub fn new(smt_proof: SmtProof) -> Result<Self, AssetError> {
35        for (vault_key, asset_value) in smt_proof.leaf().entries() {
36            // This ensures that vault key and value are consistent.
37            Asset::from_key_value_words(*vault_key, *asset_value)
38                .map_err(|err| AssetError::AssetWitnessInvalid(Box::new(err)))?;
39        }
40
41        Ok(Self(smt_proof))
42    }
43
44    /// Creates a new [`AssetWitness`] from an SMT proof without checking that the proof contains
45    /// valid assets.
46    ///
47    /// Prefer [`AssetWitness::new`] whenever possible.
48    pub fn new_unchecked(smt_proof: SmtProof) -> Self {
49        Self(smt_proof)
50    }
51
52    // PUBLIC ACCESSORS
53    // --------------------------------------------------------------------------------------------
54
55    /// Returns `true` if this [`AssetWitness`] authenticates the provided [`AssetVaultKey`], i.e.
56    /// if its leaf index matches, `false` otherwise.
57    pub fn authenticates_asset_vault_key(&self, vault_key: AssetVaultKey) -> bool {
58        self.0.leaf().index() == vault_key.to_leaf_index()
59    }
60
61    /// Searches for an [`Asset`] in the witness with the given `vault_key`.
62    pub fn find(&self, vault_key: AssetVaultKey) -> Option<Asset> {
63        self.assets().find(|asset| asset.vault_key() == vault_key)
64    }
65
66    /// Returns an iterator over the [`Asset`]s in this witness.
67    pub fn assets(&self) -> impl Iterator<Item = Asset> {
68        // TODO: Avoid cloning the vector by not calling SmtLeaf::entries.
69        // Once SmtLeaf::entries returns a slice (i.e. once
70        // https://github.com/0xMiden/crypto/pull/521 is available), replace this match statement.
71        let entries = match self.0.leaf() {
72            SmtLeaf::Empty(_) => &[],
73            SmtLeaf::Single(kv_pair) => core::slice::from_ref(kv_pair),
74            SmtLeaf::Multiple(kv_pairs) => kv_pairs,
75        };
76
77        entries.iter().map(|(key, value)| {
78            Asset::from_key_value_words(*key, *value)
79                .expect("asset witness should track valid assets")
80        })
81    }
82
83    /// Returns an iterator over every inner node of this witness' merkle path.
84    pub fn authenticated_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> + '_ {
85        self.0
86            .path()
87            .authenticated_nodes(self.0.leaf().index().position(), self.0.leaf().hash())
88            .expect("leaf index is u64 and should be less than 2^SMT_DEPTH")
89    }
90}
91
92impl From<AssetWitness> for SmtProof {
93    fn from(witness: AssetWitness) -> Self {
94        witness.0
95    }
96}
97
98impl Serializable for AssetWitness {
99    fn write_into<W: ByteWriter>(&self, target: &mut W) {
100        self.0.write_into(target);
101    }
102}
103
104impl Deserializable for AssetWitness {
105    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
106        let proof = SmtProof::read_from(source)?;
107        Self::new(proof).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
108    }
109}
110
111// TESTS
112// ================================================================================================
113
114#[cfg(test)]
115mod tests {
116    use assert_matches::assert_matches;
117    use miden_crypto::merkle::smt::Smt;
118
119    use super::*;
120    use crate::Word;
121    use crate::asset::{AssetVault, FungibleAsset, NonFungibleAsset};
122    use crate::testing::account_id::{
123        ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET,
124        ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
125    };
126
127    /// Tests that constructing an asset witness fails if any asset in the smt proof is invalid.
128    #[test]
129    fn create_asset_witness_fails_on_invalid_asset() -> anyhow::Result<()> {
130        let invalid_asset = Word::from([0, 0, 0, 5u32]);
131        let smt = Smt::with_entries([(invalid_asset, invalid_asset)])?;
132        let proof = smt.open(&invalid_asset);
133
134        let err = AssetWitness::new(proof).unwrap_err();
135
136        assert_matches!(err, AssetError::AssetWitnessInvalid(source) => {
137            assert_matches!(*source, AssetError::InvalidFaucetAccountId(_));
138        });
139
140        Ok(())
141    }
142
143    /// Tests that constructing an asset witness fails if the vault key is from a fungible asset and
144    /// the asset is a non-fungible one.
145    #[test]
146    fn create_asset_witness_fails_on_vault_key_mismatch() -> anyhow::Result<()> {
147        let fungible_asset = FungibleAsset::mock(500);
148        let non_fungible_asset = NonFungibleAsset::mock(&[1]);
149
150        let smt = Smt::with_entries([(
151            fungible_asset.vault_key().into(),
152            non_fungible_asset.to_value_word(),
153        )])?;
154        let proof = smt.open(&fungible_asset.vault_key().into());
155
156        let err = AssetWitness::new(proof).unwrap_err();
157
158        assert_matches!(err, AssetError::AssetWitnessInvalid(source) => {
159            assert_matches!(*source, AssetError::FungibleAssetValueMostSignificantElementsMustBeZero(_));
160        });
161
162        Ok(())
163    }
164
165    #[test]
166    fn asset_witness_authenticates_asset_vault_key() -> anyhow::Result<()> {
167        let fungible_asset0 =
168            FungibleAsset::new(ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET.try_into()?, 200)?;
169        let fungible_asset1 =
170            FungibleAsset::new(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into()?, 100)?;
171
172        let vault = AssetVault::new(&[fungible_asset0.into()])?;
173        let witness0 = vault.open(fungible_asset0.vault_key());
174
175        assert!(witness0.authenticates_asset_vault_key(fungible_asset0.vault_key()));
176        assert!(!witness0.authenticates_asset_vault_key(fungible_asset1.vault_key()));
177
178        Ok(())
179    }
180}