miden_objects/account/account_id/v0/
mod.rs1mod prefix;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::fmt;
5use core::hash::Hash;
6
7use miden_crypto::utils::hex_to_bytes;
8pub use prefix::AccountIdPrefixV0;
9
10use crate::account::account_id::account_type::{
11    FUNGIBLE_FAUCET,
12    NON_FUNGIBLE_FAUCET,
13    REGULAR_ACCOUNT_IMMUTABLE_CODE,
14    REGULAR_ACCOUNT_UPDATABLE_CODE,
15};
16use crate::account::account_id::storage_mode::{NETWORK, PRIVATE, PUBLIC};
17use crate::account::{AccountIdVersion, AccountStorageMode, AccountType};
18use crate::errors::AccountIdError;
19use crate::utils::{ByteReader, Deserializable, DeserializationError, Serializable};
20use crate::{AccountError, EMPTY_WORD, Felt, Hasher, Word};
21
22#[derive(Debug, Copy, Clone, Eq, PartialEq)]
29pub struct AccountIdV0 {
30    prefix: Felt,
31    suffix: Felt,
32}
33
34impl Hash for AccountIdV0 {
35    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
36        self.prefix.inner().hash(state);
37        self.suffix.inner().hash(state);
38    }
39}
40
41impl AccountIdV0 {
42    const SERIALIZED_SIZE: usize = 15;
47
48    pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT;
50    pub(crate) const TYPE_SHIFT: u64 = 4;
51
52    const VERSION_MASK: u64 = 0b1111;
54
55    pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT;
58    pub(crate) const STORAGE_MODE_SHIFT: u64 = 6;
59
60    pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT;
62
63    pub fn new(
68        seed: Word,
69        code_commitment: Word,
70        storage_commitment: Word,
71    ) -> Result<Self, AccountIdError> {
72        let seed_digest = compute_digest(seed, code_commitment, storage_commitment);
73
74        let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2]
75            .try_into()
76            .expect("we should have sliced off 2 elements");
77
78        felts[1] = shape_suffix(felts[1]);
79
80        account_id_from_felts(felts)
81    }
82
83    pub fn new_unchecked(elements: [Felt; 2]) -> Self {
85        let prefix = elements[0];
86        let suffix = elements[1];
87
88        if cfg!(debug_assertions) {
90            validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix");
91            validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix");
92        }
93
94        Self { prefix, suffix }
95    }
96
97    #[cfg(any(feature = "testing", test))]
99    pub fn dummy(
100        mut bytes: [u8; 15],
101        account_type: AccountType,
102        storage_mode: AccountStorageMode,
103    ) -> AccountIdV0 {
104        let version = AccountIdVersion::Version0 as u8;
105        let low_nibble = ((storage_mode as u8) << Self::STORAGE_MODE_SHIFT)
106            | ((account_type as u8) << Self::TYPE_SHIFT)
107            | version;
108
109        bytes[7] = low_nibble;
111
112        bytes[3] &= 0b1111_1110;
114
115        let prefix_bytes =
116            bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes");
117        let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes))
118            .expect("should be a valid felt due to the most significant bit being zero");
119
120        let mut suffix_bytes = [0; 8];
121        suffix_bytes[..7].copy_from_slice(&bytes[8..]);
124
125        let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes));
127
128        suffix = Felt::try_from(suffix.as_int() & 0x7fff_ffff_ffff_ffff)
130            .expect("no bits were set so felt should still be valid");
131
132        suffix = shape_suffix(suffix);
133
134        let account_id = account_id_from_felts([prefix, suffix])
135            .expect("we should have shaped the felts to produce a valid id");
136
137        debug_assert_eq!(account_id.account_type(), account_type);
138        debug_assert_eq!(account_id.storage_mode(), storage_mode);
139
140        account_id
141    }
142
143    pub fn compute_account_seed(
145        init_seed: [u8; 32],
146        account_type: AccountType,
147        storage_mode: AccountStorageMode,
148        version: AccountIdVersion,
149        code_commitment: Word,
150        storage_commitment: Word,
151    ) -> Result<Word, AccountError> {
152        crate::account::account_id::seed::compute_account_seed(
153            init_seed,
154            account_type,
155            storage_mode,
156            version,
157            code_commitment,
158            storage_commitment,
159        )
160    }
161
162    pub const fn account_type(&self) -> AccountType {
167        extract_type(self.prefix.as_int())
168    }
169
170    pub fn is_faucet(&self) -> bool {
172        self.account_type().is_faucet()
173    }
174
175    pub fn is_regular_account(&self) -> bool {
177        self.account_type().is_regular_account()
178    }
179
180    pub fn storage_mode(&self) -> AccountStorageMode {
182        extract_storage_mode(self.prefix().as_u64())
183            .expect("account ID should have been constructed with a valid storage mode")
184    }
185
186    pub fn is_public(&self) -> bool {
188        self.storage_mode() == AccountStorageMode::Public
189    }
190
191    pub fn version(&self) -> AccountIdVersion {
193        extract_version(self.prefix().as_u64())
194            .expect("account ID should have been constructed with a valid version")
195    }
196
197    pub fn from_hex(hex_str: &str) -> Result<AccountIdV0, AccountIdError> {
199        hex_to_bytes(hex_str)
200            .map_err(AccountIdError::AccountIdHexParseError)
201            .and_then(AccountIdV0::try_from)
202    }
203
204    pub fn to_hex(self) -> String {
206        let mut hex_string =
210            format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_int());
211        hex_string.truncate(32);
212        hex_string
213    }
214
215    pub fn prefix(&self) -> AccountIdPrefixV0 {
219        AccountIdPrefixV0::new_unchecked(self.prefix)
222    }
223
224    pub const fn suffix(&self) -> Felt {
226        self.suffix
227    }
228}
229
230impl From<AccountIdV0> for [Felt; 2] {
234    fn from(id: AccountIdV0) -> Self {
235        [id.prefix, id.suffix]
236    }
237}
238
239impl From<AccountIdV0> for [u8; 15] {
240    fn from(id: AccountIdV0) -> Self {
241        let mut result = [0_u8; 15];
242        result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
243        result[8..].copy_from_slice(&id.suffix().as_int().to_be_bytes()[..7]);
245        result
246    }
247}
248
249impl From<AccountIdV0> for u128 {
250    fn from(id: AccountIdV0) -> Self {
251        let mut le_bytes = [0_u8; 16];
252        le_bytes[..8].copy_from_slice(&id.suffix().as_int().to_le_bytes());
253        le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
254        u128::from_le_bytes(le_bytes)
255    }
256}
257
258impl TryFrom<[Felt; 2]> for AccountIdV0 {
262    type Error = AccountIdError;
263
264    fn try_from(elements: [Felt; 2]) -> Result<Self, Self::Error> {
267        account_id_from_felts(elements)
268    }
269}
270
271impl TryFrom<[u8; 15]> for AccountIdV0 {
272    type Error = AccountIdError;
273
274    fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
277        bytes[..8].reverse();
280        bytes[8..15].reverse();
282
283        let prefix_slice = &bytes[..8];
284        let suffix_slice = &bytes[8..15];
285
286        let mut suffix_bytes = [0; 8];
289        suffix_bytes[1..8].copy_from_slice(suffix_slice);
290
291        let prefix = Felt::try_from(prefix_slice)
292            .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
293
294        let suffix = Felt::try_from(suffix_bytes.as_slice())
295            .map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?;
296
297        Self::try_from([prefix, suffix])
298    }
299}
300
301impl TryFrom<u128> for AccountIdV0 {
302    type Error = AccountIdError;
303
304    fn try_from(int: u128) -> Result<Self, Self::Error> {
307        let mut bytes: [u8; 15] = [0; 15];
308        bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
309
310        Self::try_from(bytes)
311    }
312}
313
314impl Serializable for AccountIdV0 {
318    fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
319        let bytes: [u8; 15] = (*self).into();
320        bytes.write_into(target);
321    }
322
323    fn get_size_hint(&self) -> usize {
324        Self::SERIALIZED_SIZE
325    }
326}
327
328impl Deserializable for AccountIdV0 {
329    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
330        <[u8; 15]>::read_from(source)?
331            .try_into()
332            .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
333    }
334}
335
336fn account_id_from_felts(elements: [Felt; 2]) -> Result<AccountIdV0, AccountIdError> {
346    validate_prefix(elements[0])?;
347    validate_suffix(elements[1])?;
348
349    Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] })
350}
351
352pub(crate) fn validate_prefix(
355    prefix: Felt,
356) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
357    let prefix = prefix.as_int();
358
359    let storage_mode = extract_storage_mode(prefix)?;
361
362    let version = extract_version(prefix)?;
364
365    let account_type = extract_type(prefix);
366
367    Ok((account_type, storage_mode, version))
368}
369
370const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
374    let suffix = suffix.as_int();
375
376    if suffix >> 63 != 0 {
378        return Err(AccountIdError::AccountIdSuffixMostSignificantBitMustBeZero);
379    }
380
381    if suffix & 0xff != 0 {
383        return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
384    }
385
386    Ok(())
387}
388
389pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
390    let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
391    match bits as u8 {
393        PUBLIC => Ok(AccountStorageMode::Public),
394        NETWORK => Ok(AccountStorageMode::Network),
395        PRIVATE => Ok(AccountStorageMode::Private),
396        _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
397    }
398}
399
400pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
401    let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
404    AccountIdVersion::try_from(version)
405}
406
407pub(crate) const fn extract_type(prefix: u64) -> AccountType {
408    let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
409    match bits as u8 {
411        REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
412        REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
413        FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
414        NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
415        _ => {
416            panic!("type mask contains only 2 bits and we've covered all 4 possible options")
418        },
419    }
420}
421
422fn shape_suffix(suffix: Felt) -> Felt {
425    let mut suffix = suffix.as_int();
426
427    suffix &= 0xffff_ffff_ffff_ff00;
429
430    Felt::try_from(suffix).expect("no bits were set so felt should still be valid")
432}
433
434impl PartialOrd for AccountIdV0 {
438    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
439        Some(self.cmp(other))
440    }
441}
442
443impl Ord for AccountIdV0 {
444    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
445        u128::from(*self).cmp(&u128::from(*other))
446    }
447}
448
449impl fmt::Display for AccountIdV0 {
450    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451        write!(f, "{}", self.to_hex())
452    }
453}
454
455pub(crate) fn compute_digest(seed: Word, code_commitment: Word, storage_commitment: Word) -> Word {
458    let mut elements = Vec::with_capacity(16);
459    elements.extend(seed);
460    elements.extend(*code_commitment);
461    elements.extend(*storage_commitment);
462    elements.extend(EMPTY_WORD);
463    Hasher::hash_elements(&elements)
464}
465
466#[cfg(test)]
470mod tests {
471
472    use super::*;
473    use crate::account::AccountIdPrefix;
474    use crate::testing::account_id::{
475        ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
476        ACCOUNT_ID_PRIVATE_SENDER,
477        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
478        ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
479        ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
480    };
481
482    #[test]
483    fn account_id_from_felts_with_max_pop_count() {
484        let valid_suffix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
485        let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff70u64).unwrap();
486
487        let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
488        assert_eq!(id1.account_type(), AccountType::NonFungibleFaucet);
489        assert_eq!(id1.storage_mode(), AccountStorageMode::Network);
490        assert_eq!(id1.version(), AccountIdVersion::Version0);
491    }
492
493    #[test]
494    fn account_id_dummy_construction() {
495        for input in [[0xff; 15], [0; 15]] {
500            for account_type in [
501                AccountType::FungibleFaucet,
502                AccountType::NonFungibleFaucet,
503                AccountType::RegularAccountImmutableCode,
504                AccountType::RegularAccountUpdatableCode,
505            ] {
506                for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
507                    let id = AccountIdV0::dummy(input, account_type, storage_mode);
508                    assert_eq!(id.account_type(), account_type);
509                    assert_eq!(id.storage_mode(), storage_mode);
510                    assert_eq!(id.version(), AccountIdVersion::Version0);
511
512                    let serialized_id = id.to_bytes();
514                    AccountIdV0::read_from_bytes(&serialized_id).unwrap();
515                    assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
516                }
517            }
518        }
519    }
520
521    #[test]
522    fn account_id_prefix_serialization_compatibility() {
523        let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
525        let id_bytes = account_id.to_bytes();
526        assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
527
528        let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
529        assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
530
531        assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
533    }
534
535    #[test]
539    fn test_account_id_conversion_roundtrip() {
540        for (idx, account_id) in [
541            ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
542            ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
543            ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
544            ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
545            ACCOUNT_ID_PRIVATE_SENDER,
546        ]
547        .into_iter()
548        .enumerate()
549        {
550            let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
551            assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
552            assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
553            assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
554            assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
557            assert_eq!(account_id, u128::from(id), "failed in {idx}");
558        }
559    }
560
561    #[test]
562    fn test_account_id_tag_identifiers() {
563        let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
564            .expect("valid account ID");
565        assert!(account_id.is_regular_account());
566        assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
567        assert!(account_id.is_public());
568
569        let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
570            .expect("valid account ID");
571        assert!(account_id.is_regular_account());
572        assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
573        assert!(!account_id.is_public());
574
575        let account_id =
576            AccountIdV0::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
577        assert!(account_id.is_faucet());
578        assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
579        assert!(account_id.is_public());
580
581        let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
582            .expect("valid account ID");
583        assert!(account_id.is_faucet());
584        assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
585        assert!(!account_id.is_public());
586    }
587}