use alloc::string::ToString;
use alloc::vec::Vec;
use crate::crypto::SequentialCommit;
use crate::errors::NoteError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{Felt, Hasher, Word};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NoteAttachment {
attachment_scheme: NoteAttachmentScheme,
content: NoteAttachmentContent,
}
impl NoteAttachment {
pub fn new(
attachment_scheme: NoteAttachmentScheme,
content: NoteAttachmentContent,
) -> Result<Self, NoteError> {
if content.attachment_kind().is_none() && !attachment_scheme.is_none() {
return Err(NoteError::AttachmentKindNoneMustHaveAttachmentSchemeNone);
}
Ok(Self { attachment_scheme, content })
}
pub fn new_word(attachment_scheme: NoteAttachmentScheme, word: Word) -> Self {
Self {
attachment_scheme,
content: NoteAttachmentContent::new_word(word),
}
}
pub fn new_array(
attachment_scheme: NoteAttachmentScheme,
elements: Vec<Felt>,
) -> Result<Self, NoteError> {
NoteAttachmentContent::new_array(elements)
.map(|content| Self { attachment_scheme, content })
}
pub fn attachment_scheme(&self) -> NoteAttachmentScheme {
self.attachment_scheme
}
pub fn attachment_kind(&self) -> NoteAttachmentKind {
self.content.attachment_kind()
}
pub fn content(&self) -> &NoteAttachmentContent {
&self.content
}
}
impl Serializable for NoteAttachment {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.attachment_scheme().write_into(target);
self.content().write_into(target);
}
fn get_size_hint(&self) -> usize {
self.attachment_scheme().get_size_hint() + self.content().get_size_hint()
}
}
impl Deserializable for NoteAttachment {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let attachment_scheme = NoteAttachmentScheme::read_from(source)?;
let content = NoteAttachmentContent::read_from(source)?;
Self::new(attachment_scheme, content)
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum NoteAttachmentContent {
#[default]
None,
Word(Word),
Array(NoteAttachmentArray),
}
impl NoteAttachmentContent {
pub fn empty_word() -> Self {
Self::Word(Word::empty())
}
pub fn new_word(word: Word) -> Self {
Self::Word(word)
}
pub fn new_array(elements: Vec<Felt>) -> Result<Self, NoteError> {
NoteAttachmentArray::new(elements).map(Self::from)
}
pub fn attachment_kind(&self) -> NoteAttachmentKind {
match self {
NoteAttachmentContent::None => NoteAttachmentKind::None,
NoteAttachmentContent::Word(_) => NoteAttachmentKind::Word,
NoteAttachmentContent::Array(_) => NoteAttachmentKind::Array,
}
}
pub fn to_word(&self) -> Word {
match self {
NoteAttachmentContent::None => Word::empty(),
NoteAttachmentContent::Word(word) => *word,
NoteAttachmentContent::Array(attachment_commitment) => {
attachment_commitment.commitment()
},
}
}
}
impl Serializable for NoteAttachmentContent {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.attachment_kind().write_into(target);
match self {
NoteAttachmentContent::None => (),
NoteAttachmentContent::Word(word) => {
word.write_into(target);
},
NoteAttachmentContent::Array(attachment_commitment) => {
attachment_commitment.num_elements().write_into(target);
target.write_many(&attachment_commitment.elements);
},
}
}
fn get_size_hint(&self) -> usize {
let kind_size = self.attachment_kind().get_size_hint();
match self {
NoteAttachmentContent::None => kind_size,
NoteAttachmentContent::Word(word) => kind_size + word.get_size_hint(),
NoteAttachmentContent::Array(attachment_commitment) => {
kind_size
+ attachment_commitment.num_elements().get_size_hint()
+ attachment_commitment.elements.len() * crate::ZERO.get_size_hint()
},
}
}
}
impl Deserializable for NoteAttachmentContent {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let attachment_kind = NoteAttachmentKind::read_from(source)?;
match attachment_kind {
NoteAttachmentKind::None => Ok(NoteAttachmentContent::None),
NoteAttachmentKind::Word => {
let word = Word::read_from(source)?;
Ok(NoteAttachmentContent::Word(word))
},
NoteAttachmentKind::Array => {
let num_elements = u16::read_from(source)?;
let elements =
source.read_many_iter(num_elements as usize)?.collect::<Result<_, _>>()?;
Self::new_array(elements)
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NoteAttachmentArray {
elements: Vec<Felt>,
commitment: Word,
}
impl NoteAttachmentArray {
pub const MAX_NUM_ELEMENTS: u16 = 2048;
pub fn new(elements: Vec<Felt>) -> Result<Self, NoteError> {
if elements.len() > Self::MAX_NUM_ELEMENTS as usize {
return Err(NoteError::NoteAttachmentArraySizeExceeded(elements.len()));
}
let commitment = Hasher::hash_elements(&elements);
Ok(Self { elements, commitment })
}
pub fn as_slice(&self) -> &[Felt] {
&self.elements
}
pub fn num_elements(&self) -> u16 {
u16::try_from(self.elements.len()).expect("type should enforce that size fits in u16")
}
pub fn commitment(&self) -> Word {
self.commitment
}
}
impl SequentialCommit for NoteAttachmentArray {
type Commitment = Word;
fn to_elements(&self) -> Vec<Felt> {
self.elements.clone()
}
fn to_commitment(&self) -> Self::Commitment {
self.commitment
}
}
impl From<NoteAttachmentArray> for NoteAttachmentContent {
fn from(array: NoteAttachmentArray) -> Self {
NoteAttachmentContent::Array(array)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoteAttachmentScheme(u32);
impl NoteAttachmentScheme {
const NONE: u32 = 0;
pub const fn new(attachment_scheme: u32) -> Self {
Self(attachment_scheme)
}
pub const fn none() -> Self {
Self(Self::NONE)
}
pub const fn is_none(&self) -> bool {
self.0 == Self::NONE
}
pub const fn as_u32(&self) -> u32 {
self.0
}
}
impl Default for NoteAttachmentScheme {
fn default() -> Self {
Self::none()
}
}
impl core::fmt::Display for NoteAttachmentScheme {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!("{}", self.0))
}
}
impl Serializable for NoteAttachmentScheme {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.as_u32().write_into(target);
}
fn get_size_hint(&self) -> usize {
core::mem::size_of::<u32>()
}
}
impl Deserializable for NoteAttachmentScheme {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let attachment_scheme = u32::read_from(source)?;
Ok(Self::new(attachment_scheme))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(u8)]
pub enum NoteAttachmentKind {
#[default]
None = Self::NONE,
Word = Self::WORD,
Array = Self::ARRAY,
}
impl NoteAttachmentKind {
const NONE: u8 = 0;
const WORD: u8 = 1;
const ARRAY: u8 = 2;
pub const fn as_u8(&self) -> u8 {
*self as u8
}
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub const fn is_word(&self) -> bool {
matches!(self, Self::Word)
}
pub const fn is_array(&self) -> bool {
matches!(self, Self::Array)
}
}
impl TryFrom<u8> for NoteAttachmentKind {
type Error = NoteError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
Self::NONE => Ok(Self::None),
Self::WORD => Ok(Self::Word),
Self::ARRAY => Ok(Self::Array),
_ => Err(NoteError::UnknownNoteAttachmentKind(value)),
}
}
}
impl core::fmt::Display for NoteAttachmentKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let output = match self {
NoteAttachmentKind::None => "None",
NoteAttachmentKind::Word => "Word",
NoteAttachmentKind::Array => "Array",
};
f.write_str(output)
}
}
impl Serializable for NoteAttachmentKind {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.as_u8().write_into(target);
}
fn get_size_hint(&self) -> usize {
core::mem::size_of::<u8>()
}
}
impl Deserializable for NoteAttachmentKind {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let attachment_kind = u8::read_from(source)?;
Self::try_from(attachment_kind)
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::*;
#[rstest::rstest]
#[case::attachment_none(NoteAttachment::default())]
#[case::attachment_word(NoteAttachment::new_word(NoteAttachmentScheme::new(1), Word::from([3, 4, 5, 6u32])))]
#[case::attachment_array(NoteAttachment::new_array(
NoteAttachmentScheme::new(u32::MAX),
vec![Felt::new(5), Felt::new(6), Felt::new(7)],
)?)]
#[test]
fn note_attachment_serde(#[case] attachment: NoteAttachment) -> anyhow::Result<()> {
assert_eq!(attachment, NoteAttachment::read_from_bytes(&attachment.to_bytes())?);
Ok(())
}
#[test]
fn note_attachment_commitment_fails_on_too_many_elements() -> anyhow::Result<()> {
let too_many_elements = (NoteAttachmentArray::MAX_NUM_ELEMENTS as usize) + 1;
let elements = vec![Felt::from(1u32); too_many_elements];
let err = NoteAttachmentArray::new(elements).unwrap_err();
assert_matches!(err, NoteError::NoteAttachmentArraySizeExceeded(len) => {
len == too_many_elements
});
Ok(())
}
#[test]
fn note_attachment_kind_fails_on_unknown_variant() -> anyhow::Result<()> {
let err = NoteAttachmentKind::try_from(3u8).unwrap_err();
assert_matches!(err, NoteError::UnknownNoteAttachmentKind(3u8));
Ok(())
}
}