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) -> Self {
84 Self {
85 sender,
86 note_type,
87 tag: NoteTag::default(),
88 attachment: NoteAttachment::default(),
89 }
90 }
91
92 pub fn sender(&self) -> AccountId {
97 self.sender
98 }
99
100 pub fn note_type(&self) -> NoteType {
102 self.note_type
103 }
104
105 pub fn tag(&self) -> NoteTag {
107 self.tag
108 }
109
110 pub fn attachment(&self) -> &NoteAttachment {
112 &self.attachment
113 }
114
115 pub fn is_private(&self) -> bool {
117 self.note_type == NoteType::Private
118 }
119
120 fn to_header(&self) -> NoteMetadataHeader {
124 NoteMetadataHeader {
125 sender: self.sender,
126 note_type: self.note_type,
127 tag: self.tag,
128 attachment_kind: self.attachment().content().attachment_kind(),
129 attachment_scheme: self.attachment.attachment_scheme(),
130 }
131 }
132
133 pub fn to_header_word(&self) -> Word {
137 Word::from(self.to_header())
138 }
139
140 pub fn to_attachment_word(&self) -> Word {
144 self.attachment.content().to_word()
145 }
146
147 pub fn to_commitment(&self) -> Word {
153 Hasher::merge(&[self.to_header_word(), self.to_attachment_word()])
154 }
155
156 pub fn set_tag(&mut self, tag: NoteTag) {
161 self.tag = tag;
162 }
163
164 pub fn with_tag(mut self, tag: NoteTag) -> Self {
168 self.tag = tag;
169 self
170 }
171
172 pub fn set_attachment(&mut self, attachment: NoteAttachment) {
174 self.attachment = attachment;
175 }
176
177 pub fn with_attachment(mut self, attachment: NoteAttachment) -> Self {
181 self.attachment = attachment;
182 self
183 }
184}
185
186impl Serializable for NoteMetadata {
190 fn write_into<W: ByteWriter>(&self, target: &mut W) {
191 self.note_type().write_into(target);
192 self.sender().write_into(target);
193 self.tag().write_into(target);
194 self.attachment().write_into(target);
195 }
196}
197
198impl Deserializable for NoteMetadata {
199 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
200 let note_type = NoteType::read_from(source)?;
201 let sender = AccountId::read_from(source)?;
202 let tag = NoteTag::read_from(source)?;
203 let attachment = NoteAttachment::read_from(source)?;
204
205 Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment))
206 }
207}
208
209#[derive(Clone, Copy, Debug, Eq, PartialEq)]
218struct NoteMetadataHeader {
219 sender: AccountId,
220 note_type: NoteType,
221 tag: NoteTag,
222 attachment_kind: NoteAttachmentKind,
223 attachment_scheme: NoteAttachmentScheme,
224}
225
226impl From<NoteMetadataHeader> for Word {
227 fn from(header: NoteMetadataHeader) -> Self {
228 let mut metadata = Word::empty();
229
230 metadata[0] = merge_sender_suffix_and_note_type(header.sender.suffix(), header.note_type);
231 metadata[1] = header.sender.prefix().as_felt();
232 metadata[2] = Felt::from(header.tag);
233 metadata[3] =
234 merge_attachment_kind_scheme(header.attachment_kind, header.attachment_scheme);
235
236 metadata
237 }
238}
239
240impl TryFrom<Word> for NoteMetadataHeader {
241 type Error = NoteError;
242
243 fn try_from(word: Word) -> Result<Self, Self::Error> {
245 let (sender_suffix, note_type) = unmerge_sender_suffix_and_note_type(word[0])?;
246 let sender_prefix = word[1];
247 let tag = u32::try_from(word[2]).map(NoteTag::new).map_err(|_| {
248 NoteError::other("failed to convert note tag from metadata header to u32")
249 })?;
250 let (attachment_kind, attachment_scheme) = unmerge_attachment_kind_scheme(word[3])?;
251
252 let sender = AccountId::try_from([sender_prefix, sender_suffix]).map_err(|source| {
253 NoteError::other_with_source("failed to decode account ID from metadata header", source)
254 })?;
255
256 Ok(Self {
257 sender,
258 note_type,
259 tag,
260 attachment_kind,
261 attachment_scheme,
262 })
263 }
264}
265
266fn merge_sender_suffix_and_note_type(sender_id_suffix: Felt, note_type: NoteType) -> Felt {
282 let mut merged = sender_id_suffix.as_int();
283
284 let note_type_byte = note_type as u8;
285 debug_assert!(note_type_byte < 4, "note type must not contain values >= 4");
286 merged |= note_type_byte as u64;
287
288 Felt::try_from(merged).expect("encoded value should be a valid felt")
291}
292
293fn unmerge_sender_suffix_and_note_type(element: Felt) -> Result<(Felt, NoteType), NoteError> {
295 const NOTE_TYPE_MASK: u8 = 0b11;
296 const SENDER_SUFFIX_MASK: u64 = !(NOTE_TYPE_MASK as u64);
298
299 let note_type_byte = element.as_int() as u8 & NOTE_TYPE_MASK;
300 let note_type = NoteType::try_from(note_type_byte).map_err(|source| {
301 NoteError::other_with_source("failed to decode note type from metadata header", source)
302 })?;
303
304 let sender_suffix =
306 Felt::try_from(element.as_int() & SENDER_SUFFIX_MASK).expect("felt should still be valid");
307
308 Ok((sender_suffix, note_type))
309}
310
311fn merge_attachment_kind_scheme(
319 attachment_kind: NoteAttachmentKind,
320 attachment_scheme: NoteAttachmentScheme,
321) -> Felt {
322 debug_assert!(attachment_kind.as_u8() < 4, "attachment kind should fit into two bits");
323 let mut merged = (attachment_kind.as_u8() as u64) << 32;
324 let attachment_scheme = attachment_scheme.as_u32();
325 merged |= attachment_scheme as u64;
326
327 Felt::try_from(merged).expect("the upper bit should be zero and the felt therefore valid")
328}
329
330fn unmerge_attachment_kind_scheme(
332 element: Felt,
333) -> Result<(NoteAttachmentKind, NoteAttachmentScheme), NoteError> {
334 let attachment_scheme = element.as_int() as u32;
335 let attachment_kind = (element.as_int() >> 32) as u8;
336
337 let attachment_scheme = NoteAttachmentScheme::new(attachment_scheme);
338 let attachment_kind = NoteAttachmentKind::try_from(attachment_kind).map_err(|source| {
339 NoteError::other_with_source(
340 "failed to decode attachment kind from metadata header",
341 source,
342 )
343 })?;
344
345 Ok((attachment_kind, attachment_scheme))
346}
347
348#[cfg(test)]
352mod tests {
353
354 use super::*;
355 use crate::note::NoteAttachmentScheme;
356 use crate::testing::account_id::ACCOUNT_ID_MAX_ONES;
357
358 #[rstest::rstest]
359 #[case::attachment_none(NoteAttachment::default())]
360 #[case::attachment_raw(NoteAttachment::new_word(NoteAttachmentScheme::new(0), Word::from([3, 4, 5, 6u32])))]
361 #[case::attachment_commitment(NoteAttachment::new_array(
362 NoteAttachmentScheme::new(u32::MAX),
363 vec![Felt::new(5), Felt::new(6), Felt::new(7)],
364 )?)]
365 #[test]
366 fn note_metadata_serde(#[case] attachment: NoteAttachment) -> anyhow::Result<()> {
367 let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
370 let note_type = NoteType::Public;
371 let tag = NoteTag::new(u32::MAX);
372 let metadata =
373 NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment);
374
375 let deserialized = NoteMetadata::read_from_bytes(&metadata.to_bytes())?;
377 assert_eq!(deserialized, metadata);
378
379 let header = NoteMetadataHeader::try_from(metadata.to_header_word())?;
381 assert_eq!(header, metadata.to_header());
382
383 Ok(())
384 }
385}