use core::fmt;
use miden_crypto::Felt;
use super::{
AccountId,
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
NoteError,
NoteType,
Serializable,
};
use crate::account::AccountStorageMode;
const NETWORK_EXECUTION: u8 = 0;
const LOCAL_EXECUTION: u8 = 1;
const NETWORK_ACCOUNT: u32 = 0;
const NETWORK_PUBLIC_USECASE: u32 = 0x4000_0000;
const LOCAL_PUBLIC_ANY: u32 = 0x8000_0000;
const LOCAL_ANY: u32 = 0xc000_0000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum NoteExecutionMode {
Network = NETWORK_EXECUTION,
Local = LOCAL_EXECUTION,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum NoteTag {
NetworkAccount(u32),
NetworkUseCase(u16, u16),
LocalPublicAny(u32),
LocalAny(u32),
}
impl NoteTag {
pub(crate) const MAX_USE_CASE_ID_EXPONENT: u8 = 14;
pub const DEFAULT_LOCAL_TAG_LENGTH: u8 = 14;
pub const DEFAULT_NETWORK_TAG_LENGTH: u8 = 30;
pub const MAX_LOCAL_TAG_LENGTH: u8 = 30;
pub fn from_account_id(account_id: AccountId) -> Self {
match account_id.storage_mode() {
AccountStorageMode::Network => Self::from_network_account_id(account_id),
AccountStorageMode::Private | AccountStorageMode::Public => {
Self::from_local_account_id(account_id, Self::DEFAULT_LOCAL_TAG_LENGTH).unwrap()
},
}
}
pub(crate) fn from_local_account_id(
account_id: AccountId,
tag_len: u8,
) -> Result<Self, NoteError> {
if tag_len > Self::MAX_LOCAL_TAG_LENGTH {
return Err(NoteError::NoteTagLengthTooLarge(tag_len));
}
let prefix_id: u64 = account_id.prefix().into();
let high_bits = prefix_id >> 34;
let high_bits = high_bits as u32;
let high_bits = high_bits & (u32::MAX << (32 - 2 - tag_len));
Ok(Self::LocalAny(LOCAL_ANY | high_bits))
}
pub(crate) fn from_network_account_id(account_id: AccountId) -> Self {
let prefix_id: u64 = account_id.prefix().into();
let high_bits = prefix_id >> 34;
Self::NetworkAccount(high_bits as u32)
}
pub fn for_public_use_case(
use_case_id: u16,
payload: u16,
execution: NoteExecutionMode,
) -> Result<Self, NoteError> {
if (use_case_id >> 14) != 0 {
return Err(NoteError::NoteTagUseCaseTooLarge(use_case_id));
}
match execution {
NoteExecutionMode::Network => {
let use_case_bits = (NETWORK_PUBLIC_USECASE >> 16) as u16 | use_case_id;
Ok(Self::NetworkUseCase(use_case_bits, payload))
},
NoteExecutionMode::Local => {
let tag_u32 = LOCAL_PUBLIC_ANY | ((use_case_id as u32) << 16) | (payload as u32);
Ok(Self::LocalPublicAny(tag_u32))
},
}
}
pub fn for_local_use_case(use_case_id: u16, payload: u16) -> Result<Self, NoteError> {
if (use_case_id >> NoteTag::MAX_USE_CASE_ID_EXPONENT) != 0 {
return Err(NoteError::NoteTagUseCaseTooLarge(use_case_id));
}
let use_case_bits = (use_case_id as u32) << 16;
let payload_bits = payload as u32;
Ok(Self::LocalAny(LOCAL_ANY | use_case_bits | payload_bits))
}
pub fn is_single_target(&self) -> bool {
matches!(self, NoteTag::NetworkAccount(_))
}
pub fn execution_mode(&self) -> NoteExecutionMode {
match self {
NoteTag::NetworkAccount(_) | NoteTag::NetworkUseCase(..) => NoteExecutionMode::Network,
NoteTag::LocalAny(_) | NoteTag::LocalPublicAny(..) => NoteExecutionMode::Local,
}
}
pub fn as_u32(&self) -> u32 {
const LOW_BITS_MASK: u32 = 0x3fff_ffff;
match self {
NoteTag::NetworkAccount(tag) => *tag & LOW_BITS_MASK,
NoteTag::NetworkUseCase(use_case_bits, payload_bits) => {
((*use_case_bits as u32) << 16 | *payload_bits as u32) & LOW_BITS_MASK
| NETWORK_PUBLIC_USECASE
},
NoteTag::LocalPublicAny(tag) => (*tag & LOW_BITS_MASK) | LOCAL_PUBLIC_ANY,
NoteTag::LocalAny(tag) => (*tag & LOW_BITS_MASK) | LOCAL_ANY,
}
}
pub fn validate(&self, note_type: NoteType) -> Result<Self, NoteError> {
if self.execution_mode() == NoteExecutionMode::Network && note_type != NoteType::Public {
return Err(NoteError::NetworkExecutionRequiresPublicNote(note_type));
}
if self.requires_public_note() && note_type != NoteType::Public {
Err(NoteError::PublicNoteRequired(note_type))
} else {
Ok(*self)
}
}
fn requires_public_note(&self) -> bool {
matches!(
self,
NoteTag::NetworkAccount(_) | NoteTag::NetworkUseCase(_, _) | NoteTag::LocalPublicAny(_)
)
}
}
impl fmt::Display for NoteTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_u32())
}
}
impl From<u32> for NoteTag {
fn from(value: u32) -> Self {
match value & 0xc0000000 {
NETWORK_ACCOUNT => Self::NetworkAccount(value),
NETWORK_PUBLIC_USECASE => Self::NetworkUseCase((value >> 16) as u16, value as u16),
LOCAL_ANY => Self::LocalAny(value),
LOCAL_PUBLIC_ANY => Self::LocalPublicAny(value),
_ => {
unreachable!("Invalid value encountered: {:b}", value);
},
}
}
}
impl From<NoteTag> for u32 {
fn from(value: NoteTag) -> Self {
value.as_u32()
}
}
impl From<NoteTag> for Felt {
fn from(value: NoteTag) -> Self {
Felt::from(value.as_u32())
}
}
impl Serializable for NoteTag {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.as_u32().write_into(target);
}
}
impl Deserializable for NoteTag {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let tag = u32::read_from(source)?;
Ok(Self::from(tag))
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::{NoteExecutionMode, NoteTag};
use crate::NoteError;
use crate::account::AccountId;
use crate::note::NoteType;
use crate::note::note_tag::{
LOCAL_ANY,
LOCAL_PUBLIC_ANY,
NETWORK_ACCOUNT,
NETWORK_PUBLIC_USECASE,
};
use crate::testing::account_id::{
ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET,
ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_SENDER,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1,
ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE,
ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2,
ACCOUNT_ID_SENDER,
};
#[test]
fn from_account_id() {
let private_accounts = [
AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(),
AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap(),
AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(),
AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(),
];
let public_accounts = [
AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE).unwrap(),
AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2)
.unwrap(),
AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(),
AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1).unwrap(),
AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2).unwrap(),
AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap(),
AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(),
AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(),
];
let network_accounts = [
AccountId::try_from(ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE).unwrap(),
AccountId::try_from(ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET).unwrap(),
AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET).unwrap(),
];
for account_id in network_accounts {
let tag = NoteTag::from_account_id(account_id);
assert!(tag.is_single_target());
assert_eq!(tag.execution_mode(), NoteExecutionMode::Network);
tag.validate(NoteType::Public)
.expect("network execution should require notes to be public");
assert_matches!(
tag.validate(NoteType::Private),
Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
);
assert_matches!(
tag.validate(NoteType::Encrypted),
Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
);
}
for account_id in private_accounts {
let tag = NoteTag::from_account_id(account_id);
assert!(!tag.is_single_target());
assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
tag.validate(NoteType::Public)
.expect("local execution should support public notes");
tag.validate(NoteType::Private)
.expect("local execution should support private notes");
tag.validate(NoteType::Encrypted)
.expect("local execution should support encrypted notes");
}
for account_id in public_accounts {
let tag = NoteTag::from_account_id(account_id);
assert!(!tag.is_single_target());
assert_eq!(tag.execution_mode(), NoteExecutionMode::Local);
tag.validate(NoteType::Public)
.expect("local execution should support public notes");
tag.validate(NoteType::Private)
.expect("local execution should support private notes");
tag.validate(NoteType::Encrypted)
.expect("local execution should support encrypted notes");
}
for account_id in network_accounts {
let tag = NoteTag::from_account_id(account_id);
assert!(tag.is_single_target());
assert_eq!(tag.execution_mode(), NoteExecutionMode::Network);
tag.validate(NoteType::Public)
.expect("network execution should support public notes");
assert_matches!(
tag.validate(NoteType::Private),
Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private))
);
assert_matches!(
tag.validate(NoteType::Encrypted),
Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted))
);
}
}
#[test]
fn from_private_account_id() {
const PRIVATE_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE
| 0x0055_0000_0000_0000_0000_0000_0000_0000;
let private_account_id = AccountId::try_from(PRIVATE_ACCOUNT_INT).unwrap();
let expected_private_tag = 0b11110011_00010101_00000000_00000000;
assert_eq!(NoteTag::from_account_id(private_account_id).as_u32(), expected_private_tag);
}
#[test]
fn from_public_account_id() {
const PUBLIC_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE
| 0x0055_ccaa_0000_0000_0000_0000_0000_0000;
let public_account_id = AccountId::try_from(PUBLIC_ACCOUNT_INT).unwrap();
let expected_public_local_tag = 0b11101010_10010101_00000000_00000000u32;
assert_eq!(NoteTag::from_account_id(public_account_id).as_u32(), expected_public_local_tag);
}
#[test]
fn from_network_account_id() {
const NETWORK_ACCOUNT_INT: u128 = ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE
| 0x00cc_77cc_0000_0000_0000_0000_0000_0000;
let network_account_id = AccountId::try_from(NETWORK_ACCOUNT_INT).unwrap();
let expected_network_tag = 0b00101010_10110011_00011101_11110011;
assert_eq!(NoteTag::from_account_id(network_account_id).as_u32(), expected_network_tag);
}
#[test]
fn for_public_use_case() {
let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Network).unwrap();
assert_eq!(tag.as_u32(), 0b01000000_00000000_00000000_00000000u32);
tag.validate(NoteType::Public).unwrap();
assert_matches!(
tag.validate(NoteType::Private).unwrap_err(),
NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private)
);
assert_matches!(
tag.validate(NoteType::Encrypted).unwrap_err(),
NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted)
);
let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Network).unwrap();
assert_eq!(tag.as_u32(), 0b01000000_00000001_00000000_00000000u32);
let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Network).unwrap();
assert_eq!(tag.as_u32(), 0b01000000_00000000_00000000_00000001u32);
let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Network).unwrap();
assert_eq!(tag.as_u32(), 0b01100000_00000000_00000000_00000000u32);
let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Local).unwrap();
assert_eq!(tag.as_u32(), 0b10000000_00000000_00000000_00000000u32);
tag.validate(NoteType::Public).unwrap();
assert_matches!(
tag.validate(NoteType::Private).unwrap_err(),
NoteError::PublicNoteRequired(NoteType::Private)
);
assert_matches!(
tag.validate(NoteType::Encrypted).unwrap_err(),
NoteError::PublicNoteRequired(NoteType::Encrypted)
);
let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Local).unwrap();
assert_eq!(tag.as_u32(), 0b10000000_00000000_00000000_00000001u32);
let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Local).unwrap();
assert_eq!(tag.as_u32(), 0b10000000_00000001_00000000_00000000u32);
let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Local).unwrap();
assert_eq!(tag.as_u32(), 0b10100000_00000000_00000000_00000000u32);
assert_matches!(
NoteTag::for_public_use_case(1 << 15, 0b0, NoteExecutionMode::Local).unwrap_err(),
NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
);
assert_matches!(
NoteTag::for_public_use_case(1 << 14, 0b0, NoteExecutionMode::Local).unwrap_err(),
NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
);
}
#[test]
fn for_private_use_case() {
let tag = NoteTag::for_local_use_case(0b0, 0b0).unwrap();
assert_eq!(
tag.as_u32() >> 30,
LOCAL_ANY >> 30,
"local use case prefix should be local any"
);
assert_eq!(tag.as_u32(), 0b11000000_00000000_00000000_00000000u32);
tag.validate(NoteType::Public)
.expect("local execution should support public notes");
tag.validate(NoteType::Private)
.expect("local execution should support private notes");
tag.validate(NoteType::Encrypted)
.expect("local execution should support encrypted notes");
let tag = NoteTag::for_local_use_case(0b0, 0b1).unwrap();
assert_eq!(tag.as_u32(), 0b11000000_00000000_00000000_00000001u32);
let tag = NoteTag::for_local_use_case(0b1, 0b0).unwrap();
assert_eq!(tag.as_u32(), 0b11000000_00000001_00000000_00000000u32);
let tag = NoteTag::for_local_use_case(1 << 13, 0b0).unwrap();
assert_eq!(tag.as_u32(), 0b11100000_00000000_00000000_00000000u32);
assert_matches!(
NoteTag::for_local_use_case(1 << 15, 0b0).unwrap_err(),
NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 15
);
assert_matches!(
NoteTag::for_local_use_case(1 << 14, 0b0).unwrap_err(),
NoteError::NoteTagUseCaseTooLarge(use_case) if use_case == 1 << 14
);
}
#[test]
fn note_tag_as_u32() {
const HIGH_BITS_MASK: u32 = 0xc000_0000;
assert_eq!(NoteTag::NetworkAccount(u32::MAX).as_u32() & HIGH_BITS_MASK, NETWORK_ACCOUNT);
assert_eq!(
NoteTag::NetworkUseCase(u16::MAX, u16::MAX).as_u32() & HIGH_BITS_MASK,
NETWORK_PUBLIC_USECASE
);
assert_eq!(NoteTag::LocalPublicAny(u32::MAX).as_u32() & HIGH_BITS_MASK, LOCAL_PUBLIC_ANY);
assert_eq!(NoteTag::LocalAny(0).as_u32() & HIGH_BITS_MASK, LOCAL_ANY);
}
}