use super::{
AccountId,
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Felt,
NoteTag,
NoteType,
Serializable,
Word,
};
use crate::Hasher;
use crate::errors::NoteError;
use crate::note::{NoteAttachment, NoteAttachmentKind, NoteAttachmentScheme};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NoteMetadata {
sender: AccountId,
note_type: NoteType,
tag: NoteTag,
attachment: NoteAttachment,
}
impl NoteMetadata {
pub fn new(sender: AccountId, note_type: NoteType) -> Self {
Self {
sender,
note_type,
tag: NoteTag::default(),
attachment: NoteAttachment::default(),
}
}
pub fn try_from_header(
header: NoteMetadataHeader,
attachment: NoteAttachment,
) -> Result<Self, NoteError> {
if header.attachment_kind != attachment.attachment_kind() {
return Err(NoteError::AttachmentKindMismatch {
header_kind: header.attachment_kind,
attachment_kind: attachment.attachment_kind(),
});
}
if header.attachment_scheme != attachment.attachment_scheme() {
return Err(NoteError::AttachmentSchemeMismatch {
header_scheme: header.attachment_scheme,
attachment_scheme: attachment.attachment_scheme(),
});
}
Ok(Self {
sender: header.sender,
note_type: header.note_type,
tag: header.tag,
attachment,
})
}
pub fn sender(&self) -> AccountId {
self.sender
}
pub fn note_type(&self) -> NoteType {
self.note_type
}
pub fn tag(&self) -> NoteTag {
self.tag
}
pub fn attachment(&self) -> &NoteAttachment {
&self.attachment
}
pub fn is_private(&self) -> bool {
self.note_type == NoteType::Private
}
pub fn to_header(&self) -> NoteMetadataHeader {
NoteMetadataHeader {
sender: self.sender,
note_type: self.note_type,
tag: self.tag,
attachment_kind: self.attachment().content().attachment_kind(),
attachment_scheme: self.attachment.attachment_scheme(),
}
}
pub fn to_header_word(&self) -> Word {
Word::from(self.to_header())
}
pub fn to_attachment_word(&self) -> Word {
self.attachment.content().to_word()
}
pub fn to_commitment(&self) -> Word {
Hasher::merge(&[self.to_header_word(), self.to_attachment_word()])
}
pub fn set_tag(&mut self, tag: NoteTag) {
self.tag = tag;
}
pub fn with_tag(mut self, tag: NoteTag) -> Self {
self.tag = tag;
self
}
pub fn set_attachment(&mut self, attachment: NoteAttachment) {
self.attachment = attachment;
}
pub fn with_attachment(mut self, attachment: NoteAttachment) -> Self {
self.attachment = attachment;
self
}
}
impl Serializable for NoteMetadata {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.note_type().write_into(target);
self.sender().write_into(target);
self.tag().write_into(target);
self.attachment().write_into(target);
}
fn get_size_hint(&self) -> usize {
self.note_type().get_size_hint()
+ self.sender().get_size_hint()
+ self.tag().get_size_hint()
+ self.attachment().get_size_hint()
}
}
impl Deserializable for NoteMetadata {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let note_type = NoteType::read_from(source)?;
let sender = AccountId::read_from(source)?;
let tag = NoteTag::read_from(source)?;
let attachment = NoteAttachment::read_from(source)?;
Ok(NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NoteMetadataHeader {
sender: AccountId,
note_type: NoteType,
tag: NoteTag,
attachment_kind: NoteAttachmentKind,
attachment_scheme: NoteAttachmentScheme,
}
impl NoteMetadataHeader {
pub fn sender(&self) -> AccountId {
self.sender
}
pub fn note_type(&self) -> NoteType {
self.note_type
}
pub fn tag(&self) -> NoteTag {
self.tag
}
pub fn attachment_kind(&self) -> NoteAttachmentKind {
self.attachment_kind
}
pub fn attachment_scheme(&self) -> NoteAttachmentScheme {
self.attachment_scheme
}
}
impl From<NoteMetadataHeader> for Word {
fn from(header: NoteMetadataHeader) -> Self {
let mut metadata = Word::empty();
metadata[0] = merge_sender_suffix_and_note_type(header.sender.suffix(), header.note_type);
metadata[1] = header.sender.prefix().as_felt();
metadata[2] = Felt::from(header.tag);
metadata[3] =
merge_attachment_kind_scheme(header.attachment_kind, header.attachment_scheme);
metadata
}
}
impl TryFrom<Word> for NoteMetadataHeader {
type Error = NoteError;
fn try_from(word: Word) -> Result<Self, Self::Error> {
let (sender_suffix, note_type) = unmerge_sender_suffix_and_note_type(word[0])?;
let sender_prefix = word[1];
let tag = u32::try_from(word[2].as_canonical_u64()).map(NoteTag::new).map_err(|_| {
NoteError::other("failed to convert note tag from metadata header to u32")
})?;
let (attachment_kind, attachment_scheme) = unmerge_attachment_kind_scheme(word[3])?;
let sender =
AccountId::try_from_elements(sender_suffix, sender_prefix).map_err(|source| {
NoteError::other_with_source(
"failed to decode account ID from metadata header",
source,
)
})?;
Ok(Self {
sender,
note_type,
tag,
attachment_kind,
attachment_scheme,
})
}
}
fn merge_sender_suffix_and_note_type(sender_id_suffix: Felt, note_type: NoteType) -> Felt {
let mut merged = sender_id_suffix.as_canonical_u64();
let note_type_byte = note_type as u8;
debug_assert!(note_type_byte < 4, "note type must not contain values >= 4");
merged |= note_type_byte as u64;
Felt::try_from(merged).expect("encoded value should be a valid felt")
}
fn unmerge_sender_suffix_and_note_type(element: Felt) -> Result<(Felt, NoteType), NoteError> {
const NOTE_TYPE_MASK: u8 = 0b11;
const SENDER_SUFFIX_MASK: u64 = !(NOTE_TYPE_MASK as u64);
let note_type_byte = element.as_canonical_u64() as u8 & NOTE_TYPE_MASK;
let note_type = NoteType::try_from(note_type_byte).map_err(|source| {
NoteError::other_with_source("failed to decode note type from metadata header", source)
})?;
let sender_suffix = Felt::try_from(element.as_canonical_u64() & SENDER_SUFFIX_MASK)
.expect("felt should still be valid");
Ok((sender_suffix, note_type))
}
fn merge_attachment_kind_scheme(
attachment_kind: NoteAttachmentKind,
attachment_scheme: NoteAttachmentScheme,
) -> Felt {
debug_assert!(attachment_kind.as_u8() < 4, "attachment kind should fit into two bits");
let mut merged = (attachment_kind.as_u8() as u64) << 32;
let attachment_scheme = attachment_scheme.as_u32();
merged |= attachment_scheme as u64;
Felt::try_from(merged).expect("the upper bit should be zero and the felt therefore valid")
}
fn unmerge_attachment_kind_scheme(
element: Felt,
) -> Result<(NoteAttachmentKind, NoteAttachmentScheme), NoteError> {
let attachment_scheme = element.as_canonical_u64() as u32;
let attachment_kind = (element.as_canonical_u64() >> 32) as u8;
let attachment_scheme = NoteAttachmentScheme::new(attachment_scheme);
let attachment_kind = NoteAttachmentKind::try_from(attachment_kind).map_err(|source| {
NoteError::other_with_source(
"failed to decode attachment kind from metadata header",
source,
)
})?;
Ok((attachment_kind, attachment_scheme))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::note::NoteAttachmentScheme;
use crate::testing::account_id::ACCOUNT_ID_MAX_ONES;
#[rstest::rstest]
#[case::attachment_none(NoteAttachment::default())]
#[case::attachment_raw(NoteAttachment::new_word(NoteAttachmentScheme::new(0), Word::from([3, 4, 5, 6u32])))]
#[case::attachment_commitment(NoteAttachment::new_array(
NoteAttachmentScheme::new(u32::MAX),
vec![Felt::new(5), Felt::new(6), Felt::new(7)],
)?)]
#[test]
fn note_metadata_serde(#[case] attachment: NoteAttachment) -> anyhow::Result<()> {
let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
let note_type = NoteType::Public;
let tag = NoteTag::new(u32::MAX);
let metadata =
NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment);
let deserialized = NoteMetadata::read_from_bytes(&metadata.to_bytes())?;
assert_eq!(deserialized, metadata);
let header = NoteMetadataHeader::try_from(metadata.to_header_word())?;
assert_eq!(header, metadata.to_header());
Ok(())
}
}