Skip to main content

miden_protocol/asset/vault/
asset_witness.rs

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