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 NoteAttachments,
12 NoteRecipient,
13 NoteScript,
14 NoteScriptRoot,
15 NoteStorage,
16 NoteTag,
17 NoteType,
18 PartialNoteMetadata,
19};
20use miden_protocol::utils::sync::LazyLock;
21use miden_protocol::{Felt, Word};
22
23use crate::StandardsLib;
24const P2ID_SCRIPT_PATH: &str = "::miden::standards::notes::p2id::main";
29
30static P2ID_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
32 let standards_lib = StandardsLib::default();
33 let path = Path::new(P2ID_SCRIPT_PATH);
34 NoteScript::from_library_reference(standards_lib.as_ref(), path)
35 .expect("Standards library contains P2ID note script procedure")
36});
37
38pub struct P2idNote;
43
44impl P2idNote {
45 pub const NUM_STORAGE_ITEMS: usize = P2idNoteStorage::NUM_ITEMS;
50
51 pub fn script() -> NoteScript {
56 P2ID_SCRIPT.clone()
57 }
58
59 pub fn script_root() -> NoteScriptRoot {
61 P2ID_SCRIPT.root()
62 }
63
64 pub fn create<R: FeltRng>(
78 sender: AccountId,
79 target: AccountId,
80 assets: Vec<Asset>,
81 note_type: NoteType,
82 attachments: NoteAttachments,
83 rng: &mut R,
84 ) -> Result<Note, NoteError> {
85 let serial_num = rng.draw_word();
86 let recipient = P2idNoteStorage::new(target).into_recipient(serial_num);
87
88 let tag = NoteTag::with_account_target(target);
89
90 let metadata = PartialNoteMetadata::new(sender, note_type).with_tag(tag);
91 let vault = NoteAssets::new(assets)?;
92
93 Ok(Note::with_attachments(vault, metadata, recipient, attachments))
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_elements(note_storage[0], note_storage[1])
154 .map_err(|err| NoteError::other_with_source("failed to create account id", err))?;
155
156 Ok(Self { target })
157 }
158}
159
160#[cfg(test)]
164mod tests {
165 use miden_protocol::Felt;
166 use miden_protocol::account::{AccountId, AccountIdVersion, AccountType};
167 use miden_protocol::errors::NoteError;
168
169 use super::*;
170
171 #[test]
172 fn try_from_valid_storage_succeeds() {
173 let target = AccountId::dummy([1u8; 15], AccountIdVersion::Version1, AccountType::Private);
174
175 let storage = vec![target.suffix(), target.prefix().as_felt()];
176
177 let parsed =
178 P2idNoteStorage::try_from(storage.as_slice()).expect("storage should be valid");
179
180 assert_eq!(parsed.target(), target);
181 }
182
183 #[test]
184 fn try_from_invalid_length_returns_error() {
185 let storage = vec![Felt::ZERO];
186
187 let err = P2idNoteStorage::try_from(storage.as_slice())
188 .expect_err("should fail due to invalid length");
189
190 assert!(matches!(
191 err,
192 NoteError::InvalidNoteStorageLength {
193 expected: P2idNote::NUM_STORAGE_ITEMS,
194 actual: 1
195 }
196 ));
197 }
198
199 #[test]
200 fn try_from_invalid_storage_contents_returns_error() {
201 let storage = vec![Felt::new_unchecked(999_u64), Felt::new_unchecked(888_u64)];
202
203 let err = P2idNoteStorage::try_from(storage.as_slice())
204 .expect_err("should fail due to invalid account id encoding");
205
206 assert!(matches!(err, NoteError::Other { source: Some(_), .. }));
207 }
208}