miden_protocol/asset/vault/
asset_witness.rs1use 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#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct AssetWitness(SmtProof);
23
24impl AssetWitness {
25 pub fn new(smt_proof: SmtProof) -> Result<Self, AssetError> {
35 for (vault_key, asset_value) in smt_proof.leaf().entries() {
36 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 pub fn new_unchecked(smt_proof: SmtProof) -> Self {
49 Self(smt_proof)
50 }
51
52 pub fn authenticates_asset_vault_key(&self, vault_key: AssetVaultKey) -> bool {
58 self.0.leaf().index() == vault_key.to_leaf_index()
59 }
60
61 pub fn find(&self, vault_key: AssetVaultKey) -> Option<Asset> {
63 self.assets().find(|asset| asset.vault_key() == vault_key)
64 }
65
66 pub fn assets(&self) -> impl Iterator<Item = Asset> {
68 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 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#[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 #[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 #[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}