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::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 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::{NETWORK, 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 TryFrom<[Felt; 2]> for AccountIdV0 {
334 type Error = AccountIdError;
335
336 fn try_from(elements: [Felt; 2]) -> Result<Self, Self::Error> {
339 account_id_from_felts(elements)
340 }
341}
342
343impl TryFrom<[u8; 15]> for AccountIdV0 {
344 type Error = AccountIdError;
345
346 fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
349 bytes[..8].reverse();
352 bytes[8..15].reverse();
354
355 let prefix_slice = &bytes[..8];
356 let suffix_slice = &bytes[8..15];
357
358 let mut suffix_bytes = [0; 8];
361 suffix_bytes[1..8].copy_from_slice(suffix_slice);
362
363 let prefix = Felt::try_from(prefix_slice)
364 .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
365
366 let suffix = Felt::try_from(suffix_bytes.as_slice())
367 .map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?;
368
369 Self::try_from([prefix, suffix])
370 }
371}
372
373impl TryFrom<u128> for AccountIdV0 {
374 type Error = AccountIdError;
375
376 fn try_from(int: u128) -> Result<Self, Self::Error> {
379 let mut bytes: [u8; 15] = [0; 15];
380 bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
381
382 Self::try_from(bytes)
383 }
384}
385
386impl Serializable for AccountIdV0 {
390 fn write_into<W: miden_crypto::utils::ByteWriter>(&self, target: &mut W) {
391 let bytes: [u8; 15] = (*self).into();
392 bytes.write_into(target);
393 }
394
395 fn get_size_hint(&self) -> usize {
396 Self::SERIALIZED_SIZE
397 }
398}
399
400impl Deserializable for AccountIdV0 {
401 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
402 <[u8; 15]>::read_from(source)?
403 .try_into()
404 .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
405 }
406}
407
408fn account_id_from_felts(elements: [Felt; 2]) -> Result<AccountIdV0, AccountIdError> {
418 validate_prefix(elements[0])?;
419 validate_suffix(elements[1])?;
420
421 Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] })
422}
423
424pub(crate) fn validate_prefix(
427 prefix: Felt,
428) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
429 let prefix = prefix.as_int();
430
431 let storage_mode = extract_storage_mode(prefix)?;
433
434 let version = extract_version(prefix)?;
436
437 let account_type = extract_type(prefix);
438
439 Ok((account_type, storage_mode, version))
440}
441
442const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
446 let suffix = suffix.as_int();
447
448 if extract_anchor_epoch(suffix) == u16::MAX {
449 return Err(AccountIdError::AnchorEpochMustNotBeU16Max);
450 }
451
452 if suffix & 0xff != 0 {
454 return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
455 }
456
457 Ok(())
458}
459
460pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
461 let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
462 match bits as u8 {
464 PUBLIC => Ok(AccountStorageMode::Public),
465 NETWORK => Ok(AccountStorageMode::Network),
466 PRIVATE => Ok(AccountStorageMode::Private),
467 _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
468 }
469}
470
471pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
472 let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
475 AccountIdVersion::try_from(version)
476}
477
478pub(crate) const fn extract_type(prefix: u64) -> AccountType {
479 let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
480 match bits as u8 {
482 REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
483 REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
484 FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
485 NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
486 _ => {
487 panic!("type mask contains only 2 bits and we've covered all 4 possible options")
489 },
490 }
491}
492
493const fn extract_anchor_epoch(suffix: u64) -> u16 {
494 ((suffix & AccountIdV0::ANCHOR_EPOCH_MASK) >> AccountIdV0::ANCHOR_EPOCH_SHIFT) as u16
495}
496
497fn shape_suffix(suffix: Felt, anchor_epoch: u16) -> Result<Felt, AccountIdError> {
500 if anchor_epoch == u16::MAX {
501 return Err(AccountIdError::AnchorEpochMustNotBeU16Max);
502 }
503
504 let mut suffix = suffix.as_int();
505
506 suffix &= 0x0000_ffff_ffff_ff00;
508
509 suffix |= (anchor_epoch as u64) << AccountIdV0::ANCHOR_EPOCH_SHIFT;
511
512 Ok(Felt::try_from(suffix).expect("epoch is never all ones so felt should be valid"))
515}
516
517impl PartialOrd for AccountIdV0 {
521 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
522 Some(self.cmp(other))
523 }
524}
525
526impl Ord for AccountIdV0 {
527 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
528 u128::from(*self).cmp(&u128::from(*other))
529 }
530}
531
532impl fmt::Display for AccountIdV0 {
533 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
534 write!(f, "{}", self.to_hex())
535 }
536}
537
538pub(crate) fn compute_digest(
541 seed: Word,
542 code_commitment: Digest,
543 storage_commitment: Digest,
544 anchor_block_commitment: Digest,
545) -> Digest {
546 let mut elements = Vec::with_capacity(16);
547 elements.extend(seed);
548 elements.extend(*code_commitment);
549 elements.extend(*storage_commitment);
550 elements.extend(*anchor_block_commitment);
551 Hasher::hash_elements(&elements)
552}
553
554#[cfg(test)]
558mod tests {
559
560 use super::*;
561 use crate::{
562 account::AccountIdPrefix,
563 testing::account_id::{
564 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_SENDER,
565 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
566 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
567 },
568 };
569
570 #[test]
571 fn test_account_id_from_seed_with_epoch() {
572 let code_commitment: Digest = Digest::default();
573 let storage_commitment: Digest = Digest::default();
574 let anchor_block_commitment: Digest = Digest::default();
575
576 let seed = AccountIdV0::compute_account_seed(
577 [10; 32],
578 AccountType::FungibleFaucet,
579 AccountStorageMode::Public,
580 AccountIdVersion::Version0,
581 code_commitment,
582 storage_commitment,
583 anchor_block_commitment,
584 )
585 .unwrap();
586
587 for anchor_epoch in [0, u16::MAX - 1, 5000] {
588 let anchor = AccountIdAnchor::new_unchecked(anchor_epoch, anchor_block_commitment);
589 let id = AccountIdV0::new(seed, anchor, code_commitment, storage_commitment).unwrap();
590 assert_eq!(id.anchor_epoch(), anchor_epoch, "failed for account ID: {id}");
591 }
592 }
593
594 #[test]
595 fn account_id_from_felts_with_high_pop_count() {
596 let valid_suffix = Felt::try_from(0xfffe_ffff_ffff_ff00u64).unwrap();
597 let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
598
599 let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
600 assert_eq!(id1.account_type(), AccountType::RegularAccountImmutableCode);
601 assert_eq!(id1.storage_mode(), AccountStorageMode::Public);
602 assert_eq!(id1.version(), AccountIdVersion::Version0);
603 assert_eq!(id1.anchor_epoch(), u16::MAX - 1);
604 }
605
606 #[test]
607 fn account_id_construction() {
608 for input in [[0xff; 15], [0; 15]] {
613 for account_type in [
614 AccountType::FungibleFaucet,
615 AccountType::NonFungibleFaucet,
616 AccountType::RegularAccountImmutableCode,
617 AccountType::RegularAccountUpdatableCode,
618 ] {
619 for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
620 let id = AccountIdV0::dummy(input, account_type, storage_mode);
621 assert_eq!(id.account_type(), account_type);
622 assert_eq!(id.storage_mode(), storage_mode);
623 assert_eq!(id.version(), AccountIdVersion::Version0);
624 assert_eq!(id.anchor_epoch(), 0);
625
626 let serialized_id = id.to_bytes();
628 AccountIdV0::read_from_bytes(&serialized_id).unwrap();
629 assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
630 }
631 }
632 }
633 }
634
635 #[test]
636 fn account_id_prefix_serialization_compatibility() {
637 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
639 let id_bytes = account_id.to_bytes();
640 assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
641
642 let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
643 assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
644
645 assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
647 }
648
649 #[test]
653 fn test_account_id_conversion_roundtrip() {
654 for (idx, account_id) in [
655 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
656 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
657 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
658 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
659 ACCOUNT_ID_PRIVATE_SENDER,
660 ]
661 .into_iter()
662 .enumerate()
663 {
664 let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
665 assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
666 assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
667 assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
668 assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
671 assert_eq!(account_id, u128::from(id), "failed in {idx}");
672 }
673 }
674
675 #[test]
676 fn test_account_id_tag_identifiers() {
677 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
678 .expect("valid account ID");
679 assert!(account_id.is_regular_account());
680 assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
681 assert!(account_id.is_public());
682
683 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
684 .expect("valid account ID");
685 assert!(account_id.is_regular_account());
686 assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
687 assert!(!account_id.is_public());
688
689 let account_id =
690 AccountIdV0::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
691 assert!(account_id.is_faucet());
692 assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
693 assert!(account_id.is_public());
694
695 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
696 .expect("valid account ID");
697 assert!(account_id.is_faucet());
698 assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
699 assert!(!account_id.is_public());
700 }
701}