miden_standards/note/
p2id.rs1use alloc::vec::Vec;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::assembly::Path;
5use miden_protocol::asset::Asset;
6use miden_protocol::crypto::rand::FeltRng;
7use miden_protocol::errors::NoteError;
8use miden_protocol::note::{
9 Note,
10 NoteAssets,
11 NoteAttachment,
12 NoteMetadata,
13 NoteRecipient,
14 NoteScript,
15 NoteStorage,
16 NoteTag,
17 NoteType,
18};
19use miden_protocol::utils::sync::LazyLock;
20use miden_protocol::{Felt, Word};
21
22use crate::StandardsLib;
23const P2ID_SCRIPT_PATH: &str = "::miden::standards::notes::p2id::main";
28
29static P2ID_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
31 let standards_lib = StandardsLib::default();
32 let path = Path::new(P2ID_SCRIPT_PATH);
33 NoteScript::from_library_reference(standards_lib.as_ref(), path)
34 .expect("Standards library contains P2ID note script procedure")
35});
36
37pub struct P2idNote;
42
43impl P2idNote {
44 pub const NUM_STORAGE_ITEMS: usize = P2idNoteStorage::NUM_ITEMS;
49
50 pub fn script() -> NoteScript {
55 P2ID_SCRIPT.clone()
56 }
57
58 pub fn script_root() -> Word {
60 P2ID_SCRIPT.root()
61 }
62
63 pub fn create<R: FeltRng>(
77 sender: AccountId,
78 target: AccountId,
79 assets: Vec<Asset>,
80 note_type: NoteType,
81 attachment: NoteAttachment,
82 rng: &mut R,
83 ) -> Result<Note, NoteError> {
84 let serial_num = rng.draw_word();
85 let recipient = P2idNoteStorage::new(target).into_recipient(serial_num);
86
87 let tag = NoteTag::with_account_target(target);
88
89 let metadata =
90 NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment);
91 let vault = NoteAssets::new(assets)?;
92
93 Ok(Note::new(vault, metadata, recipient))
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub struct P2idNoteStorage {
104 target: AccountId,
105}
106
107impl P2idNoteStorage {
108 pub const NUM_ITEMS: usize = 2;
113
114 pub fn new(target: AccountId) -> Self {
116 Self { target }
117 }
118
119 pub fn into_recipient(self, serial_num: Word) -> NoteRecipient {
124 NoteRecipient::new(serial_num, P2idNote::script(), NoteStorage::from(self))
125 }
126
127 pub fn target(&self) -> AccountId {
129 self.target
130 }
131}
132
133impl From<P2idNoteStorage> for NoteStorage {
134 fn from(storage: P2idNoteStorage) -> Self {
135 NoteStorage::new(vec![storage.target.suffix(), storage.target.prefix().as_felt()])
138 .expect("number of storage items should not exceed max storage items")
139 }
140}
141
142impl TryFrom<&[Felt]> for P2idNoteStorage {
143 type Error = NoteError;
144
145 fn try_from(note_storage: &[Felt]) -> Result<Self, Self::Error> {
146 if note_storage.len() != P2idNote::NUM_STORAGE_ITEMS {
147 return Err(NoteError::InvalidNoteStorageLength {
148 expected: P2idNote::NUM_STORAGE_ITEMS,
149 actual: note_storage.len(),
150 });
151 }
152
153 let target = AccountId::try_from([note_storage[1], note_storage[0]])
154 .map_err(|e| NoteError::other_with_source("failed to create account id", e))?;
155
156 Ok(Self { target })
157 }
158}
159
160#[cfg(test)]
164mod tests {
165 use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType};
166 use miden_protocol::errors::NoteError;
167 use miden_protocol::{Felt, FieldElement};
168
169 use super::*;
170
171 #[test]
172 fn try_from_valid_storage_succeeds() {
173 let target = AccountId::dummy(
174 [1u8; 15],
175 AccountIdVersion::Version0,
176 AccountType::FungibleFaucet,
177 AccountStorageMode::Private,
178 );
179
180 let storage = vec![target.suffix(), target.prefix().as_felt()];
181
182 let parsed =
183 P2idNoteStorage::try_from(storage.as_slice()).expect("storage should be valid");
184
185 assert_eq!(parsed.target(), target);
186 }
187
188 #[test]
189 fn try_from_invalid_length_returns_error() {
190 let storage = vec![Felt::ZERO];
191
192 let err = P2idNoteStorage::try_from(storage.as_slice())
193 .expect_err("should fail due to invalid length");
194
195 assert!(matches!(
196 err,
197 NoteError::InvalidNoteStorageLength {
198 expected: P2idNote::NUM_STORAGE_ITEMS,
199 actual: 1
200 }
201 ));
202 }
203
204 #[test]
205 fn try_from_invalid_storage_contents_returns_error() {
206 let storage = vec![Felt::new(999u64), Felt::new(888u64)];
207
208 let err = P2idNoteStorage::try_from(storage.as_slice())
209 .expect_err("should fail due to invalid account id encoding");
210
211 assert!(matches!(err, NoteError::Other { source: Some(_), .. }));
212 }
213}