miden_protocol/asset/vault/
asset_witness.rs1use 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#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct AssetWitness(SmtProof);
16
17impl AssetWitness {
18 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 pub fn new_unchecked(smt_proof: SmtProof) -> Self {
47 Self(smt_proof)
48 }
49
50 pub fn authenticates_asset_vault_key(&self, vault_key: AssetVaultKey) -> bool {
56 self.0.leaf().index() == vault_key.to_leaf_index()
57 }
58
59 pub fn find(&self, vault_key: AssetVaultKey) -> Option<Asset> {
61 self.assets().find(|asset| asset.vault_key() == vault_key)
62 }
63
64 pub fn assets(&self) -> impl Iterator<Item = Asset> {
66 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 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#[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 #[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 #[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}