1use 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 try_from_header(
99 header: NoteMetadataHeader,
100 attachment: NoteAttachment,
101 ) -> Result<Self, NoteError> {
102 if header.attachment_kind != attachment.attachment_kind() {
103 return Err(NoteError::AttachmentKindMismatch {
104 header_kind: header.attachment_kind,
105 attachment_kind: attachment.attachment_kind(),
106 });
107 }
108
109 if header.attachment_scheme != attachment.attachment_scheme() {
110 return Err(NoteError::AttachmentSchemeMismatch {
111 header_scheme: header.attachment_scheme,
112 attachment_scheme: attachment.attachment_scheme(),
113 });
114 }
115
116 Ok(Self {
117 sender: header.sender,
118 note_type: header.note_type,
119 tag: header.tag,
120 attachment,
121 })
122 }
123
124 pub fn sender(&self) -> AccountId {
129 self.sender
130 }
131
132 pub fn note_type(&self) -> NoteType {
134 self.note_type
135 }
136
137 pub fn tag(&self) -> NoteTag {
139 self.tag
140 }
141
142 pub fn attachment(&self) -> &NoteAttachment {
144 &self.attachment
145 }
146
147 pub fn is_private(&self) -> bool {
149 self.note_type == NoteType::Private
150 }
151
152 pub fn to_header(&self) -> NoteMetadataHeader {
156 NoteMetadataHeader {
157 sender: self.sender,
158 note_type: self.note_type,
159 tag: self.tag,
160 attachment_kind: self.attachment().content().attachment_kind(),
161 attachment_scheme: self.attachment.attachment_scheme(),
162 }
163 }
164
165 pub fn to_header_word(&self) -> Word {
169 Word::from(self.to_header())
170 }
171
172 pub fn to_attachment_word(&self) -> Word {
176 self.attachment.content().to_word()
177 }
178
179 pub fn to_commitment(&self) -> Word {
185 Hasher::merge(&[self.to_header_word(), self.to_attachment_word()])
186 }
187
188 pub fn set_tag(&mut self, tag: NoteTag) {
193 self.tag = tag;
194 }
195
196 pub fn with_tag(mut self, tag: NoteTag) -> Self {
200 self.tag = tag;
201 self
202 }
203
204 pub fn set_attachment(&mut self, attachment: NoteAttachment) {
206 self.attachment = attachment;
207 }
208
209 pub fn with_attachment(mut self, attachment: NoteAttachment) -> Self {
213 self.attachment = attachment;
214 self
215 }
216}
217
218impl Serializable for NoteMetadata {
222 fn write_into<W: ByteWriter>(&self, target: &mut W) {
223 self.note_type().write_into(target);
224 self.sender().write_into(target);
225 self.tag().write_into(target);
226 self.attachment().write_into(target);
227 }
228
229 fn get_size_hint(&self) -> usize {
230 self.note_type().get_size_hint()
231 + self.sender().get_size_hint()
232 + self.tag().get_size_hint()
233 + self.attachment().get_size_hint()
234 }
235}
236
237impl Deserializable for NoteMetadata {
238 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
239 let note_type = NoteType::read_from(source)?;
240 let sender = AccountId::read_from(source)?;
241 let tag = NoteTag::read_from(source)?;
242 let attachment = NoteAttachment::read_from(source)?;
243
244 Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment))
245 }
246}
247
248#[derive(Clone, Copy, Debug, Eq, PartialEq)]
255pub struct NoteMetadataHeader {
256 sender: AccountId,
257 note_type: NoteType,
258 tag: NoteTag,
259 attachment_kind: NoteAttachmentKind,
260 attachment_scheme: NoteAttachmentScheme,
261}
262
263impl NoteMetadataHeader {
264 pub fn sender(&self) -> AccountId {
269 self.sender
270 }
271
272 pub fn note_type(&self) -> NoteType {
274 self.note_type
275 }
276
277 pub fn tag(&self) -> NoteTag {
279 self.tag
280 }
281
282 pub fn attachment_kind(&self) -> NoteAttachmentKind {
284 self.attachment_kind
285 }
286
287 pub fn attachment_scheme(&self) -> NoteAttachmentScheme {
289 self.attachment_scheme
290 }
291}
292
293impl From<NoteMetadataHeader> for Word {
294 fn from(header: NoteMetadataHeader) -> Self {
295 let mut metadata = Word::empty();
296
297 metadata[0] = merge_sender_suffix_and_note_type(header.sender.suffix(), header.note_type);
298 metadata[1] = header.sender.prefix().as_felt();
299 metadata[2] = Felt::from(header.tag);
300 metadata[3] =
301 merge_attachment_kind_scheme(header.attachment_kind, header.attachment_scheme);
302
303 metadata
304 }
305}
306
307impl TryFrom<Word> for NoteMetadataHeader {
308 type Error = NoteError;
309
310 fn try_from(word: Word) -> Result<Self, Self::Error> {
312 let (sender_suffix, note_type) = unmerge_sender_suffix_and_note_type(word[0])?;
313 let sender_prefix = word[1];
314 let tag = u32::try_from(word[2].as_canonical_u64()).map(NoteTag::new).map_err(|_| {
315 NoteError::other("failed to convert note tag from metadata header to u32")
316 })?;
317 let (attachment_kind, attachment_scheme) = unmerge_attachment_kind_scheme(word[3])?;
318
319 let sender =
320 AccountId::try_from_elements(sender_suffix, sender_prefix).map_err(|source| {
321 NoteError::other_with_source(
322 "failed to decode account ID from metadata header",
323 source,
324 )
325 })?;
326
327 Ok(Self {
328 sender,
329 note_type,
330 tag,
331 attachment_kind,
332 attachment_scheme,
333 })
334 }
335}
336
337fn merge_sender_suffix_and_note_type(sender_id_suffix: Felt, note_type: NoteType) -> Felt {
353 let mut merged = sender_id_suffix.as_canonical_u64();
354
355 let note_type_byte = note_type as u8;
356 debug_assert!(note_type_byte < 4, "note type must not contain values >= 4");
357 merged |= note_type_byte as u64;
358
359 Felt::try_from(merged).expect("encoded value should be a valid felt")
362}
363
364fn unmerge_sender_suffix_and_note_type(element: Felt) -> Result<(Felt, NoteType), NoteError> {
366 const NOTE_TYPE_MASK: u8 = 0b11;
367 const SENDER_SUFFIX_MASK: u64 = !(NOTE_TYPE_MASK as u64);
369
370 let note_type_byte = element.as_canonical_u64() as u8 & NOTE_TYPE_MASK;
371 let note_type = NoteType::try_from(note_type_byte).map_err(|source| {
372 NoteError::other_with_source("failed to decode note type from metadata header", source)
373 })?;
374
375 let sender_suffix = Felt::try_from(element.as_canonical_u64() & SENDER_SUFFIX_MASK)
377 .expect("felt should still be valid");
378
379 Ok((sender_suffix, note_type))
380}
381
382fn merge_attachment_kind_scheme(
390 attachment_kind: NoteAttachmentKind,
391 attachment_scheme: NoteAttachmentScheme,
392) -> Felt {
393 debug_assert!(attachment_kind.as_u8() < 4, "attachment kind should fit into two bits");
394 let mut merged = (attachment_kind.as_u8() as u64) << 32;
395 let attachment_scheme = attachment_scheme.as_u32();
396 merged |= attachment_scheme as u64;
397
398 Felt::try_from(merged).expect("the upper bit should be zero and the felt therefore valid")
399}
400
401fn unmerge_attachment_kind_scheme(
403 element: Felt,
404) -> Result<(NoteAttachmentKind, NoteAttachmentScheme), NoteError> {
405 let attachment_scheme = element.as_canonical_u64() as u32;
406 let attachment_kind = (element.as_canonical_u64() >> 32) as u8;
407
408 let attachment_scheme = NoteAttachmentScheme::new(attachment_scheme);
409 let attachment_kind = NoteAttachmentKind::try_from(attachment_kind).map_err(|source| {
410 NoteError::other_with_source(
411 "failed to decode attachment kind from metadata header",
412 source,
413 )
414 })?;
415
416 Ok((attachment_kind, attachment_scheme))
417}
418
419#[cfg(test)]
423mod tests {
424
425 use super::*;
426 use crate::note::NoteAttachmentScheme;
427 use crate::testing::account_id::ACCOUNT_ID_MAX_ONES;
428
429 #[rstest::rstest]
430 #[case::attachment_none(NoteAttachment::default())]
431 #[case::attachment_raw(NoteAttachment::new_word(NoteAttachmentScheme::new(0), Word::from([3, 4, 5, 6u32])))]
432 #[case::attachment_commitment(NoteAttachment::new_array(
433 NoteAttachmentScheme::new(u32::MAX),
434 vec![Felt::new(5), Felt::new(6), Felt::new(7)],
435 )?)]
436 #[test]
437 fn note_metadata_serde(#[case] attachment: NoteAttachment) -> anyhow::Result<()> {
438 let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
441 let note_type = NoteType::Public;
442 let tag = NoteTag::new(u32::MAX);
443 let metadata =
444 NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment);
445
446 let deserialized = NoteMetadata::read_from_bytes(&metadata.to_bytes())?;
448 assert_eq!(deserialized, metadata);
449
450 let header = NoteMetadataHeader::try_from(metadata.to_header_word())?;
452 assert_eq!(header, metadata.to_header());
453
454 Ok(())
455 }
456}