1use std::sync::Arc;
2
3use miden_protocol::crypto::merkle::SparseMerklePath;
4use miden_protocol::note::{
5 Note,
6 NoteAttachmentHeader,
7 NoteAttachmentScheme,
8 NoteAttachments,
9 NoteDetails,
10 NoteDetailsCommitment,
11 NoteHeader,
12 NoteId,
13 NoteInclusionProof,
14 NoteMetadata,
15 NoteScript,
16 NoteTag,
17 NoteType,
18 PartialNoteMetadata,
19};
20use miden_protocol::utils::serde::Serializable;
21use miden_protocol::{MastForest, MastNodeId, Word};
22use miden_standards::note::AccountTargetNetworkNote;
23
24use crate::decode::{ConversionResultExt, DecodeBytesExt, GrpcDecodeExt};
25use crate::errors::ConversionError;
26use crate::{decode, generated as proto};
27
28impl From<NoteType> for proto::note::NoteType {
32 fn from(note_type: NoteType) -> Self {
33 match note_type {
34 NoteType::Public => proto::note::NoteType::Public,
35 NoteType::Private => proto::note::NoteType::Private,
36 }
37 }
38}
39
40impl TryFrom<proto::note::NoteType> for NoteType {
41 type Error = ConversionError;
42
43 fn try_from(note_type: proto::note::NoteType) -> Result<Self, Self::Error> {
44 match note_type {
45 proto::note::NoteType::Public => Ok(NoteType::Public),
46 proto::note::NoteType::Private => Ok(NoteType::Private),
47 proto::note::NoteType::Unspecified => {
48 Err(ConversionError::message("enum variant discriminant out of range"))
49 },
50 }
51 }
52}
53
54impl From<NoteMetadata> for proto::note::NoteMetadata {
58 fn from(val: NoteMetadata) -> Self {
59 let sender = Some(val.sender().into());
60 let note_type = proto::note::NoteType::from(val.note_type()) as i32;
61 let tag = val.tag().as_u32();
62 let attachment_schemes = val
63 .attachment_headers()
64 .iter()
65 .map(|header| u32::from(header.scheme().map_or(0, |s| s.as_u16())))
66 .collect();
67 let attachments_commitment = Some(val.attachments_commitment().into());
68
69 proto::note::NoteMetadata {
70 sender,
71 note_type,
72 tag,
73 attachment_schemes,
74 attachments_commitment,
75 }
76 }
77}
78
79impl TryFrom<proto::note::NoteMetadata> for NoteMetadata {
80 type Error = ConversionError;
81
82 fn try_from(value: proto::note::NoteMetadata) -> Result<Self, Self::Error> {
83 let decoder = value.decoder();
84 let sender = decode!(decoder, value.sender)?;
85 let note_type = proto::note::NoteType::try_from(value.note_type)
86 .map_err(|_| ConversionError::message("enum variant discriminant out of range"))?
87 .try_into()
88 .context("note_type")?;
89 let tag = NoteTag::new(value.tag);
90 let attachments_commitment: Word = decode!(decoder, value.attachments_commitment)?;
91
92 if value.attachment_schemes.len() > NoteAttachments::MAX_COUNT {
93 return Err(ConversionError::message("too many attachment schemes"));
94 }
95 let mut attachment_headers = [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT];
96 for (slot, raw) in attachment_headers.iter_mut().zip(value.attachment_schemes) {
97 let raw = u16::try_from(raw)
98 .map_err(|_| ConversionError::message("attachment scheme out of u16 range"))?;
99 *slot = if raw == 0 {
100 NoteAttachmentHeader::absent()
101 } else {
102 NoteAttachmentHeader::new(NoteAttachmentScheme::new(raw)?)
103 };
104 }
105
106 let partial = PartialNoteMetadata::new(sender, note_type).with_tag(tag);
107 Ok(NoteMetadata::from_parts(partial, attachment_headers, attachments_commitment))
108 }
109}
110
111impl From<Note> for proto::note::NetworkNote {
115 fn from(note: Note) -> Self {
116 let metadata = Some(proto::note::NoteMetadata::from(*note.metadata()));
117 let attachments = note.attachments().to_bytes();
118 let details = NoteDetails::from(note).to_bytes();
119 Self { metadata, details, attachments }
120 }
121}
122
123impl From<Note> for proto::note::Note {
124 fn from(note: Note) -> Self {
125 let metadata = Some(proto::note::NoteMetadata::from(*note.metadata()));
126 let attachments = note.attachments().to_bytes();
127 let details = Some(NoteDetails::from(note).to_bytes());
128 Self { metadata, details, attachments }
129 }
130}
131
132impl From<AccountTargetNetworkNote> for proto::note::NetworkNote {
133 fn from(note: AccountTargetNetworkNote) -> Self {
134 note.into_note().into()
135 }
136}
137
138impl TryFrom<proto::note::NetworkNote> for AccountTargetNetworkNote {
139 type Error = ConversionError;
140
141 fn try_from(value: proto::note::NetworkNote) -> Result<Self, Self::Error> {
142 let proto::note::NetworkNote { metadata, details, attachments } = value;
143
144 let metadata = metadata
145 .ok_or(ConversionError::missing_field::<proto::note::NetworkNote>("metadata"))?;
146 let partial_metadata = partial_note_metadata_from_proto(metadata)?;
147
148 let note_details = NoteDetails::decode_bytes(&details, "NoteDetails")?;
149 let (assets, recipient) = note_details.into_parts();
150 let attachments = decode_attachments(&attachments)?;
151
152 let note = Note::with_attachments(assets, partial_metadata, recipient, attachments);
153 AccountTargetNetworkNote::new(note).map_err(ConversionError::from)
154 }
155}
156
157impl TryFrom<proto::note::Note> for Note {
158 type Error = ConversionError;
159
160 fn try_from(proto_note: proto::note::Note) -> Result<Self, Self::Error> {
161 let proto::note::Note { metadata, details, attachments } = proto_note;
162
163 let metadata =
164 metadata.ok_or(ConversionError::missing_field::<proto::note::Note>("metadata"))?;
165 let partial_metadata = partial_note_metadata_from_proto(metadata)?;
166
167 let details =
168 details.ok_or(ConversionError::missing_field::<proto::note::Note>("details"))?;
169 let note_details = NoteDetails::decode_bytes(&details, "NoteDetails")?;
170 let (assets, recipient) = note_details.into_parts();
171 let attachments = decode_attachments(&attachments)?;
172
173 Ok(Note::with_attachments(assets, partial_metadata, recipient, attachments))
174 }
175}
176
177impl From<Word> for proto::note::NoteId {
181 fn from(digest: Word) -> Self {
182 Self { id: Some(digest.into()) }
183 }
184}
185
186impl TryFrom<proto::note::NoteId> for Word {
187 type Error = ConversionError;
188
189 fn try_from(note_id: proto::note::NoteId) -> Result<Self, Self::Error> {
190 note_id
191 .id
192 .as_ref()
193 .ok_or(ConversionError::missing_field::<proto::note::NoteId>("id"))?
194 .try_into()
195 }
196}
197
198impl From<&NoteId> for proto::note::NoteId {
199 fn from(note_id: &NoteId) -> Self {
200 Self { id: Some(note_id.into()) }
201 }
202}
203
204impl From<(&NoteId, &NoteInclusionProof)> for proto::note::NoteInclusionInBlockProof {
205 fn from((note_id, proof): (&NoteId, &NoteInclusionProof)) -> Self {
206 Self {
207 note_id: Some(note_id.into()),
208 block_num: proof.location().block_num().as_u32(),
209 note_index_in_block: proof.location().block_note_tree_index().into(),
210 inclusion_path: Some(proof.note_path().clone().into()),
211 }
212 }
213}
214
215impl TryFrom<&proto::note::NoteInclusionInBlockProof> for (NoteId, NoteInclusionProof) {
216 type Error = ConversionError;
217
218 fn try_from(
219 proof: &proto::note::NoteInclusionInBlockProof,
220 ) -> Result<(NoteId, NoteInclusionProof), Self::Error> {
221 let inclusion_path = SparseMerklePath::try_from(
222 proof
223 .inclusion_path
224 .as_ref()
225 .ok_or(ConversionError::missing_field::<proto::note::NoteInclusionInBlockProof>(
226 "inclusion_path",
227 ))?
228 .clone(),
229 )
230 .context("inclusion_path")?;
231
232 let note_id = Word::try_from(
233 proof
234 .note_id
235 .as_ref()
236 .ok_or(ConversionError::missing_field::<proto::note::NoteInclusionInBlockProof>(
237 "note_id",
238 ))?
239 .id
240 .as_ref()
241 .ok_or(ConversionError::missing_field::<proto::note::NoteId>("id"))?,
242 )
243 .context("note_id")?;
244
245 Ok((
246 NoteId::from_raw(note_id),
247 NoteInclusionProof::new(
248 proof.block_num.into(),
249 proof.note_index_in_block.try_into().context("note_index_in_block")?,
250 inclusion_path,
251 )?,
252 ))
253 }
254}
255
256impl From<NoteHeader> for proto::note::NoteHeader {
260 fn from(header: NoteHeader) -> Self {
261 Self {
262 details_commitment: Some(header.details_commitment().as_word().into()),
263 metadata: Some(header.into_metadata().into()),
264 }
265 }
266}
267
268impl TryFrom<proto::note::NoteHeader> for NoteHeader {
269 type Error = ConversionError;
270
271 fn try_from(value: proto::note::NoteHeader) -> Result<Self, Self::Error> {
272 let decoder = value.decoder();
273 let details_commitment_word: Word = decode!(decoder, value.details_commitment)?;
274 let metadata: NoteMetadata = decode!(decoder, value.metadata)?;
275
276 Ok(NoteHeader::new(
277 NoteDetailsCommitment::from_raw(details_commitment_word),
278 metadata,
279 ))
280 }
281}
282
283impl From<NoteScript> for proto::note::NoteScript {
287 fn from(script: NoteScript) -> Self {
288 Self {
289 entrypoint: script.entrypoint().into(),
290 mast: script.mast().to_bytes(),
291 }
292 }
293}
294
295impl TryFrom<proto::note::NoteScript> for NoteScript {
296 type Error = ConversionError;
297
298 fn try_from(value: proto::note::NoteScript) -> Result<Self, Self::Error> {
299 let proto::note::NoteScript { entrypoint, mast } = value;
300
301 let mast = MastForest::decode_bytes(&mast, "note_script.mast")?;
302 let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)
303 .map_err(|err| ConversionError::deserialization("note_script.entrypoint", err))?;
304
305 Ok(Self::from_parts(Arc::new(mast), entrypoint))
306 }
307}
308
309fn partial_note_metadata_from_proto(
317 value: proto::note::NoteMetadata,
318) -> Result<PartialNoteMetadata, ConversionError> {
319 let decoder = value.decoder();
320 let sender = decode!(decoder, value.sender)?;
321 let note_type = proto::note::NoteType::try_from(value.note_type)
322 .map_err(|_| ConversionError::message("enum variant discriminant out of range"))?
323 .try_into()
324 .context("note_type")?;
325 let tag = NoteTag::new(value.tag);
326 Ok(PartialNoteMetadata::new(sender, note_type).with_tag(tag))
327}
328
329fn decode_attachments(bytes: &[u8]) -> Result<NoteAttachments, ConversionError> {
332 if bytes.is_empty() {
333 Ok(NoteAttachments::empty())
334 } else {
335 NoteAttachments::decode_bytes(bytes, "NoteAttachments")
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use miden_protocol::account::{AccountId, AccountIdVersion, AccountType};
342
343 use super::*;
344
345 #[test]
346 fn note_header_roundtrip_preserves_id() {
347 let details_commitment =
349 NoteDetailsCommitment::from_raw(Word::try_from([1u64, 2, 3, 4]).unwrap());
350 let sender = AccountId::dummy([1; 15], AccountIdVersion::Version1, AccountType::Public);
351 let metadata = NoteMetadata::new(
352 PartialNoteMetadata::new(sender, NoteType::Public).with_tag(NoteTag::from(7u32)),
353 &NoteAttachments::default(),
354 );
355
356 let original = NoteHeader::new(details_commitment, metadata);
357
358 let proto_header: proto::note::NoteHeader = original.into();
360 let decoded = NoteHeader::try_from(proto_header).expect("proto NoteHeader should decode");
361
362 assert_eq!(decoded.id(), original.id());
366 assert_eq!(decoded.details_commitment(), original.details_commitment());
367 assert_eq!(decoded.metadata(), original.metadata());
368 }
369}