From e3c2e110566e9ff9773f2ec0a6b7c8ce3ea7e144 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Fri, 29 Nov 2024 17:01:10 +0100 Subject: [PATCH] fix: account id byte & hex encoding --- objects/src/accounts/account_id.rs | 119 ++-- objects/src/accounts/account_id_old.rs | 772 +++++++++++++++++++++++++ objects/src/accounts/builder/mod.rs | 7 +- objects/src/accounts/seed_old.rs | 268 +++++++++ objects/src/assets/fungible.rs | 25 +- objects/src/assets/nonfungible.rs | 12 +- 6 files changed, 1148 insertions(+), 55 deletions(-) create mode 100644 objects/src/accounts/account_id_old.rs create mode 100644 objects/src/accounts/seed_old.rs diff --git a/objects/src/accounts/account_id.rs b/objects/src/accounts/account_id.rs index 6ea7fe5e2..ecfa84f96 100644 --- a/objects/src/accounts/account_id.rs +++ b/objects/src/accounts/account_id.rs @@ -164,6 +164,9 @@ impl AccountId { /// Specifies a minimum number of ones for a valid account ID. pub const MIN_ACCOUNT_ONES: u32 = 5; + /// The serialized size of an [`AccountId`] in bytes. + pub const SERIALIZED_SIZE: usize = 15; + pub fn new( seed: Word, block_epoch: u16, @@ -292,9 +295,13 @@ impl AccountId { pub fn from_hex(hex_str: &str) -> Result { hex_to_bytes(hex_str).map_err(AccountError::AccountIdHexParseError).and_then( |mut bytes: [u8; 15]| { - // TryFrom<[u8; 15]> expects little-endian order, so we need to convert the - // bytes representation from big endian to little endian by reversing. - bytes.reverse(); + // TryFrom<[u8; 15]> expects [first_felt, second_felt] in little-endian order, so we + // need to convert the bytes representation from big endian to little endian by + // reversing each felt. The first felt has 8 and the second felt has + // 7 bytes. + bytes[0..8].reverse(); + bytes[8..15].reverse(); + AccountId::try_from(bytes) }, ) @@ -303,7 +310,13 @@ impl AccountId { /// Returns a big-endian, hex-encoded string of length 32, including the `0x` prefix, so it /// encodes 15 bytes. pub fn to_hex(&self) -> String { - format!("0x{:016x}{:014x}", self.first_felt().as_int(), self.second_felt().as_int()) + // We need to pad the second felt with 16 zeroes so it produces a correctly padded 8 byte + // big-endian hex string. Only then can we cut off the last zero byte by truncating. We + // cannot use `:014x` padding. + let mut hex_string = + format!("0x{:016x}{:016x}", self.first_felt().as_int(), self.second_felt().as_int()); + hex_string.truncate(32); + hex_string } pub fn prefix(&self) -> AccountIdPrefix { @@ -334,7 +347,9 @@ impl From for [u8; 15] { fn from(id: AccountId) -> Self { let mut result = [0_u8; 15]; result[..8].copy_from_slice(&id.first_felt().as_int().to_le_bytes()); - result[8..].copy_from_slice(&id.second_felt().as_int().to_le_bytes()[..7]); + // The last byte of the second felt is always zero, and in little endian this is the first + // byte, so we skip it here. + result[8..].copy_from_slice(&id.second_felt().as_int().to_le_bytes()[1..8]); result } } @@ -424,8 +439,7 @@ impl Serializable for AccountId { } fn get_size_hint(&self) -> usize { - // TODO: Turn into constant? - 15 + Self::SERIALIZED_SIZE } } @@ -613,81 +627,108 @@ pub mod testing { pub const ACCOUNT_ID_SENDER: u128 = account_id( AccountType::RegularAccountImmutableCode, AccountStorageMode::Private, - 0b0001_1111, + 0xaabb_ccdd, ); pub const ACCOUNT_ID_OFF_CHAIN_SENDER: u128 = account_id( AccountType::RegularAccountImmutableCode, AccountStorageMode::Private, - 0b0010_1111, + 0xbbcc_ddee, ); pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN: u128 = account_id( AccountType::RegularAccountUpdatableCode, AccountStorageMode::Private, - 0b0011_1111, + 0xccdd_eeff, ); // REGULAR ACCOUNTS - ON-CHAIN pub const ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN: u128 = account_id( AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, - 0b0001_1111, + 0xaabb_ccdd, ); pub const ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2: u128 = account_id( AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, - 0b0010_1111, + 0xbbcc_ddee, ); pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN: u128 = account_id( AccountType::RegularAccountUpdatableCode, AccountStorageMode::Public, - 0b0011_1111, + 0xccdd_eeff, ); pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2: u128 = account_id( AccountType::RegularAccountUpdatableCode, AccountStorageMode::Public, - 0b0100_1111, + 0xeeff_ccdd, ); // FUNGIBLE TOKENS - OFF-CHAIN pub const ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN: u128 = - account_id(AccountType::FungibleFaucet, AccountStorageMode::Private, 0b0001_1111); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Private, 0xaabb_ccdd); // FUNGIBLE TOKENS - ON-CHAIN pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN: u128 = - account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0b0001_1111); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xaabb_ccdd); pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1: u128 = - account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0b0010_1111); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xbbcc_ddee); pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2: u128 = - account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0b0011_1111); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xccdd_eeff); pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3: u128 = - account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0b0100_1111); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xeeff_ccdd); // NON-FUNGIBLE TOKENS - OFF-CHAIN pub const ACCOUNT_ID_INSUFFICIENT_ONES: u128 = account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0b0000_0000); // invalid pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN: u128 = - account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0b0001_1111); + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0xaabb_ccdd); // NON-FUNGIBLE TOKENS - ON-CHAIN pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN: u128 = - account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0b0010_1111); + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0xbbcc_ddee); pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1: u128 = - account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0b0011_1111); + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0xccdd_eeff); // UTILITIES // -------------------------------------------------------------------------------------------- + /// Produces a valid account ID with the given account type and storage mode. + /// + /// - Version ist set to 0. + /// - Epoch is set to 0. + /// - The 2nd most significant bit is set to 1, so it is easier to test the note_tag, for + /// example. + /// + /// Finally, distributes the given `random` value over the ID to produce reasonably realistic + /// values. This is easiest explained with an example. Suppose `random` is `0xaabb_ccdd`, + /// then the layout of the generated ID will be: + /// + /// ```text + /// 1st felt: [0b0100_0000 | 0xaa | 4 zero bytes | 0xbb | metadata byte] + /// 2nd felt: [2 zero bytes (epoch) | 0xcc | 3 zero bytes | 0xdd | zero byte] + /// ``` pub const fn account_id( account_type: AccountType, storage_mode: AccountStorageMode, random: u32, ) -> u128 { - let mut id = 0; + let mut first_felt: u64 = 0; - id |= account_type as u128; - id |= (storage_mode as u128) << ACCOUNT_STORAGE_MASK_SHIFT; - // Shift the random part of the ID so we don't overwrite the metadata. - id |= (random as u128) << 8; + first_felt |= account_type as u64; + first_felt |= (storage_mode as u64) << ACCOUNT_STORAGE_MASK_SHIFT; - // Shifts in zeroes from the right so the second felt will be entirely 0. - id << 64 + // Produce more realistic IDs by distributing the random value. + let random_1st_felt_upper = random & 0xff00_0000; + let random_1st_felt_lower = random & 0x00ff_0000; + let random_2nd_felt_upper = random & 0x0000_ff00; + let random_2nd_felt_lower = random & 0x0000_00ff; + + // Shift the random part of the ID to start at the most significant end. + first_felt |= (random_1st_felt_upper as u64) << 24; + first_felt |= (random_1st_felt_lower as u64) >> 8; + + let mut id = (first_felt as u128) << 64; + + id |= (random_2nd_felt_upper as u128) << 32; + id |= (random_2nd_felt_lower as u128) << 8; + + id } } @@ -767,8 +808,11 @@ mod tests { assert_eq!(id.account_type(), account_type); assert_eq!(id.storage_mode(), storage_mode); assert_eq!(id.block_epoch(), 0); + // Do a serialization roundtrip to ensure validity. - AccountId::read_from_bytes(&id.to_bytes()).unwrap(); + let serialized_id = id.to_bytes(); + AccountId::read_from_bytes(&serialized_id).unwrap(); + assert_eq!(serialized_id.len(), AccountId::SERIALIZED_SIZE); } } } @@ -788,18 +832,21 @@ mod tests { #[test] fn test_account_id_conversion_roundtrip() { - for account_id in [ + for (idx, account_id) in [ ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_OFF_CHAIN_SENDER, - ] { + ] + .into_iter() + .enumerate() + { let id = AccountId::try_from(account_id).expect("account ID should be valid"); - assert_eq!(id, AccountId::from_hex(&id.to_hex()).unwrap()); - assert_eq!(id, AccountId::try_from(<[u8; 15]>::from(id)).unwrap()); - assert_eq!(id, AccountId::try_from(u128::from(id)).unwrap()); - assert_eq!(account_id, u128::from(id)); + assert_eq!(id, AccountId::from_hex(&id.to_hex()).unwrap(), "failed in {idx}"); + assert_eq!(id, AccountId::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}"); + assert_eq!(id, AccountId::try_from(u128::from(id)).unwrap(), "failed in {idx}"); + assert_eq!(account_id, u128::from(id), "failed in {idx}"); } } diff --git a/objects/src/accounts/account_id_old.rs b/objects/src/accounts/account_id_old.rs new file mode 100644 index 000000000..badee6795 --- /dev/null +++ b/objects/src/accounts/account_id_old.rs @@ -0,0 +1,772 @@ +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use core::{fmt, str::FromStr}; + +use super::{ + get_account_seed, AccountError, ByteReader, Deserializable, DeserializationError, Digest, Felt, + Hasher, Serializable, Word, ZERO, +}; +use crate::{crypto::merkle::LeafIndex, utils::hex_to_bytes, ACCOUNT_TREE_DEPTH}; + +// CONSTANTS +// ================================================================================================ + +// The higher two bits of the most significant nibble determines the account storage mode +pub const ACCOUNT_STORAGE_MASK_SHIFT: u64 = 62; +pub const ACCOUNT_STORAGE_MASK: u64 = 0b11 << ACCOUNT_STORAGE_MASK_SHIFT; + +// The lower two bits of the most significant nibble determines the account type +pub const ACCOUNT_TYPE_MASK_SHIFT: u64 = 60; +pub const ACCOUNT_TYPE_MASK: u64 = 0b11 << ACCOUNT_TYPE_MASK_SHIFT; +pub const ACCOUNT_ISFAUCET_MASK: u64 = 0b10 << ACCOUNT_TYPE_MASK_SHIFT; + +// ACCOUNT TYPES +// ================================================================================================ + +pub const FUNGIBLE_FAUCET: u64 = 0b10; +pub const NON_FUNGIBLE_FAUCET: u64 = 0b11; +pub const REGULAR_ACCOUNT_IMMUTABLE_CODE: u64 = 0b00; +pub const REGULAR_ACCOUNT_UPDATABLE_CODE: u64 = 0b01; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u64)] +pub enum AccountType { + FungibleFaucet = FUNGIBLE_FAUCET, + NonFungibleFaucet = NON_FUNGIBLE_FAUCET, + RegularAccountImmutableCode = REGULAR_ACCOUNT_IMMUTABLE_CODE, + RegularAccountUpdatableCode = REGULAR_ACCOUNT_UPDATABLE_CODE, +} + +impl AccountType { + /// Returns `true` if the account is a faucet. + pub fn is_faucet(&self) -> bool { + matches!(self, Self::FungibleFaucet | Self::NonFungibleFaucet) + } + + /// Returns `true` if the account is a regular account. + pub fn is_regular_account(&self) -> bool { + matches!(self, Self::RegularAccountImmutableCode | Self::RegularAccountUpdatableCode) + } +} + +/// Extracts the [AccountType] encoded in an u64. +/// +/// The account id is encoded in the bits `[62,60]` of the u64, see [ACCOUNT_TYPE_MASK]. +/// +/// # Note +/// +/// This function does not validate the u64, it is assumed the value is valid [Felt]. +pub const fn account_type_from_u64(value: u64) -> AccountType { + debug_assert!( + ACCOUNT_TYPE_MASK.count_ones() == 2, + "This method assumes there are only 2bits in the mask" + ); + + let bits = (value & ACCOUNT_TYPE_MASK) >> ACCOUNT_TYPE_MASK_SHIFT; + match bits { + REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode, + REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode, + FUNGIBLE_FAUCET => AccountType::FungibleFaucet, + NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet, + _ => { + // "account_type mask contains only 2bits, there are 4 options total" + unreachable!() + }, + } +} + +/// Returns the [AccountType] given an integer representation of `account_id`. +impl From for AccountType { + fn from(value: u64) -> Self { + account_type_from_u64(value) + } +} + +// ACCOUNT STORAGE TYPES +// ================================================================================================ + +pub const PUBLIC: u64 = 0b00; +pub const PRIVATE: u64 = 0b10; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u64)] +pub enum AccountStorageMode { + Public = PUBLIC, + Private = PRIVATE, +} + +impl fmt::Display for AccountStorageMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AccountStorageMode::Public => write!(f, "public"), + AccountStorageMode::Private => write!(f, "private"), + } + } +} + +impl TryFrom<&str> for AccountStorageMode { + type Error = AccountError; + + fn try_from(value: &str) -> Result { + match value.to_lowercase().as_str() { + "public" => Ok(AccountStorageMode::Public), + "private" => Ok(AccountStorageMode::Private), + _ => Err(AccountError::InvalidAccountStorageMode(value.into())), + } + } +} + +impl TryFrom for AccountStorageMode { + type Error = AccountError; + + fn try_from(value: String) -> Result { + AccountStorageMode::from_str(&value) + } +} + +impl FromStr for AccountStorageMode { + type Err = AccountError; + + fn from_str(input: &str) -> Result { + AccountStorageMode::try_from(input) + } +} + +// ACCOUNT ID +// ================================================================================================ + +/// Unique identifier of an account. +/// +/// Account ID consists of 1 field element (~64 bits). The most significant bits in the id are used +/// to encode the account' storage and type. +/// +/// The top two bits are used to encode the storage type. The values [PRIVATE] and [PUBLIC] +/// encode the account's storage type. The next two bits encode the account type. The values +/// [FUNGIBLE_FAUCET], [NON_FUNGIBLE_FAUCET], [REGULAR_ACCOUNT_IMMUTABLE_CODE], and +/// [REGULAR_ACCOUNT_UPDATABLE_CODE] encode the account's type. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct AccountId(Felt); + +impl AccountId { + /// Specifies a minimum number of trailing zeros required in the last element of the seed + /// digest. + /// + /// Note: The account id includes 4 bits of metadata, these bits determine the account type + /// (normal account, fungible token, non-fungible token), the storage type (on/off chain), and + /// for the normal accounts if the code is updatable or not. These metadata bits are also + /// checked by the PoW and add to the total work defined below. + #[cfg(not(any(feature = "testing", test)))] + pub const REGULAR_ACCOUNT_SEED_DIGEST_MIN_TRAILING_ZEROS: u32 = 23; + #[cfg(not(any(feature = "testing", test)))] + pub const FAUCET_SEED_DIGEST_MIN_TRAILING_ZEROS: u32 = 31; + #[cfg(any(feature = "testing", test))] + pub const REGULAR_ACCOUNT_SEED_DIGEST_MIN_TRAILING_ZEROS: u32 = 5; + #[cfg(any(feature = "testing", test))] + pub const FAUCET_SEED_DIGEST_MIN_TRAILING_ZEROS: u32 = 6; + + /// Specifies a minimum number of ones for a valid account ID. + pub const MIN_ACCOUNT_ONES: u32 = 5; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Returns a new account ID derived from the specified seed, code commitment and storage + /// commitment. + /// + /// The account ID is computed by hashing the seed, code commitment and storage commitment and + /// using 1 element of the resulting digest to form the ID. Specifically we take element 0. + /// We also require that the last element of the seed digest has at least `23` trailing + /// zeros if it is a regular account, or `31` trailing zeros if it is a faucet account. + /// + /// The seed digest is computed using a sequential hash over + /// hash(SEED, CODE_COMMITMENT, STORAGE_COMMITMENT, ZERO). This takes two permutations. + /// + /// # Errors + /// Returns an error if the resulting account ID does not comply with account ID rules: + /// - the metadata embedded in the ID (i.e., the first 4 bits) is valid. + /// - the ID has at least `5` ones. + /// - the last element of the seed digest has at least `23` trailing zeros for regular accounts. + /// - the last element of the seed digest has at least `31` trailing zeros for faucet accounts. + pub fn new( + seed: Word, + code_commitment: Digest, + storage_commitment: Digest, + ) -> Result { + let seed_digest = compute_digest(seed, code_commitment, storage_commitment); + + Self::validate_seed_digest(&seed_digest)?; + seed_digest[0].try_into() + } + + /// Creates a new [AccountId] without checking its validity. + /// + /// This function requires that the provided value is a valid [Felt] representation of an + /// [AccountId]. + pub fn new_unchecked(value: Felt) -> Self { + Self(value) + } + + /// Creates a new dummy [AccountId] for testing purposes. + #[cfg(any(feature = "testing", test))] + pub fn new_dummy(init_seed: [u8; 32], account_type: AccountType) -> Self { + let code_commitment = Digest::default(); + let storage_commitment = Digest::default(); + + let seed = get_account_seed( + init_seed, + account_type, + AccountStorageMode::Public, + code_commitment, + storage_commitment, + ) + .unwrap(); + + Self::new(seed, code_commitment, storage_commitment).unwrap() + } + + /// Constructs an [`AccountId`] for testing purposes with the given account type and storage + /// mode. + /// + /// This function does the following: + /// - The bit representation of the account type and storage mode is prepended to the most + /// significant byte of `bytes`. + /// - The 5th most significant bit is cleared. + /// - The lowest 5 bits are set to ensure the id has at least [`Self::MIN_ACCOUNT_ONES`]. + /// - The bytes are then converted to a `u64` in big-endian format. Due to clearing the 5th most + /// significant bit, the resulting `u64` will be a valid [`Felt`]. + #[cfg(any(feature = "testing", test))] + pub fn new_with_type_and_mode( + mut bytes: [u8; 8], + account_type: AccountType, + storage_mode: AccountStorageMode, + ) -> AccountId { + let id_high_nibble = (storage_mode as u8) << 6 | (account_type as u8) << 4; + + // Clear the highest five bits of the most significant byte. + // The high nibble must be cleared so we can set it to the storage mode and account type + // we've constructed. + // The 5th most significant bit is cleared to ensure the resulting id is a valid Felt even + // when all other bits are set. + bytes[0] &= 0x07; + // Set high nibble of the most significant byte. + bytes[0] |= id_high_nibble; + // Set the lowest 5 bits to ensure we have at least MIN_ACCOUNT_ONES. + bytes[7] |= 0x1f; + + let account_id = account_id_from_felt( + Felt::try_from(u64::from_be_bytes(bytes)) + .expect("must be a valid felt after clearing the 5th highest bit"), + ) + .expect("account id shoult satisfy criteria of a valid ID"); + + debug_assert_eq!(account_id.account_type(), account_type); + debug_assert_eq!(account_id.storage_mode(), storage_mode); + + account_id + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the type of this account ID. + pub const fn account_type(&self) -> AccountType { + account_type_from_u64(self.0.as_int()) + } + + /// Returns true if an account with this ID is a faucet (can issue assets). + pub fn is_faucet(&self) -> bool { + matches!( + self.account_type(), + AccountType::FungibleFaucet | AccountType::NonFungibleFaucet + ) + } + + /// Returns true if an account with this ID is a regular account. + pub fn is_regular_account(&self) -> bool { + is_regular_account(self.0.as_int()) + } + + /// Returns the storage mode of this account (e.g., public or private). + pub fn storage_mode(&self) -> AccountStorageMode { + let bits = (self.0.as_int() & ACCOUNT_STORAGE_MASK) >> ACCOUNT_STORAGE_MASK_SHIFT; + match bits { + PUBLIC => AccountStorageMode::Public, + PRIVATE => AccountStorageMode::Private, + _ => panic!("Account with invalid storage bits created"), + } + } + + /// Returns true if an account with this ID is a public account. + pub fn is_public(&self) -> bool { + self.storage_mode() == AccountStorageMode::Public + } + + /// Finds and returns a seed suitable for creating an account ID for the specified account type + /// using the provided initial seed as a starting point. + pub fn get_account_seed( + init_seed: [u8; 32], + account_type: AccountType, + storage_mode: AccountStorageMode, + code_commitment: Digest, + storage_commitment: Digest, + ) -> Result { + get_account_seed(init_seed, account_type, storage_mode, code_commitment, storage_commitment) + } + + /// Creates an Account Id from a hex string. Assumes the string starts with "0x" and + /// that the hexadecimal characters are big-endian encoded. + pub fn from_hex(hex_value: &str) -> Result { + hex_to_bytes(hex_value).map_err(AccountError::AccountIdHexParseError).and_then( + |mut bytes: [u8; 8]| { + // `bytes` ends up being parsed as felt, and the input to that is assumed to be + // little-endian so we need to reverse the order + bytes.reverse(); + bytes.try_into() + }, + ) + } + + /// Returns a big-endian, hex-encoded string. + pub fn to_hex(&self) -> String { + format!("0x{:016x}", self.0.as_int()) + } + + // UTILITY METHODS + // -------------------------------------------------------------------------------------------- + + /// Returns an error if: + /// - There are fewer then: + /// - 24 trailing ZEROs in the last element of the seed digest for regular accounts. + /// - 32 trailing ZEROs in the last element of the seed digest for faucet accounts. + pub(super) fn validate_seed_digest(digest: &Digest) -> Result<(), AccountError> { + // check the id satisfies the proof-of-work requirement. + let required_zeros = if is_regular_account(digest[0].as_int()) { + Self::REGULAR_ACCOUNT_SEED_DIGEST_MIN_TRAILING_ZEROS + } else { + Self::FAUCET_SEED_DIGEST_MIN_TRAILING_ZEROS + }; + + let trailing_zeros = digest_pow(*digest); + if required_zeros > trailing_zeros { + return Err(AccountError::SeedDigestTooFewTrailingZeros { + expected: required_zeros, + actual: trailing_zeros, + }); + } + + Ok(()) + } +} + +impl PartialOrd for AccountId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AccountId { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.0.as_int().cmp(&other.0.as_int()) + } +} + +impl fmt::Display for AccountId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{:016x}", self.0.as_int()) + } +} + +// CONVERSIONS FROM ACCOUNT ID +// ================================================================================================ + +impl From for Felt { + fn from(id: AccountId) -> Self { + id.0 + } +} + +impl From for [u8; 8] { + fn from(id: AccountId) -> Self { + let mut result = [0_u8; 8]; + result[..8].copy_from_slice(&id.0.as_int().to_le_bytes()); + result + } +} + +impl From for u64 { + fn from(id: AccountId) -> Self { + id.0.as_int() + } +} + +/// Account IDs are used as indexes in the account database, which is a tree of depth 64. +impl From for LeafIndex { + fn from(id: AccountId) -> Self { + LeafIndex::new_max_depth(id.0.as_int()) + } +} + +// CONVERSIONS TO ACCOUNT ID +// ================================================================================================ + +/// Returns an [AccountId] instantiated with the provided field element. +/// +/// # Errors +/// Returns an error if: +/// - If there are fewer than [AccountId::MIN_ACCOUNT_ONES] in the provided value. +/// - If the provided value contains invalid account ID metadata (i.e., the first 4 bits). +pub fn account_id_from_felt(value: Felt) -> Result { + let int_value = value.as_int(); + + let count = int_value.count_ones(); + if count < AccountId::MIN_ACCOUNT_ONES { + return Err(AccountError::AccountIdTooFewOnes(count)); + } + + let bits = (int_value & ACCOUNT_STORAGE_MASK) >> ACCOUNT_STORAGE_MASK_SHIFT; + match bits { + PUBLIC | PRIVATE => (), + _ => return Err(AccountError::InvalidAccountStorageMode(format!("0b{bits:b}"))), + }; + + Ok(AccountId(value)) +} + +impl TryFrom for AccountId { + type Error = AccountError; + + /// Returns an [AccountId] instantiated with the provided field element. + /// + /// # Errors + /// Returns an error if: + /// - If there are fewer than [AccountId::MIN_ACCOUNT_ONES] in the provided value. + /// - If the provided value contains invalid account ID metadata (i.e., the first 4 bits). + fn try_from(value: Felt) -> Result { + account_id_from_felt(value) + } +} + +impl TryFrom<[u8; 8]> for AccountId { + type Error = AccountError; + + // Expects little-endian byte order + fn try_from(value: [u8; 8]) -> Result { + let element = parse_felt(&value[..8])?; + Self::try_from(element) + } +} + +impl TryFrom for AccountId { + type Error = AccountError; + + fn try_from(value: u64) -> Result { + let element = parse_felt(&value.to_le_bytes())?; + Self::try_from(element) + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for AccountId { + fn write_into(&self, target: &mut W) { + self.0.write_into(target); + } + + fn get_size_hint(&self) -> usize { + self.0.get_size_hint() + } +} + +impl Deserializable for AccountId { + fn read_from(source: &mut R) -> Result { + Felt::read_from(source)? + .try_into() + .map_err(|err: AccountError| DeserializationError::InvalidValue(err.to_string())) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ +fn parse_felt(bytes: &[u8]) -> Result { + Felt::try_from(bytes).map_err(AccountError::AccountIdInvalidFieldElement) +} + +/// Returns the digest of two hashing permutations over the seed, code commitment, storage +/// commitment and padding. +pub(super) fn compute_digest( + seed: Word, + code_commitment: Digest, + storage_commitment: Digest, +) -> Digest { + let mut elements = Vec::with_capacity(16); + elements.extend(seed); + elements.extend(*code_commitment); + elements.extend(*storage_commitment); + elements.resize(16, ZERO); + Hasher::hash_elements(&elements) +} + +/// Given a [Digest] returns its proof-of-work. +pub(super) fn digest_pow(digest: Digest) -> u32 { + digest.as_elements()[3].as_int().trailing_zeros() +} + +/// Returns true if an account with this ID is a regular account. +fn is_regular_account(account_id: u64) -> bool { + let account_type = account_id.into(); + matches!( + account_type, + AccountType::RegularAccountUpdatableCode | AccountType::RegularAccountImmutableCode + ) +} + +// TESTING +// ================================================================================================ + +#[cfg(any(feature = "testing", test))] +pub mod testing { + use super::{ + AccountStorageMode, AccountType, ACCOUNT_STORAGE_MASK_SHIFT, ACCOUNT_TYPE_MASK_SHIFT, + }; + + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + // REGULAR ACCOUNTS - OFF-CHAIN + pub const ACCOUNT_ID_SENDER: u64 = account_id( + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + 0b0001_1111, + ); + pub const ACCOUNT_ID_OFF_CHAIN_SENDER: u64 = account_id( + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + 0b0010_1111, + ); + pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN: u64 = account_id( + AccountType::RegularAccountUpdatableCode, + AccountStorageMode::Private, + 0b0011_1111, + ); + // REGULAR ACCOUNTS - ON-CHAIN + pub const ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN: u64 = account_id( + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Public, + 0b0001_1111, + ); + pub const ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2: u64 = account_id( + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Public, + 0b0010_1111, + ); + pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN: u64 = account_id( + AccountType::RegularAccountUpdatableCode, + AccountStorageMode::Public, + 0b0011_1111, + ); + pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2: u64 = account_id( + AccountType::RegularAccountUpdatableCode, + AccountStorageMode::Public, + 0b0100_1111, + ); + + // FUNGIBLE TOKENS - OFF-CHAIN + pub const ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN: u64 = + account_id(AccountType::FungibleFaucet, AccountStorageMode::Private, 0b0001_1111); + // FUNGIBLE TOKENS - ON-CHAIN + pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN: u64 = + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0b0001_1111); + pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1: u64 = + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0b0010_1111); + pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2: u64 = + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0b0011_1111); + pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3: u64 = + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0b0100_1111); + + // NON-FUNGIBLE TOKENS - OFF-CHAIN + pub const ACCOUNT_ID_INSUFFICIENT_ONES: u64 = + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0b0000_0000); // invalid + pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN: u64 = + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0b0001_1111); + // NON-FUNGIBLE TOKENS - ON-CHAIN + pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN: u64 = + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0b0010_1111); + pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1: u64 = + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0b0011_1111); + + // UTILITIES + // -------------------------------------------------------------------------------------------- + + pub const fn account_id( + account_type: AccountType, + storage_mode: AccountStorageMode, + rest: u64, + ) -> u64 { + let mut id = 0; + + id ^= (storage_mode as u64) << ACCOUNT_STORAGE_MASK_SHIFT; + id ^= (account_type as u64) << ACCOUNT_TYPE_MASK_SHIFT; + id ^= rest; + + id + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_crypto::utils::{Deserializable, Serializable}; + + use super::{ + testing::*, AccountId, AccountStorageMode, AccountType, ACCOUNT_ISFAUCET_MASK, + ACCOUNT_TYPE_MASK_SHIFT, FUNGIBLE_FAUCET, NON_FUNGIBLE_FAUCET, + REGULAR_ACCOUNT_IMMUTABLE_CODE, REGULAR_ACCOUNT_UPDATABLE_CODE, + }; + + #[test] + fn test_account_id() { + use crate::accounts::AccountId; + + for account_type in [ + AccountType::RegularAccountImmutableCode, + AccountType::RegularAccountUpdatableCode, + AccountType::NonFungibleFaucet, + AccountType::FungibleFaucet, + ] { + for storage_mode in [AccountStorageMode::Public, AccountStorageMode::Private] { + let acc = AccountId::try_from(account_id(account_type, storage_mode, 0b1111_1111)) + .unwrap(); + assert_eq!(acc.account_type(), account_type); + assert_eq!(acc.storage_mode(), storage_mode); + } + } + } + + #[test] + fn test_account_id_from_hex_and_back() { + for account_id in [ + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, + ] { + let acc = AccountId::try_from(account_id).expect("Valid account ID"); + assert_eq!(acc, AccountId::from_hex(&acc.to_hex()).unwrap()); + } + } + + #[test] + fn test_account_id_serde() { + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN) + .expect("Valid account ID"); + assert_eq!(account_id, AccountId::read_from_bytes(&account_id.to_bytes()).unwrap()); + + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN) + .expect("Valid account ID"); + assert_eq!(account_id, AccountId::read_from_bytes(&account_id.to_bytes()).unwrap()); + + let account_id = + AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).expect("Valid account ID"); + assert_eq!(account_id, AccountId::read_from_bytes(&account_id.to_bytes()).unwrap()); + + let account_id = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN) + .expect("Valid account ID"); + assert_eq!(account_id, AccountId::read_from_bytes(&account_id.to_bytes()).unwrap()); + } + + #[test] + fn test_account_id_account_type() { + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN) + .expect("Valid account ID"); + + let account_type: AccountType = ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN.into(); + assert_eq!(account_type, account_id.account_type()); + + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN) + .expect("Valid account ID"); + let account_type: AccountType = ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN.into(); + assert_eq!(account_type, account_id.account_type()); + + let account_id = + AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).expect("Valid account ID"); + let account_type: AccountType = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.into(); + assert_eq!(account_type, account_id.account_type()); + + let account_id = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN) + .expect("Valid account ID"); + let account_type: AccountType = ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN.into(); + assert_eq!(account_type, account_id.account_type()); + } + + #[test] + fn test_account_id_tag_identifiers() { + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN) + .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 = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN) + .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 = + AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).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 = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN) + .expect("Valid account ID"); + assert!(account_id.is_faucet()); + assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet); + assert!(!account_id.is_public()); + } + + /// The following test ensure there is a bit available to identify an account as a faucet or + /// normal. + #[test] + fn test_account_id_faucet_bit() { + // faucets have a bit set + assert_ne!((FUNGIBLE_FAUCET << ACCOUNT_TYPE_MASK_SHIFT) & ACCOUNT_ISFAUCET_MASK, 0); + assert_ne!((NON_FUNGIBLE_FAUCET << ACCOUNT_TYPE_MASK_SHIFT) & ACCOUNT_ISFAUCET_MASK, 0); + + // normal accounts do not have the faucet bit set + assert_eq!( + (REGULAR_ACCOUNT_IMMUTABLE_CODE << ACCOUNT_TYPE_MASK_SHIFT) & ACCOUNT_ISFAUCET_MASK, + 0 + ); + assert_eq!( + (REGULAR_ACCOUNT_UPDATABLE_CODE << ACCOUNT_TYPE_MASK_SHIFT) & ACCOUNT_ISFAUCET_MASK, + 0 + ); + } + + #[test] + fn account_id_construction() { + // Use the highest possible input to check if the constructed id is a valid Felt in that + // scenario. + // Use the lowest possible input to check whether the constructor satisfies + // MIN_ACCOUNT_ONES. + for input in [[0xff; 8], [0; 8]] { + for account_type in [ + AccountType::FungibleFaucet, + AccountType::NonFungibleFaucet, + AccountType::RegularAccountImmutableCode, + AccountType::RegularAccountUpdatableCode, + ] { + for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] { + let id = AccountId::new_with_type_and_mode(input, account_type, storage_mode); + // Do a serialization roundtrip to ensure validity. + AccountId::read_from_bytes(&id.to_bytes()).unwrap(); + } + } + } + } +} diff --git a/objects/src/accounts/builder/mod.rs b/objects/src/accounts/builder/mod.rs index 01083e9aa..029595d2a 100644 --- a/objects/src/accounts/builder/mod.rs +++ b/objects/src/accounts/builder/mod.rs @@ -283,6 +283,7 @@ mod tests { use std::sync::LazyLock; use assembly::{Assembler, Library}; + use assert_matches::assert_matches; use vm_core::FieldElement; use super::*; @@ -426,13 +427,13 @@ mod tests { let build_error = Account::builder() .init_seed([0xff; 32]) + .block_hash([10; 32].try_into().unwrap()) + .block_epoch(0) .with_component(CustomComponent1 { slot0: storage_slot0 }) .with_assets(AssetVault::mock().assets()) .build() .unwrap_err(); - assert!( - matches!(build_error, AccountError::BuildError(msg, _) if msg == "account asset vault must be empty on new accounts") - ) + assert_matches!(build_error, AccountError::BuildError(msg, _) if msg == "account asset vault must be empty on new accounts") } } diff --git a/objects/src/accounts/seed_old.rs b/objects/src/accounts/seed_old.rs new file mode 100644 index 000000000..10c1bf467 --- /dev/null +++ b/objects/src/accounts/seed_old.rs @@ -0,0 +1,268 @@ +use alloc::vec::Vec; +#[cfg(feature = "concurrent")] +use std::{ + sync::{ + mpsc::{self, Sender}, + Arc, RwLock, + }, + thread::{self, spawn}, +}; + +use crate::accounts::account_id::AccountId; +use super::{ + account_id::compute_digest, AccountError, AccountStorageMode, AccountType, Digest, + Felt, Word, +}; + +// SEED GENERATORS +// -------------------------------------------------------------------------------------------- + +/// Finds and returns a seed suitable for creating an account ID for the specified account type +/// using the provided initial seed as a starting point. Using multi-threading. +#[cfg(feature = "concurrent")] +pub fn get_account_seed( + init_seed: [u8; 32], + account_type: AccountType, + storage_mode: AccountStorageMode, + code_commitment: Digest, + storage_commitment: Digest, +) -> Result { + let thread_count = thread::available_parallelism().map_or(1, |v| v.get()); + + let (send, recv) = mpsc::channel(); + let stop = Arc::new(RwLock::new(false)); + + for count in 0..thread_count { + let send = send.clone(); + let stop = Arc::clone(&stop); + let mut init_seed = init_seed; + init_seed[0] = init_seed[0].wrapping_add(count as u8); + spawn(move || { + get_account_seed_inner( + send, + stop, + init_seed, + account_type, + storage_mode, + code_commitment, + storage_commitment, + ) + }); + } + + #[allow(unused_variables)] + let (digest, seed) = recv.recv().unwrap(); + + // Safety: this is the only writer for this lock, it should never be poisoned + *stop.write().unwrap() = true; + + #[cfg(feature = "log")] + ::log::info!( + "Using account seed [pow={}, digest={}, seed={}]", + super::account_id::digest_pow(digest), + log::digest_hex(digest), + log::word_hex(seed), + ); + + Ok(seed) +} + +#[cfg(feature = "concurrent")] +pub fn get_account_seed_inner( + send: Sender<(Digest, Word)>, + stop: Arc>, + init_seed: [u8; 32], + account_type: AccountType, + storage_mode: AccountStorageMode, + code_commitment: Digest, + storage_commitment: Digest, +) { + let init_seed: Vec<[u8; 8]> = + init_seed.chunks(8).map(|chunk| chunk.try_into().unwrap()).collect(); + let mut current_seed: Word = [ + Felt::new(u64::from_le_bytes(init_seed[0])), + Felt::new(u64::from_le_bytes(init_seed[1])), + Felt::new(u64::from_le_bytes(init_seed[2])), + Felt::new(u64::from_le_bytes(init_seed[3])), + ]; + let mut current_digest = compute_digest(current_seed, code_commitment, storage_commitment); + + #[cfg(feature = "log")] + let mut log = log::Log::start(current_digest, current_seed, account_type, storage_mode); + + // loop until we have a seed that satisfies the specified account type. + let mut count = 0; + loop { + #[cfg(feature = "log")] + log.iteration(current_digest, current_seed); + + // regularly check if another thread found a digest + count += 1; + if count % 500_000 == 0 && *stop.read().unwrap() { + return; + } + + // check if the seed satisfies the specified account type + if AccountId::validate_seed_digest(¤t_digest).is_ok() { + if let Ok(account_id) = AccountId::try_from(current_digest[0]) { + if account_id.account_type() == account_type + && account_id.storage_mode() == storage_mode + { + #[cfg(feature = "log")] + log.done(current_digest, current_seed, account_id); + + let _ = send.send((current_digest, current_seed)); + return; + }; + } + } + current_seed = current_digest.into(); + current_digest = compute_digest(current_seed, code_commitment, storage_commitment); + } +} + +#[cfg(not(feature = "concurrent"))] +pub fn get_account_seed( + init_seed: [u8; 32], + account_type: AccountType, + storage_mode: AccountStorageMode, + code_commitment: Digest, + storage_commitment: Digest, +) -> Result { + get_account_seed_single( + init_seed, + account_type, + storage_mode, + code_commitment, + storage_commitment, + ) +} + +/// Finds and returns a seed suitable for creating an account ID for the specified account type +/// using the provided initial seed as a starting point. Using a single thread. +pub fn get_account_seed_single( + init_seed: [u8; 32], + account_type: AccountType, + storage_mode: AccountStorageMode, + code_commitment: Digest, + storage_commitment: Digest, +) -> Result { + let init_seed: Vec<[u8; 8]> = + init_seed.chunks(8).map(|chunk| chunk.try_into().unwrap()).collect(); + let mut current_seed: Word = [ + Felt::new(u64::from_le_bytes(init_seed[0])), + Felt::new(u64::from_le_bytes(init_seed[1])), + Felt::new(u64::from_le_bytes(init_seed[2])), + Felt::new(u64::from_le_bytes(init_seed[3])), + ]; + let mut current_digest = compute_digest(current_seed, code_commitment, storage_commitment); + + #[cfg(feature = "log")] + let mut log = log::Log::start(current_digest, current_seed, account_type, storage_mode); + + // loop until we have a seed that satisfies the specified account type. + loop { + #[cfg(feature = "log")] + log.iteration(current_digest, current_seed); + + // check if the seed satisfies the specified account type + if AccountId::validate_seed_digest(¤t_digest).is_ok() { + if let Ok(account_id) = AccountId::try_from(current_digest[0]) { + if account_id.account_type() == account_type + && account_id.storage_mode() == storage_mode + { + #[cfg(feature = "log")] + log.done(current_digest, current_seed, account_id); + + return Ok(current_seed); + }; + } + } + current_seed = current_digest.into(); + current_digest = compute_digest(current_seed, code_commitment, storage_commitment); + } +} + +#[cfg(feature = "log")] +mod log { + use alloc::string::String; + + use assembly::utils::to_hex; + use miden_crypto::FieldElement; + + use super::{ + super::{account_id::digest_pow, Digest, Word}, + AccountId, AccountType, + }; + use crate::accounts::AccountStorageMode; + + /// Keeps track of the best digest found so far and count how many iterations have been done. + pub struct Log { + digest: Digest, + seed: Word, + count: usize, + pow: u32, + } + + /// Given a [Digest] returns its hex representation. + pub fn digest_hex(digest: Digest) -> String { + to_hex(digest.as_bytes()) + } + + /// Given a [Word] returns its hex representation. + pub fn word_hex(word: Word) -> String { + to_hex(FieldElement::elements_as_bytes(&word)) + } + + impl Log { + pub fn start( + digest: Digest, + seed: Word, + account_type: AccountType, + storage_mode: AccountStorageMode, + ) -> Self { + log::info!( + "Generating new account seed [pow={}, digest={}, seed={} type={:?} onchain={:?}]", + digest_pow(digest), + digest_hex(digest), + word_hex(seed), + account_type, + storage_mode, + ); + + Self { digest, seed, count: 0, pow: 0 } + } + + pub fn iteration(&mut self, digest: Digest, seed: Word) { + self.count += 1; + + let pow = digest_pow(digest); + if pow >= self.pow { + self.digest = digest; + self.seed = seed; + self.pow = pow; + } + + if self.count % 500_000 == 0 { + log::debug!( + "Account seed loop [count={}, pow={}, digest={}, seed={}]", + self.count, + self.pow, + digest_hex(self.digest), + word_hex(self.seed), + ); + } + } + + pub fn done(self, digest: Digest, seed: Word, account_id: AccountId) { + log::info!( + "Found account seed [pow={}, current_digest={}, current_seed={} type={:?} onchain={}]]", + digest_pow(digest), + digest_hex(digest), + word_hex(seed), + account_id.account_type(), + account_id.is_public(), + ); + } + } +} diff --git a/objects/src/assets/fungible.rs b/objects/src/assets/fungible.rs index d7e3a716a..f2d36bc39 100644 --- a/objects/src/assets/fungible.rs +++ b/objects/src/assets/fungible.rs @@ -1,10 +1,7 @@ use alloc::{boxed::Box, string::ToString}; use core::fmt; -use vm_core::{ - utils::{ByteReader, ByteWriter, Deserializable, Serializable}, - FieldElement, -}; +use vm_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; use vm_processor::DeserializationError; use super::{is_not_a_non_fungible_asset, AccountType, Asset, AssetError, Felt, Word, ZERO}; @@ -30,8 +27,8 @@ impl FungibleAsset { /// The serialized size of a [`FungibleAsset`] in bytes. /// - /// Currently an account id (felt) plus an amount (u64). - pub const SERIALIZED_SIZE: usize = Felt::ELEMENT_BYTES + core::mem::size_of::(); + /// Currently an account id (15 bytes) plus an amount (u64). + pub const SERIALIZED_SIZE: usize = AccountId::SERIALIZED_SIZE + core::mem::size_of::(); // CONSTRUCTOR // -------------------------------------------------------------------------------------------- @@ -49,7 +46,7 @@ impl FungibleAsset { /// Creates a new [FungibleAsset] without checking its validity. pub(crate) fn new_unchecked(value: Word) -> FungibleAsset { FungibleAsset { - faucet_id: AccountId::new_unchecked([value[2], value[3]]), + faucet_id: AccountId::new_unchecked([value[3], value[2]]), amount: value[0].as_int(), } } @@ -76,8 +73,8 @@ impl FungibleAsset { pub fn vault_key(&self) -> Word { let mut key = Word::default(); let elements: [Felt; 2] = self.faucet_id.into(); - key[2] = elements[0]; - key[3] = elements[1]; + key[2] = elements[1]; + key[3] = elements[0]; key } @@ -152,8 +149,8 @@ impl From for Word { let mut result = Word::default(); let id_elements: [Felt; 2] = asset.faucet_id.into(); result[0] = Felt::new(asset.amount); - result[2] = id_elements[0]; - result[3] = id_elements[1]; + result[2] = id_elements[1]; + result[3] = id_elements[0]; debug_assert!(is_not_a_non_fungible_asset(result)); result } @@ -246,12 +243,14 @@ mod tests { let account_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3).unwrap(); let asset = FungibleAsset::new(account_id, 50).unwrap(); let mut asset_bytes = asset.to_bytes(); + assert_eq!(asset_bytes.len(), asset.get_size_hint()); + assert_eq!(asset.get_size_hint(), FungibleAsset::SERIALIZED_SIZE); - let non_fungible_id = + let non_fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(); // Set invalid Faucet ID. - asset_bytes[0..15].copy_from_slice(&non_fungible_id.to_bytes()); + asset_bytes[0..15].copy_from_slice(&non_fungible_faucet_id.to_bytes()); let err = FungibleAsset::read_from_bytes(&asset_bytes).unwrap_err(); assert!(matches!(err, DeserializationError::InvalidValue(_))); } diff --git a/objects/src/assets/nonfungible.rs b/objects/src/assets/nonfungible.rs index f7cf67f92..5618133b5 100644 --- a/objects/src/assets/nonfungible.rs +++ b/objects/src/assets/nonfungible.rs @@ -244,6 +244,8 @@ impl NonFungibleAssetDetails { #[cfg(test)] mod tests { + use assert_matches::assert_matches; + use super::*; use crate::accounts::{ account_id::testing::{ @@ -273,9 +275,13 @@ mod tests { let details = NonFungibleAssetDetails::new(account.prefix(), vec![4, 5, 6, 7]).unwrap(); let asset = NonFungibleAsset::new(&details).unwrap(); let mut asset_bytes = asset.to_bytes(); - // Set invalid Faucet ID. - asset_bytes[0..8].copy_from_slice(&ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN.to_le_bytes()); + + let fungible_faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(); + + // Set invalid Faucet ID Prefix. + asset_bytes[0..8].copy_from_slice(&fungible_faucet_id.prefix().to_bytes()); + let err = NonFungibleAsset::read_from_bytes(&asset_bytes).unwrap_err(); - assert!(matches!(err, DeserializationError::InvalidValue(_))); + assert_matches!(err, DeserializationError::InvalidValue(msg) if msg.contains("must be of type NonFungibleFaucet")); } }