1mod prefix;
2use alloc::{
3 string::{String, ToString},
4 vec::Vec,
5};
6use core::fmt;
7
8use bech32::{Bech32m, primitives::decode::CheckedHrpstring};
9use miden_crypto::{merkle::LeafIndex, utils::hex_to_bytes};
10pub use prefix::AccountIdPrefixV0;
11use vm_core::{
12 Felt, Word,
13 utils::{ByteReader, Deserializable, Serializable},
14};
15use vm_processor::{DeserializationError, Digest};
16
17use crate::{
18 ACCOUNT_TREE_DEPTH, AccountError, Hasher,
19 account::{
20 AccountIdAnchor, AccountIdVersion, AccountStorageMode, AccountType,
21 account_id::{
22 NetworkId,
23 account_type::{
24 FUNGIBLE_FAUCET, NON_FUNGIBLE_FAUCET, REGULAR_ACCOUNT_IMMUTABLE_CODE,
25 REGULAR_ACCOUNT_UPDATABLE_CODE,
26 },
27 address_type::AddressType,
28 storage_mode::{PRIVATE, PUBLIC},
29 },
30 },
31 errors::{AccountIdError, Bech32Error},
32};
33
34#[derive(Debug, Copy, Clone, Eq, PartialEq)]
41pub struct AccountIdV0 {
42 prefix: Felt,
43 suffix: Felt,
44}
45
46impl AccountIdV0 {
47 const SERIALIZED_SIZE: usize = 15;
52
53 pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT;
55 pub(crate) const TYPE_SHIFT: u64 = 4;
56
57 const VERSION_MASK: u64 = 0b1111;
59
60 const ANCHOR_EPOCH_MASK: u64 = 0xffff << Self::ANCHOR_EPOCH_SHIFT;
62 const ANCHOR_EPOCH_SHIFT: u64 = 48;
63
64 pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT;
67 pub(crate) const STORAGE_MODE_SHIFT: u64 = 6;
68
69 pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT;
71
72 pub fn new(
77 seed: Word,
78 anchor: AccountIdAnchor,
79 code_commitment: Digest,
80 storage_commitment: Digest,
81 ) -> Result<Self, AccountIdError> {
82 let seed_digest =
83 compute_digest(seed, code_commitment, storage_commitment, anchor.block_commitment());
84
85 let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2]
86 .try_into()
87 .expect("we should have sliced off 2 elements");
88
89 felts[1] = shape_suffix(felts[1], anchor.epoch())?;
90
91 account_id_from_felts(felts)
93 }
94
95 pub fn new_unchecked(elements: [Felt; 2]) -> Self {
97 let prefix = elements[0];
98 let suffix = elements[1];
99
100 if cfg!(debug_assertions) {
102 validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix");
103 validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix");
104 }
105
106 Self { prefix, suffix }
107 }
108
109 #[cfg(any(feature = "testing", test))]
111 pub fn dummy(
112 mut bytes: [u8; 15],
113 account_type: AccountType,
114 storage_mode: AccountStorageMode,
115 ) -> AccountIdV0 {
116 let version = AccountIdVersion::Version0 as u8;
117 let low_nibble = ((storage_mode as u8) << Self::STORAGE_MODE_SHIFT)
118 | ((account_type as u8) << Self::TYPE_SHIFT)
119 | version;
120
121 bytes[7] = low_nibble;
123
124 bytes[3] &= 0b1111_1110;
126
127 let prefix_bytes =
128 bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes");
129 let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes))
130 .expect("should be a valid felt due to the most significant bit being zero");
131
132 let mut suffix_bytes = [0; 8];
133 suffix_bytes[..7].copy_from_slice(&bytes[8..]);
136 let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes));
138
139 suffix = shape_suffix(suffix, 0).expect("anchor epoch is not u16::MAX");
140
141 let account_id = account_id_from_felts([prefix, suffix])
142 .expect("we should have shaped the felts to produce a valid id");
143
144 debug_assert_eq!(account_id.account_type(), account_type);
145 debug_assert_eq!(account_id.storage_mode(), storage_mode);
146
147 account_id
148 }
149
150 pub fn compute_account_seed(
152 init_seed: [u8; 32],
153 account_type: AccountType,
154 storage_mode: AccountStorageMode,
155 version: AccountIdVersion,
156 code_commitment: Digest,
157 storage_commitment: Digest,
158 anchor_block_commitment: Digest,
159 ) -> Result<Word, AccountError> {
160 crate::account::account_id::seed::compute_account_seed(
161 init_seed,
162 account_type,
163 storage_mode,
164 version,
165 code_commitment,
166 storage_commitment,
167 anchor_block_commitment,
168 )
169 }
170
171 pub const fn account_type(&self) -> AccountType {
176 extract_type(self.prefix.as_int())
177 }
178
179 pub fn is_faucet(&self) -> bool {
181 self.account_type().is_faucet()
182 }
183
184 pub fn is_regular_account(&self) -> bool {
186 self.account_type().is_regular_account()
187 }
188
189 pub fn storage_mode(&self) -> AccountStorageMode {
191 extract_storage_mode(self.prefix().as_u64())
192 .expect("account ID should have been constructed with a valid storage mode")
193 }
194
195 pub fn is_public(&self) -> bool {
197 self.storage_mode() == AccountStorageMode::Public
198 }
199
200 pub fn version(&self) -> AccountIdVersion {
202 extract_version(self.prefix().as_u64())
203 .expect("account ID should have been constructed with a valid version")
204 }
205
206 pub fn anchor_epoch(&self) -> u16 {
208 extract_anchor_epoch(self.suffix().as_int())
209 }
210
211 pub fn from_hex(hex_str: &str) -> Result<AccountIdV0, AccountIdError> {
213 hex_to_bytes(hex_str)
214 .map_err(AccountIdError::AccountIdHexParseError)
215 .and_then(AccountIdV0::try_from)
216 }
217
218 pub fn to_hex(self) -> String {
220 let mut hex_string =
224 format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_int());
225 hex_string.truncate(32);
226 hex_string
227 }
228
229 pub fn to_bech32(&self, network_id: NetworkId) -> String {
231 let id_bytes: [u8; Self::SERIALIZED_SIZE] = (*self).into();
232
233 let mut data = [0; Self::SERIALIZED_SIZE + 1];
234 data[0] = AddressType::AccountId as u8;
235 data[1..16].copy_from_slice(&id_bytes);
236
237 bech32::encode::<Bech32m>(network_id.into_hrp(), &data)
241 .expect("code length of bech32 should not be exceeded")
242 }
243
244 pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AccountIdError> {
246 let checked_string = CheckedHrpstring::new::<Bech32m>(bech32_string).map_err(|source| {
249 AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into()))
254 })?;
255
256 let hrp = checked_string.hrp();
257 let network_id = NetworkId::from_hrp(hrp);
258
259 let mut byte_iter = checked_string.byte_iter();
260 if byte_iter.len() != Self::SERIALIZED_SIZE + 1 {
262 return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
263 expected: Self::SERIALIZED_SIZE + 1,
264 actual: byte_iter.len(),
265 }));
266 }
267
268 let address_byte = byte_iter.next().expect("there should be at least one byte");
269 if address_byte != AddressType::AccountId as u8 {
270 return Err(AccountIdError::Bech32DecodeError(Bech32Error::UnknownAddressType(
271 address_byte,
272 )));
273 }
274
275 let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE];
278 for (i, byte) in byte_iter.enumerate() {
279 id_bytes[i] = byte;
280 }
281
282 let account_id = Self::try_from(id_bytes)?;
283
284 Ok((network_id, account_id))
285 }
286
287 pub fn prefix(&self) -> AccountIdPrefixV0 {
291 AccountIdPrefixV0::new_unchecked(self.prefix)
294 }
295
296 pub const fn suffix(&self) -> Felt {
298 self.suffix
299 }
300}
301
302impl From<AccountIdV0> for [Felt; 2] {
306 fn from(id: AccountIdV0) -> Self {
307 [id.prefix, id.suffix]
308 }
309}
310
311impl From<AccountIdV0> for [u8; 15] {
312 fn from(id: AccountIdV0) -> Self {
313 let mut result = [0_u8; 15];
314 result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
315 result[8..].copy_from_slice(&id.suffix().as_int().to_be_bytes()[..7]);
317 result
318 }
319}
320
321impl From<AccountIdV0> for u128 {
322 fn from(id: AccountIdV0) -> Self {
323 let mut le_bytes = [0_u8; 16];
324 le_bytes[..8].copy_from_slice(&id.suffix().as_int().to_le_bytes());
325 le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
326 u128::from_le_bytes(le_bytes)
327 }
328}
329
330impl From<AccountIdV0> for LeafIndex<ACCOUNT_TREE_DEPTH> {
332 fn from(id: AccountIdV0) -> Self {
333 LeafIndex::new_max_depth(id.prefix().as_u64())
334 }
335}
336
337impl TryFrom<[Felt; 2]> for AccountIdV0 {
341 type Error = AccountIdError;
342
343 fn try_from(elements: [Felt; 2]) -> Result<Self, Self::Error> {
346 account_id_from_felts(elements)
347 }
348}
349
350impl TryFrom<[u8; 15]> for AccountIdV0 {
351 type Error = AccountIdError;
352
353 fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
356 bytes[..8].reverse();
359 bytes[8..15].reverse();
361
362 let prefix_slice = &bytes[..8];
363 let suffix_slice = &bytes[8..15];
364
365 let mut suffix_bytes = [0; 8];
368 suffix_bytes[1..8].copy_from_slice(suffix_slice);
369
370 let prefix = Felt::try_from(prefix_slice)
371 .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
372
373 let suffix = Felt::try_from(suffix_bytes.as_slice())
374 .map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?;
375
376 Self::try_from([prefix, suffix])
377 }
378}
379
380impl TryFrom<u128> for AccountIdV0 {
381 type Error = AccountIdError;
382
383 fn try_from(int: u128) -> Result<Self, Self::Error> {
386 let mut bytes: [u8; 15] = [0; 15];
387 bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
388
389 Self::try_from(bytes)
390 }
391}
392
393impl Serializable for AccountIdV0 {
397 fn write_into<W: miden_crypto::utils::ByteWriter>(&self, target: &mut W) {
398 let bytes: [u8; 15] = (*self).into();
399 bytes.write_into(target);
400 }
401
402 fn get_size_hint(&self) -> usize {
403 Self::SERIALIZED_SIZE
404 }
405}
406
407impl Deserializable for AccountIdV0 {
408 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
409 <[u8; 15]>::read_from(source)?
410 .try_into()
411 .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
412 }
413}
414
415fn account_id_from_felts(elements: [Felt; 2]) -> Result<AccountIdV0, AccountIdError> {
425 validate_prefix(elements[0])?;
426 validate_suffix(elements[1])?;
427
428 Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] })
429}
430
431pub(crate) fn validate_prefix(
434 prefix: Felt,
435) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
436 let prefix = prefix.as_int();
437
438 let storage_mode = extract_storage_mode(prefix)?;
440
441 let version = extract_version(prefix)?;
443
444 let account_type = extract_type(prefix);
445
446 Ok((account_type, storage_mode, version))
447}
448
449const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
453 let suffix = suffix.as_int();
454
455 if extract_anchor_epoch(suffix) == u16::MAX {
456 return Err(AccountIdError::AnchorEpochMustNotBeU16Max);
457 }
458
459 if suffix & 0xff != 0 {
461 return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
462 }
463
464 Ok(())
465}
466
467pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
468 let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
469 match bits as u8 {
471 PUBLIC => Ok(AccountStorageMode::Public),
472 PRIVATE => Ok(AccountStorageMode::Private),
473 _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
474 }
475}
476
477pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
478 let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
481 AccountIdVersion::try_from(version)
482}
483
484pub(crate) const fn extract_type(prefix: u64) -> AccountType {
485 let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
486 match bits as u8 {
488 REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
489 REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
490 FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
491 NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
492 _ => {
493 panic!("type mask contains only 2 bits and we've covered all 4 possible options")
495 },
496 }
497}
498
499const fn extract_anchor_epoch(suffix: u64) -> u16 {
500 ((suffix & AccountIdV0::ANCHOR_EPOCH_MASK) >> AccountIdV0::ANCHOR_EPOCH_SHIFT) as u16
501}
502
503fn shape_suffix(suffix: Felt, anchor_epoch: u16) -> Result<Felt, AccountIdError> {
506 if anchor_epoch == u16::MAX {
507 return Err(AccountIdError::AnchorEpochMustNotBeU16Max);
508 }
509
510 let mut suffix = suffix.as_int();
511
512 suffix &= 0x0000_ffff_ffff_ff00;
514
515 suffix |= (anchor_epoch as u64) << AccountIdV0::ANCHOR_EPOCH_SHIFT;
517
518 Ok(Felt::try_from(suffix).expect("epoch is never all ones so felt should be valid"))
521}
522
523impl PartialOrd for AccountIdV0 {
527 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
528 Some(self.cmp(other))
529 }
530}
531
532impl Ord for AccountIdV0 {
533 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
534 u128::from(*self).cmp(&u128::from(*other))
535 }
536}
537
538impl fmt::Display for AccountIdV0 {
539 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
540 write!(f, "{}", self.to_hex())
541 }
542}
543
544pub(crate) fn compute_digest(
547 seed: Word,
548 code_commitment: Digest,
549 storage_commitment: Digest,
550 anchor_block_commitment: Digest,
551) -> Digest {
552 let mut elements = Vec::with_capacity(16);
553 elements.extend(seed);
554 elements.extend(*code_commitment);
555 elements.extend(*storage_commitment);
556 elements.extend(*anchor_block_commitment);
557 Hasher::hash_elements(&elements)
558}
559
560#[cfg(test)]
564mod tests {
565
566 use super::*;
567 use crate::{
568 account::AccountIdPrefix,
569 testing::account_id::{
570 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_SENDER,
571 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
572 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
573 },
574 };
575
576 #[test]
577 fn test_account_id_from_seed_with_epoch() {
578 let code_commitment: Digest = Digest::default();
579 let storage_commitment: Digest = Digest::default();
580 let anchor_block_commitment: Digest = Digest::default();
581
582 let seed = AccountIdV0::compute_account_seed(
583 [10; 32],
584 AccountType::FungibleFaucet,
585 AccountStorageMode::Public,
586 AccountIdVersion::Version0,
587 code_commitment,
588 storage_commitment,
589 anchor_block_commitment,
590 )
591 .unwrap();
592
593 for anchor_epoch in [0, u16::MAX - 1, 5000] {
594 let anchor = AccountIdAnchor::new_unchecked(anchor_epoch, anchor_block_commitment);
595 let id = AccountIdV0::new(seed, anchor, code_commitment, storage_commitment).unwrap();
596 assert_eq!(id.anchor_epoch(), anchor_epoch, "failed for account ID: {id}");
597 }
598 }
599
600 #[test]
601 fn account_id_from_felts_with_high_pop_count() {
602 let valid_suffix = Felt::try_from(0xfffe_ffff_ffff_ff00u64).unwrap();
603 let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
604
605 let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
606 assert_eq!(id1.account_type(), AccountType::RegularAccountImmutableCode);
607 assert_eq!(id1.storage_mode(), AccountStorageMode::Public);
608 assert_eq!(id1.version(), AccountIdVersion::Version0);
609 assert_eq!(id1.anchor_epoch(), u16::MAX - 1);
610 }
611
612 #[test]
613 fn account_id_construction() {
614 for input in [[0xff; 15], [0; 15]] {
619 for account_type in [
620 AccountType::FungibleFaucet,
621 AccountType::NonFungibleFaucet,
622 AccountType::RegularAccountImmutableCode,
623 AccountType::RegularAccountUpdatableCode,
624 ] {
625 for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
626 let id = AccountIdV0::dummy(input, account_type, storage_mode);
627 assert_eq!(id.account_type(), account_type);
628 assert_eq!(id.storage_mode(), storage_mode);
629 assert_eq!(id.version(), AccountIdVersion::Version0);
630 assert_eq!(id.anchor_epoch(), 0);
631
632 let serialized_id = id.to_bytes();
634 AccountIdV0::read_from_bytes(&serialized_id).unwrap();
635 assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
636 }
637 }
638 }
639 }
640
641 #[test]
642 fn account_id_prefix_serialization_compatibility() {
643 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
645 let id_bytes = account_id.to_bytes();
646 assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
647
648 let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
649 assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
650
651 assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
653 }
654
655 #[test]
659 fn test_account_id_conversion_roundtrip() {
660 for (idx, account_id) in [
661 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
662 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
663 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
664 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
665 ACCOUNT_ID_PRIVATE_SENDER,
666 ]
667 .into_iter()
668 .enumerate()
669 {
670 let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
671 assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
672 assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
673 assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
674 assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
677 assert_eq!(account_id, u128::from(id), "failed in {idx}");
678 }
679 }
680
681 #[test]
682 fn test_account_id_tag_identifiers() {
683 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
684 .expect("valid account ID");
685 assert!(account_id.is_regular_account());
686 assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
687 assert!(account_id.is_public());
688
689 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
690 .expect("valid account ID");
691 assert!(account_id.is_regular_account());
692 assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
693 assert!(!account_id.is_public());
694
695 let account_id =
696 AccountIdV0::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
697 assert!(account_id.is_faucet());
698 assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
699 assert!(account_id.is_public());
700
701 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
702 .expect("valid account ID");
703 assert!(account_id.is_faucet());
704 assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
705 assert!(!account_id.is_public());
706 }
707}