mod prefix;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use core::hash::Hash;
use bech32::Bech32m;
use bech32::primitives::decode::{ByteIter, CheckedHrpstring};
use miden_crypto::utils::hex_to_bytes;
pub use prefix::AccountIdPrefixV0;
use crate::account::account_id::NetworkId;
use crate::account::account_id::account_type::{
FUNGIBLE_FAUCET,
NON_FUNGIBLE_FAUCET,
REGULAR_ACCOUNT_IMMUTABLE_CODE,
REGULAR_ACCOUNT_UPDATABLE_CODE,
};
use crate::account::account_id::storage_mode::{NETWORK, PRIVATE, PUBLIC};
use crate::account::{AccountIdVersion, AccountStorageMode, AccountType};
use crate::address::AddressType;
use crate::errors::{AccountIdError, Bech32Error};
use crate::utils::{ByteReader, Deserializable, DeserializationError, Serializable};
use crate::{AccountError, EMPTY_WORD, Felt, Hasher, Word};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct AccountIdV0 {
prefix: Felt,
suffix: Felt,
}
impl Hash for AccountIdV0 {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.prefix.inner().hash(state);
self.suffix.inner().hash(state);
}
}
impl AccountIdV0 {
const SERIALIZED_SIZE: usize = 15;
pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT;
pub(crate) const TYPE_SHIFT: u64 = 4;
const VERSION_MASK: u64 = 0b1111;
pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT;
pub(crate) const STORAGE_MODE_SHIFT: u64 = 6;
pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT;
pub fn new(
seed: Word,
code_commitment: Word,
storage_commitment: Word,
) -> Result<Self, AccountIdError> {
let seed_digest = compute_digest(seed, code_commitment, storage_commitment);
let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2]
.try_into()
.expect("we should have sliced off 2 elements");
felts[1] = shape_suffix(felts[1]);
account_id_from_felts(felts)
}
pub fn new_unchecked(elements: [Felt; 2]) -> Self {
let prefix = elements[0];
let suffix = elements[1];
if cfg!(debug_assertions) {
validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix");
validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix");
}
Self { prefix, suffix }
}
#[cfg(any(feature = "testing", test))]
pub fn dummy(
mut bytes: [u8; 15],
account_type: AccountType,
storage_mode: AccountStorageMode,
) -> AccountIdV0 {
let version = AccountIdVersion::Version0 as u8;
let low_nibble = ((storage_mode as u8) << Self::STORAGE_MODE_SHIFT)
| ((account_type as u8) << Self::TYPE_SHIFT)
| version;
bytes[7] = low_nibble;
bytes[3] &= 0b1111_1110;
let prefix_bytes =
bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes");
let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes))
.expect("should be a valid felt due to the most significant bit being zero");
let mut suffix_bytes = [0; 8];
suffix_bytes[..7].copy_from_slice(&bytes[8..]);
let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes));
suffix = Felt::try_from(suffix.as_int() & 0x7fff_ffff_ffff_ffff)
.expect("no bits were set so felt should still be valid");
suffix = shape_suffix(suffix);
let account_id = account_id_from_felts([prefix, suffix])
.expect("we should have shaped the felts to produce a valid id");
debug_assert_eq!(account_id.account_type(), account_type);
debug_assert_eq!(account_id.storage_mode(), storage_mode);
account_id
}
pub fn compute_account_seed(
init_seed: [u8; 32],
account_type: AccountType,
storage_mode: AccountStorageMode,
version: AccountIdVersion,
code_commitment: Word,
storage_commitment: Word,
) -> Result<Word, AccountError> {
crate::account::account_id::seed::compute_account_seed(
init_seed,
account_type,
storage_mode,
version,
code_commitment,
storage_commitment,
)
}
pub const fn account_type(&self) -> AccountType {
extract_type(self.prefix.as_int())
}
pub fn is_faucet(&self) -> bool {
self.account_type().is_faucet()
}
pub fn is_regular_account(&self) -> bool {
self.account_type().is_regular_account()
}
pub fn storage_mode(&self) -> AccountStorageMode {
extract_storage_mode(self.prefix().as_u64())
.expect("account ID should have been constructed with a valid storage mode")
}
pub fn is_public(&self) -> bool {
self.storage_mode() == AccountStorageMode::Public
}
pub fn version(&self) -> AccountIdVersion {
extract_version(self.prefix().as_u64())
.expect("account ID should have been constructed with a valid version")
}
pub fn from_hex(hex_str: &str) -> Result<AccountIdV0, AccountIdError> {
hex_to_bytes(hex_str)
.map_err(AccountIdError::AccountIdHexParseError)
.and_then(AccountIdV0::try_from)
}
pub fn to_hex(self) -> String {
let mut hex_string =
format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_int());
hex_string.truncate(32);
hex_string
}
pub fn to_bech32(&self, network_id: NetworkId) -> String {
let id_bytes: [u8; Self::SERIALIZED_SIZE] = (*self).into();
let mut data = [0; Self::SERIALIZED_SIZE + 1];
data[0] = AddressType::AccountId as u8;
data[1..16].copy_from_slice(&id_bytes);
bech32::encode::<Bech32m>(network_id.into_hrp(), &data)
.expect("code length of bech32 should not be exceeded")
}
pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AccountIdError> {
let checked_string = CheckedHrpstring::new::<Bech32m>(bech32_string).map_err(|source| {
AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into()))
})?;
let hrp = checked_string.hrp();
let network_id = NetworkId::from_hrp(hrp);
let mut byte_iter = checked_string.byte_iter();
if byte_iter.len() != Self::SERIALIZED_SIZE + 1 {
return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
expected: Self::SERIALIZED_SIZE + 1,
actual: byte_iter.len(),
}));
}
let address_byte = byte_iter.next().expect("there should be at least one byte");
if address_byte != AddressType::AccountId as u8 {
return Err(AccountIdError::Bech32DecodeError(Bech32Error::UnknownAddressType(
address_byte,
)));
}
Self::from_bech32_byte_iter(byte_iter).map(|account_id| (network_id, account_id))
}
pub(crate) fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result<Self, AccountIdError> {
if byte_iter.len() != Self::SERIALIZED_SIZE {
return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
expected: Self::SERIALIZED_SIZE,
actual: byte_iter.len(),
}));
}
let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE];
for (i, byte) in byte_iter.enumerate() {
id_bytes[i] = byte;
}
let account_id = Self::try_from(id_bytes)?;
Ok(account_id)
}
pub fn prefix(&self) -> AccountIdPrefixV0 {
AccountIdPrefixV0::new_unchecked(self.prefix)
}
pub const fn suffix(&self) -> Felt {
self.suffix
}
}
impl From<AccountIdV0> for [Felt; 2] {
fn from(id: AccountIdV0) -> Self {
[id.prefix, id.suffix]
}
}
impl From<AccountIdV0> for [u8; 15] {
fn from(id: AccountIdV0) -> Self {
let mut result = [0_u8; 15];
result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
result[8..].copy_from_slice(&id.suffix().as_int().to_be_bytes()[..7]);
result
}
}
impl From<AccountIdV0> for u128 {
fn from(id: AccountIdV0) -> Self {
let mut le_bytes = [0_u8; 16];
le_bytes[..8].copy_from_slice(&id.suffix().as_int().to_le_bytes());
le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
u128::from_le_bytes(le_bytes)
}
}
impl TryFrom<[Felt; 2]> for AccountIdV0 {
type Error = AccountIdError;
fn try_from(elements: [Felt; 2]) -> Result<Self, Self::Error> {
account_id_from_felts(elements)
}
}
impl TryFrom<[u8; 15]> for AccountIdV0 {
type Error = AccountIdError;
fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
bytes[..8].reverse();
bytes[8..15].reverse();
let prefix_slice = &bytes[..8];
let suffix_slice = &bytes[8..15];
let mut suffix_bytes = [0; 8];
suffix_bytes[1..8].copy_from_slice(suffix_slice);
let prefix = Felt::try_from(prefix_slice)
.map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
let suffix = Felt::try_from(suffix_bytes.as_slice())
.map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?;
Self::try_from([prefix, suffix])
}
}
impl TryFrom<u128> for AccountIdV0 {
type Error = AccountIdError;
fn try_from(int: u128) -> Result<Self, Self::Error> {
let mut bytes: [u8; 15] = [0; 15];
bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
Self::try_from(bytes)
}
}
impl Serializable for AccountIdV0 {
fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
let bytes: [u8; 15] = (*self).into();
bytes.write_into(target);
}
fn get_size_hint(&self) -> usize {
Self::SERIALIZED_SIZE
}
}
impl Deserializable for AccountIdV0 {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
<[u8; 15]>::read_from(source)?
.try_into()
.map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
}
}
fn account_id_from_felts(elements: [Felt; 2]) -> Result<AccountIdV0, AccountIdError> {
validate_prefix(elements[0])?;
validate_suffix(elements[1])?;
Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] })
}
pub(crate) fn validate_prefix(
prefix: Felt,
) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
let prefix = prefix.as_int();
let storage_mode = extract_storage_mode(prefix)?;
let version = extract_version(prefix)?;
let account_type = extract_type(prefix);
Ok((account_type, storage_mode, version))
}
const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
let suffix = suffix.as_int();
if suffix >> 63 != 0 {
return Err(AccountIdError::AccountIdSuffixMostSignificantBitMustBeZero);
}
if suffix & 0xff != 0 {
return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
}
Ok(())
}
pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
match bits as u8 {
PUBLIC => Ok(AccountStorageMode::Public),
NETWORK => Ok(AccountStorageMode::Network),
PRIVATE => Ok(AccountStorageMode::Private),
_ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
}
}
pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
AccountIdVersion::try_from(version)
}
pub(crate) const fn extract_type(prefix: u64) -> AccountType {
let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
match bits as u8 {
REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
_ => {
panic!("type mask contains only 2 bits and we've covered all 4 possible options")
},
}
}
fn shape_suffix(suffix: Felt) -> Felt {
let mut suffix = suffix.as_int();
suffix &= 0xffff_ffff_ffff_ff00;
Felt::try_from(suffix).expect("no bits were set so felt should still be valid")
}
impl PartialOrd for AccountIdV0 {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AccountIdV0 {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
u128::from(*self).cmp(&u128::from(*other))
}
}
impl fmt::Display for AccountIdV0 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
pub(crate) fn compute_digest(seed: Word, code_commitment: Word, storage_commitment: Word) -> Word {
let mut elements = Vec::with_capacity(16);
elements.extend(seed);
elements.extend(*code_commitment);
elements.extend(*storage_commitment);
elements.extend(EMPTY_WORD);
Hasher::hash_elements(&elements)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::account::AccountIdPrefix;
use crate::testing::account_id::{
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_SENDER,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
};
#[test]
fn account_id_from_felts_with_max_pop_count() {
let valid_suffix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff70u64).unwrap();
let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
assert_eq!(id1.account_type(), AccountType::NonFungibleFaucet);
assert_eq!(id1.storage_mode(), AccountStorageMode::Network);
assert_eq!(id1.version(), AccountIdVersion::Version0);
}
#[test]
fn account_id_dummy_construction() {
for input in [[0xff; 15], [0; 15]] {
for account_type in [
AccountType::FungibleFaucet,
AccountType::NonFungibleFaucet,
AccountType::RegularAccountImmutableCode,
AccountType::RegularAccountUpdatableCode,
] {
for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
let id = AccountIdV0::dummy(input, account_type, storage_mode);
assert_eq!(id.account_type(), account_type);
assert_eq!(id.storage_mode(), storage_mode);
assert_eq!(id.version(), AccountIdVersion::Version0);
let serialized_id = id.to_bytes();
AccountIdV0::read_from_bytes(&serialized_id).unwrap();
assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
}
}
}
}
#[test]
fn account_id_prefix_serialization_compatibility() {
let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
let id_bytes = account_id.to_bytes();
assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
}
#[test]
fn test_account_id_conversion_roundtrip() {
for (idx, account_id) in [
ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
ACCOUNT_ID_PRIVATE_SENDER,
]
.into_iter()
.enumerate()
{
let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
assert_eq!(account_id, u128::from(id), "failed in {idx}");
}
}
#[test]
fn test_account_id_tag_identifiers() {
let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
.expect("valid account ID");
assert!(account_id.is_regular_account());
assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
assert!(account_id.is_public());
let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
.expect("valid account ID");
assert!(account_id.is_regular_account());
assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
assert!(!account_id.is_public());
let account_id =
AccountIdV0::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
assert!(account_id.is_faucet());
assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
assert!(account_id.is_public());
let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
.expect("valid account ID");
assert!(account_id.is_faucet());
assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
assert!(!account_id.is_public());
}
}