miden_protocol/note/
metadata.rs1use super::{
2 AccountId,
3 ByteReader,
4 ByteWriter,
5 Deserializable,
6 DeserializationError,
7 Felt,
8 NoteTag,
9 NoteType,
10 Serializable,
11 Word,
12};
13use crate::Hasher;
14use crate::errors::NoteError;
15use crate::note::{NoteAttachment, NoteAttachmentKind, NoteAttachmentScheme};
16
17#[derive(Clone, Debug, Eq, PartialEq)]
59pub struct NoteMetadata {
60 sender: AccountId,
62
63 note_type: NoteType,
65
66 tag: NoteTag,
68
69 attachment: NoteAttachment,
73}
74
75impl NoteMetadata {
76 pub fn new(sender: AccountId, note_type: NoteType, tag: NoteTag) -> Self {
81 Self {
82 sender,
83 note_type,
84 tag,
85 attachment: NoteAttachment::default(),
86 }
87 }
88
89 pub fn sender(&self) -> AccountId {
94 self.sender
95 }
96
97 pub fn note_type(&self) -> NoteType {
99 self.note_type
100 }
101
102 pub fn tag(&self) -> NoteTag {
104 self.tag
105 }
106
107 pub fn attachment(&self) -> &NoteAttachment {
109 &self.attachment
110 }
111
112 pub fn is_private(&self) -> bool {
114 self.note_type == NoteType::Private
115 }
116
117 fn to_header(&self) -> NoteMetadataHeader {
121 NoteMetadataHeader {
122 sender: self.sender,
123 note_type: self.note_type,
124 tag: self.tag,
125 attachment_kind: self.attachment().content().attachment_kind(),
126 attachment_scheme: self.attachment.attachment_scheme(),
127 }
128 }
129
130 pub fn to_header_word(&self) -> Word {
134 Word::from(self.to_header())
135 }
136
137 pub fn to_attachment_word(&self) -> Word {
141 self.attachment.content().to_word()
142 }
143
144 pub fn to_commitment(&self) -> Word {
150 Hasher::merge(&[self.to_header_word(), self.to_attachment_word()])
151 }
152
153 pub fn set_attachment(&mut self, attachment: NoteAttachment) {
158 self.attachment = attachment;
159 }
160
161 pub fn with_attachment(mut self, attachment: NoteAttachment) -> Self {
163 self.attachment = attachment;
164 self
165 }
166}
167
168impl Serializable for NoteMetadata {
172 fn write_into<W: ByteWriter>(&self, target: &mut W) {
173 self.note_type().write_into(target);
174 self.sender().write_into(target);
175 self.tag().write_into(target);
176 self.attachment().write_into(target);
177 }
178}
179
180impl Deserializable for NoteMetadata {
181 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
182 let note_type = NoteType::read_from(source)?;
183 let sender = AccountId::read_from(source)?;
184 let tag = NoteTag::read_from(source)?;
185 let attachment = NoteAttachment::read_from(source)?;
186
187 Ok(NoteMetadata::new(sender, note_type, tag).with_attachment(attachment))
188 }
189}
190
191#[derive(Clone, Copy, Debug, Eq, PartialEq)]
200struct NoteMetadataHeader {
201 sender: AccountId,
202 note_type: NoteType,
203 tag: NoteTag,
204 attachment_kind: NoteAttachmentKind,
205 attachment_scheme: NoteAttachmentScheme,
206}
207
208impl From<NoteMetadataHeader> for Word {
209 fn from(header: NoteMetadataHeader) -> Self {
210 let mut metadata = Word::empty();
211
212 metadata[0] = merge_sender_suffix_and_note_type(header.sender.suffix(), header.note_type);
213 metadata[1] = header.sender.prefix().as_felt();
214 metadata[2] = Felt::from(header.tag);
215 metadata[3] =
216 merge_attachment_kind_scheme(header.attachment_kind, header.attachment_scheme);
217
218 metadata
219 }
220}
221
222impl TryFrom<Word> for NoteMetadataHeader {
223 type Error = NoteError;
224
225 fn try_from(word: Word) -> Result<Self, Self::Error> {
227 let (sender_suffix, note_type) = unmerge_sender_suffix_and_note_type(word[0])?;
228 let sender_prefix = word[1];
229 let tag = u32::try_from(word[2]).map(NoteTag::new).map_err(|_| {
230 NoteError::other("failed to convert note tag from metadata header to u32")
231 })?;
232 let (attachment_kind, attachment_scheme) = unmerge_attachment_kind_scheme(word[3])?;
233
234 let sender = AccountId::try_from([sender_prefix, sender_suffix]).map_err(|source| {
235 NoteError::other_with_source("failed to decode account ID from metadata header", source)
236 })?;
237
238 Ok(Self {
239 sender,
240 note_type,
241 tag,
242 attachment_kind,
243 attachment_scheme,
244 })
245 }
246}
247
248fn merge_sender_suffix_and_note_type(sender_id_suffix: Felt, note_type: NoteType) -> Felt {
264 let mut merged = sender_id_suffix.as_int();
265
266 let note_type_byte = note_type as u8;
267 debug_assert!(note_type_byte < 4, "note type must not contain values >= 4");
268 merged |= note_type_byte as u64;
269
270 Felt::try_from(merged).expect("encoded value should be a valid felt")
273}
274
275fn unmerge_sender_suffix_and_note_type(element: Felt) -> Result<(Felt, NoteType), NoteError> {
277 const NOTE_TYPE_MASK: u8 = 0b11;
278 const SENDER_SUFFIX_MASK: u64 = !(NOTE_TYPE_MASK as u64);
280
281 let note_type_byte = element.as_int() as u8 & NOTE_TYPE_MASK;
282 let note_type = NoteType::try_from(note_type_byte).map_err(|source| {
283 NoteError::other_with_source("failed to decode note type from metadata header", source)
284 })?;
285
286 let sender_suffix =
288 Felt::try_from(element.as_int() & SENDER_SUFFIX_MASK).expect("felt should still be valid");
289
290 Ok((sender_suffix, note_type))
291}
292
293fn merge_attachment_kind_scheme(
301 attachment_kind: NoteAttachmentKind,
302 attachment_scheme: NoteAttachmentScheme,
303) -> Felt {
304 debug_assert!(attachment_kind.as_u8() < 4, "attachment kind should fit into two bits");
305 let mut merged = (attachment_kind.as_u8() as u64) << 32;
306 let attachment_scheme = attachment_scheme.as_u32();
307 merged |= attachment_scheme as u64;
308
309 Felt::try_from(merged).expect("the upper bit should be zero and the felt therefore valid")
310}
311
312fn unmerge_attachment_kind_scheme(
314 element: Felt,
315) -> Result<(NoteAttachmentKind, NoteAttachmentScheme), NoteError> {
316 let attachment_scheme = element.as_int() as u32;
317 let attachment_kind = (element.as_int() >> 32) as u8;
318
319 let attachment_scheme = NoteAttachmentScheme::new(attachment_scheme);
320 let attachment_kind = NoteAttachmentKind::try_from(attachment_kind).map_err(|source| {
321 NoteError::other_with_source(
322 "failed to decode attachment kind from metadata header",
323 source,
324 )
325 })?;
326
327 Ok((attachment_kind, attachment_scheme))
328}
329
330#[cfg(test)]
334mod tests {
335
336 use super::*;
337 use crate::note::NoteAttachmentScheme;
338 use crate::testing::account_id::ACCOUNT_ID_MAX_ONES;
339
340 #[rstest::rstest]
341 #[case::attachment_none(NoteAttachment::default())]
342 #[case::attachment_raw(NoteAttachment::new_word(NoteAttachmentScheme::new(0), Word::from([3, 4, 5, 6u32])))]
343 #[case::attachment_commitment(NoteAttachment::new_array(
344 NoteAttachmentScheme::new(u32::MAX),
345 vec![Felt::new(5), Felt::new(6), Felt::new(7)],
346 )?)]
347 #[test]
348 fn note_metadata_serde(#[case] attachment: NoteAttachment) -> anyhow::Result<()> {
349 let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
352 let note_type = NoteType::Public;
353 let tag = NoteTag::new(u32::MAX);
354 let metadata = NoteMetadata::new(sender, note_type, tag).with_attachment(attachment);
355
356 let deserialized = NoteMetadata::read_from_bytes(&metadata.to_bytes())?;
358 assert_eq!(deserialized, metadata);
359
360 let header = NoteMetadataHeader::try_from(metadata.to_header_word())?;
362 assert_eq!(header, metadata.to_header());
363
364 Ok(())
365 }
366}