use core::fmt;
use miden_crypto::Felt;
use super::{
AccountId,
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
NoteError,
Serializable,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct NoteTag(u32);
impl NoteTag {
pub const DEFAULT_ACCOUNT_TARGET_TAG_LENGTH: u8 = 14;
pub const MAX_ACCOUNT_TARGET_TAG_LENGTH: u8 = 32;
pub const fn new(tag: u32) -> Self {
Self(tag)
}
pub fn with_account_target(account_id: AccountId) -> Self {
Self::with_custom_account_target(account_id, Self::DEFAULT_ACCOUNT_TARGET_TAG_LENGTH)
.expect("default account target tag length must be valid")
}
pub fn with_custom_account_target(
account_id: AccountId,
tag_len: u8,
) -> Result<Self, NoteError> {
if tag_len > Self::MAX_ACCOUNT_TARGET_TAG_LENGTH {
return Err(NoteError::NoteTagLengthTooLarge(tag_len));
}
let prefix = account_id.prefix().as_u64();
let high_bits = (prefix >> 32) as u32;
let mask = u32::MAX.checked_shl(u32::BITS - tag_len as u32).unwrap_or(0);
let tag = high_bits & mask;
Ok(Self(tag))
}
pub fn as_u32(&self) -> u32 {
self.0
}
}
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(tag: u32) -> Self {
Self::new(tag)
}
}
impl From<NoteTag> for u32 {
fn from(tag: NoteTag) -> Self {
tag.as_u32()
}
}
impl From<NoteTag> for Felt {
fn from(tag: NoteTag) -> Self {
Felt::from(tag.as_u32())
}
}
impl Serializable for NoteTag {
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 NoteTag {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let tag = u32::read_from(source)?;
Ok(Self::new(tag))
}
}
#[cfg(test)]
mod tests {
use super::NoteTag;
use crate::account::{AccountId, AccountStorageMode};
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,
AccountIdBuilder,
};
#[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(),
AccountIdBuilder::new()
.storage_mode(AccountStorageMode::Private)
.build_with_seed([2; 32]),
];
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(),
AccountIdBuilder::new()
.storage_mode(AccountStorageMode::Public)
.build_with_seed([3; 32]),
];
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(),
AccountIdBuilder::new()
.storage_mode(AccountStorageMode::Network)
.build_with_seed([4; 32]),
];
for account_id in private_accounts
.iter()
.chain(public_accounts.iter())
.chain(network_accounts.iter())
{
let tag = NoteTag::with_account_target(*account_id);
assert_eq!(tag.as_u32() << 16, 0, "16 least significant bits should be zero");
let expected = ((account_id.prefix().as_u64() >> 32) as u32) >> 16;
let actual = tag.as_u32() >> 16;
assert_eq!(actual, expected, "14 most significant bits should match");
}
}
#[test]
fn from_custom_account_target() -> anyhow::Result<()> {
let account_id = AccountId::try_from(ACCOUNT_ID_SENDER)?;
let tag = NoteTag::with_custom_account_target(
account_id,
NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH,
)?;
assert_eq!(
(account_id.prefix().as_u64() >> 32) as u32,
tag.as_u32(),
"32 most significant bits should match"
);
Ok(())
}
}