Skip to main content

miden_objects/conversion/
note.rs

1use alloc::format;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4
5use miden_protocol::asset::Asset;
6use miden_protocol::note::{
7    Note,
8    NoteAssets,
9    NoteAttachment,
10    NoteAttachmentHeader,
11    NoteAttachmentScheme,
12    NoteAttachments,
13    NoteDetails,
14    NoteDetailsCommitment,
15    NoteHeader,
16    NoteId,
17    NoteInclusionProof,
18    NoteMetadata,
19    NoteRecipient,
20    NoteScript,
21    NoteStorage,
22    NoteTag,
23    NoteType,
24    PartialNoteMetadata,
25};
26use miden_protocol::{Felt, MastNodeId, Word};
27
28use super::{MessageDecodeExt, MessageDecoder, required};
29use crate::{ConversionError, ConversionResultExt, proto};
30
31// NOTE TYPE
32// ================================================================================================
33
34impl From<NoteType> for proto::note::NoteType {
35    fn from(note_type: NoteType) -> Self {
36        match note_type {
37            NoteType::Private => proto::note::NoteType::Private,
38            NoteType::Public => proto::note::NoteType::Public,
39        }
40    }
41}
42
43impl TryFrom<proto::note::NoteType> for NoteType {
44    type Error = ConversionError;
45
46    fn try_from(note_type: proto::note::NoteType) -> Result<Self, Self::Error> {
47        match note_type {
48            proto::note::NoteType::Private => Ok(NoteType::Private),
49            proto::note::NoteType::Public => Ok(NoteType::Public),
50            proto::note::NoteType::Unspecified => {
51                Err(ConversionError::message("enum variant discriminant out of range"))
52            },
53        }
54    }
55}
56
57// NOTE METADATA
58// ================================================================================================
59
60impl From<NoteMetadata> for proto::note::NoteMetadata {
61    fn from(metadata: NoteMetadata) -> Self {
62        Self {
63            version: proto::note::NoteVersion::V1 as i32,
64            sender: Some(metadata.sender().into()),
65            note_type: proto::note::NoteType::from(metadata.note_type()) as i32,
66            tag: metadata.tag().as_u32(),
67            attachment_schemes: metadata
68                .attachment_headers()
69                .iter()
70                .map(|header| u32::from(header.scheme().map_or(0, |scheme| scheme.as_u16())))
71                .collect(),
72            attachments_commitment: Some(metadata.attachments_commitment().into()),
73        }
74    }
75}
76
77impl TryFrom<proto::note::NoteMetadata> for NoteMetadata {
78    type Error = ConversionError;
79
80    fn try_from(metadata: proto::note::NoteMetadata) -> Result<Self, Self::Error> {
81        decode_note_version(metadata.version).context("version")?;
82        decode_note_metadata(metadata)
83    }
84}
85
86// NOTE ATTACHMENTS
87// ================================================================================================
88
89impl From<&NoteAttachment> for proto::note::NoteAttachment {
90    fn from(attachment: &NoteAttachment) -> Self {
91        Self {
92            scheme: u32::from(attachment.attachment_scheme().as_u16()),
93            words: attachment.content().as_words().iter().map(Into::into).collect(),
94        }
95    }
96}
97
98impl TryFrom<proto::note::NoteAttachment> for NoteAttachment {
99    type Error = ConversionError;
100
101    fn try_from(attachment: proto::note::NoteAttachment) -> Result<Self, Self::Error> {
102        let scheme = u16::try_from(attachment.scheme).context("scheme")?;
103        let scheme = NoteAttachmentScheme::new(scheme)
104            .map_err(ConversionError::from)
105            .context("scheme")?;
106        let words = attachment
107            .words
108            .into_iter()
109            .map(Word::try_from)
110            .collect::<Result<Vec<_>, _>>()
111            .context("words")?;
112
113        NoteAttachment::with_words(scheme, words)
114            .map_err(ConversionError::from)
115            .context("words")
116    }
117}
118
119impl From<NoteAttachments> for proto::note::NoteAttachments {
120    fn from(attachments: NoteAttachments) -> Self {
121        Self::from(&attachments)
122    }
123}
124
125impl From<&NoteAttachments> for proto::note::NoteAttachments {
126    fn from(attachments: &NoteAttachments) -> Self {
127        Self {
128            attachments: attachments.iter().map(Into::into).collect(),
129        }
130    }
131}
132
133impl TryFrom<proto::note::NoteAttachments> for NoteAttachments {
134    type Error = ConversionError;
135
136    fn try_from(attachments: proto::note::NoteAttachments) -> Result<Self, Self::Error> {
137        let attachments = attachments
138            .attachments
139            .into_iter()
140            .map(NoteAttachment::try_from)
141            .collect::<Result<Vec<_>, _>>()
142            .context("attachments")?;
143
144        NoteAttachments::new(attachments)
145            .map_err(ConversionError::from)
146            .context("attachments")
147    }
148}
149
150// NOTE DETAILS
151// ================================================================================================
152
153impl From<NoteStorage> for proto::note::NoteStorage {
154    fn from(storage: NoteStorage) -> Self {
155        Self::from(&storage)
156    }
157}
158
159impl From<&NoteStorage> for proto::note::NoteStorage {
160    fn from(storage: &NoteStorage) -> Self {
161        Self {
162            items: storage.items().iter().map(Into::into).collect(),
163        }
164    }
165}
166
167impl TryFrom<proto::note::NoteStorage> for NoteStorage {
168    type Error = ConversionError;
169
170    fn try_from(storage: proto::note::NoteStorage) -> Result<Self, Self::Error> {
171        let items = storage
172            .items
173            .into_iter()
174            .map(Felt::try_from)
175            .collect::<Result<Vec<_>, _>>()
176            .context("items")?;
177
178        NoteStorage::new(items).map_err(ConversionError::from).context("items")
179    }
180}
181
182impl From<NoteRecipient> for proto::note::NoteRecipient {
183    fn from(recipient: NoteRecipient) -> Self {
184        Self::from(&recipient)
185    }
186}
187
188impl From<&NoteRecipient> for proto::note::NoteRecipient {
189    fn from(recipient: &NoteRecipient) -> Self {
190        Self {
191            serial_num: Some(recipient.serial_num().into()),
192            script: Some(recipient.script().into()),
193            storage: Some(recipient.storage().into()),
194        }
195    }
196}
197
198impl TryFrom<proto::note::NoteRecipient> for NoteRecipient {
199    type Error = ConversionError;
200
201    fn try_from(recipient: proto::note::NoteRecipient) -> Result<Self, Self::Error> {
202        let decoder = recipient.decoder();
203        let serial_num = required!(decoder, recipient.serial_num)?;
204        let script = required!(decoder, recipient.script)?;
205        let storage = required!(decoder, recipient.storage)?;
206
207        Ok(NoteRecipient::new(serial_num, script, storage))
208    }
209}
210
211impl From<NoteDetails> for proto::note::NoteDetails {
212    fn from(details: NoteDetails) -> Self {
213        Self::from(&details)
214    }
215}
216
217impl From<&NoteDetails> for proto::note::NoteDetails {
218    fn from(details: &NoteDetails) -> Self {
219        Self {
220            assets: details.assets().iter().copied().map(Into::into).collect(),
221            recipient: Some(details.recipient().into()),
222        }
223    }
224}
225
226impl TryFrom<proto::note::NoteDetails> for NoteDetails {
227    type Error = ConversionError;
228
229    fn try_from(details: proto::note::NoteDetails) -> Result<Self, Self::Error> {
230        let decoder = details.decoder();
231        let assets = details
232            .assets
233            .into_iter()
234            .map(Asset::try_from)
235            .collect::<Result<Vec<_>, _>>()
236            .context("assets")?;
237        let assets = NoteAssets::new(assets).map_err(ConversionError::from).context("assets")?;
238        let recipient = required!(decoder, details.recipient)?;
239
240        Ok(NoteDetails::new(assets, recipient))
241    }
242}
243
244// NOTE
245// ================================================================================================
246
247impl From<Note> for proto::note::Note {
248    fn from(note: Note) -> Self {
249        let (assets, metadata, recipient, attachments) = note.into_parts();
250        Self {
251            metadata: Some(metadata.into()),
252            note_details: Some(NoteDetails::new(assets, recipient).into()),
253            note_attachments: Some(attachments.into()),
254        }
255    }
256}
257
258impl TryFrom<proto::note::Note> for Note {
259    type Error = ConversionError;
260
261    fn try_from(proto_note: proto::note::Note) -> Result<Self, Self::Error> {
262        let decoder = proto_note.decoder();
263        let proto::note::Note { metadata, note_details, note_attachments } = proto_note;
264
265        let metadata = required!(decoder, metadata)?;
266        let partial_metadata = partial_note_metadata_from_proto(metadata)?;
267
268        let note_details: NoteDetails = required!(decoder, note_details)?;
269        let (assets, recipient) = note_details.into_parts();
270        let attachments = decode_note_attachments::<proto::note::Note>(note_attachments)?;
271
272        Ok(Note::with_attachments(assets, partial_metadata, recipient, attachments))
273    }
274}
275
276// NOTE ID
277// ================================================================================================
278
279impl From<Word> for proto::note::NoteId {
280    fn from(digest: Word) -> Self {
281        Self { id: Some(digest.into()) }
282    }
283}
284
285impl TryFrom<proto::note::NoteId> for Word {
286    type Error = ConversionError;
287
288    fn try_from(note_id: proto::note::NoteId) -> Result<Self, Self::Error> {
289        let decoder = note_id.decoder();
290        required!(decoder, note_id.id)
291    }
292}
293
294impl From<&NoteId> for proto::note::NoteId {
295    fn from(note_id: &NoteId) -> Self {
296        Self { id: Some(note_id.as_word().into()) }
297    }
298}
299
300impl From<(&NoteId, &NoteInclusionProof)> for proto::note::NoteInclusionProof {
301    fn from((note_id, proof): (&NoteId, &NoteInclusionProof)) -> Self {
302        Self {
303            note_id: Some(note_id.into()),
304            block_num: Some(proof.location().block_num().into()),
305            note_index_in_block: proof.location().block_note_tree_index().into(),
306            inclusion_path: Some(proof.note_path().clone().into()),
307        }
308    }
309}
310
311impl TryFrom<&proto::note::NoteInclusionProof> for (NoteId, NoteInclusionProof) {
312    type Error = ConversionError;
313
314    fn try_from(
315        proof: &proto::note::NoteInclusionProof,
316    ) -> Result<(NoteId, NoteInclusionProof), Self::Error> {
317        let proof = proof.clone();
318        let decoder = proof.decoder();
319        let inclusion_path = required!(decoder, proof.inclusion_path)?;
320        let note_id = required!(decoder, proof.note_id)?;
321        let block_num = required!(decoder, proof.block_num).context("block_num")?;
322
323        Ok((
324            NoteId::from_raw(note_id),
325            NoteInclusionProof::new(
326                block_num,
327                proof.note_index_in_block.try_into().context("note_index_in_block")?,
328                inclusion_path,
329            )?,
330        ))
331    }
332}
333
334// NOTE HEADER
335// ================================================================================================
336
337impl From<NoteHeader> for proto::note::NoteHeader {
338    fn from(header: NoteHeader) -> Self {
339        Self {
340            details_commitment: Some(header.details_commitment().as_word().into()),
341            metadata: Some(header.into_metadata().into()),
342        }
343    }
344}
345
346impl TryFrom<proto::note::NoteHeader> for NoteHeader {
347    type Error = ConversionError;
348
349    fn try_from(value: proto::note::NoteHeader) -> Result<Self, Self::Error> {
350        let decoder = value.decoder();
351        let details_commitment_word = required!(decoder, value.details_commitment)?;
352        let metadata: NoteMetadata = required!(decoder, value.metadata)?;
353
354        Ok(NoteHeader::new(
355            NoteDetailsCommitment::from_raw(details_commitment_word),
356            metadata,
357        ))
358    }
359}
360
361// NOTE SCRIPT
362// ================================================================================================
363
364impl From<NoteScript> for proto::note::NoteScript {
365    fn from(script: NoteScript) -> Self {
366        Self::from(&script)
367    }
368}
369
370impl From<&NoteScript> for proto::note::NoteScript {
371    fn from(script: &NoteScript) -> Self {
372        Self {
373            entrypoint: script.entrypoint().into(),
374            mast: Some(script.mast().as_ref().into()),
375        }
376    }
377}
378
379impl TryFrom<proto::note::NoteScript> for NoteScript {
380    type Error = ConversionError;
381
382    fn try_from(value: proto::note::NoteScript) -> Result<Self, Self::Error> {
383        let decoder = value.decoder();
384        let mast = required!(decoder, value.mast)?;
385        let entrypoint = value.entrypoint;
386        let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)
387            .map_err(|err| ConversionError::deserialization("note_script.entrypoint", err))?;
388
389        Self::from_parts(Arc::new(mast), entrypoint).map_err(ConversionError::new)
390    }
391}
392
393// HELPERS
394// ================================================================================================
395
396/// Decodes the `(sender, note_type, tag)` triple from a proto `NoteMetadata` into a
397/// [`PartialNoteMetadata`]. The attachment-related fields on the proto are ignored — when full
398/// attachments are also transmitted, the receiver derives the canonical headers and commitment from
399/// those instead.
400fn partial_note_metadata_from_proto(
401    value: proto::note::NoteMetadata,
402) -> Result<PartialNoteMetadata, ConversionError> {
403    decode_note_version(value.version).context("version")?;
404    decode_partial_note_metadata(value.sender, value.note_type, value.tag)
405}
406
407fn decode_note_version(version: i32) -> Result<(), ConversionError> {
408    match proto::note::NoteVersion::try_from(version) {
409        Ok(proto::note::NoteVersion::V1) => Ok(()),
410        Ok(proto::note::NoteVersion::Unspecified) => {
411            Err(ConversionError::message("note metadata version is unspecified"))
412        },
413        Err(error) => Err(ConversionError::with_source(
414            format!("unknown note metadata version {version}"),
415            error,
416        )),
417    }
418}
419
420fn decode_note_metadata(
421    metadata: proto::note::NoteMetadata,
422) -> Result<NoteMetadata, ConversionError> {
423    let proto::note::NoteMetadata {
424        sender,
425        note_type,
426        tag,
427        attachment_schemes,
428        attachments_commitment,
429        ..
430    } = metadata;
431
432    let partial = decode_partial_note_metadata(sender, note_type, tag)?;
433    let decoder = MessageDecoder::<proto::note::NoteMetadata>::default();
434    let attachments_commitment = required!(decoder, attachments_commitment)?;
435
436    if attachment_schemes.len() > NoteAttachments::MAX_COUNT {
437        return Err(ConversionError::message("too many attachment schemes"));
438    }
439    let mut attachment_headers = [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT];
440    for (slot, raw) in attachment_headers.iter_mut().zip(attachment_schemes) {
441        let raw = u16::try_from(raw)
442            .map_err(|_| ConversionError::message("attachment scheme out of u16 range"))?;
443        *slot = if raw == 0 {
444            NoteAttachmentHeader::absent()
445        } else {
446            NoteAttachmentHeader::new(NoteAttachmentScheme::new(raw)?)
447        };
448    }
449
450    Ok(NoteMetadata::from_parts(partial, attachment_headers, attachments_commitment))
451}
452
453fn decode_partial_note_metadata(
454    sender: Option<proto::account::AccountId>,
455    note_type: i32,
456    tag: u32,
457) -> Result<PartialNoteMetadata, ConversionError> {
458    let decoder = MessageDecoder::<proto::note::NoteMetadata>::default();
459    let sender = required!(decoder, sender)?;
460    let note_type = proto::note::NoteType::try_from(note_type)
461        .map_err(|_| ConversionError::message("enum variant discriminant out of range"))?
462        .try_into()
463        .context("note_type")?;
464    let tag = NoteTag::new(tag);
465    Ok(PartialNoteMetadata::new(sender, note_type).with_tag(tag))
466}
467
468/// Requires and decodes the structured attachments carried by a note message.
469fn decode_note_attachments<M: prost::Message>(
470    note_attachments: Option<proto::note::NoteAttachments>,
471) -> Result<NoteAttachments, ConversionError> {
472    let decoder = MessageDecoder::<M>::default();
473    required!(decoder, note_attachments)
474}