1mod prefix;
2use alloc::{
3 string::{String, ToString},
4 vec::Vec,
5};
6use core::fmt;
7
8use miden_crypto::{merkle::LeafIndex, utils::hex_to_bytes};
9pub use prefix::AccountIdPrefixV0;
10use vm_core::{
11 utils::{ByteReader, Deserializable, Serializable},
12 Felt, Word,
13};
14use vm_processor::{DeserializationError, Digest};
15
16use crate::{
17 account::{
18 account_id::{
19 account_type::{
20 FUNGIBLE_FAUCET, NON_FUNGIBLE_FAUCET, REGULAR_ACCOUNT_IMMUTABLE_CODE,
21 REGULAR_ACCOUNT_UPDATABLE_CODE,
22 },
23 storage_mode::{PRIVATE, PUBLIC},
24 },
25 AccountIdAnchor, AccountIdVersion, AccountStorageMode, AccountType,
26 },
27 errors::AccountIdError,
28 AccountError, Hasher, ACCOUNT_TREE_DEPTH,
29};
30
31#[derive(Debug, Copy, Clone, Eq, PartialEq)]
38pub struct AccountIdV0 {
39 prefix: Felt,
40 suffix: Felt,
41}
42
43impl AccountIdV0 {
44 const SERIALIZED_SIZE: usize = 15;
49
50 pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT;
52 pub(crate) const TYPE_SHIFT: u64 = 4;
53
54 const VERSION_MASK: u64 = 0b1111;
56
57 const ANCHOR_EPOCH_MASK: u64 = 0xffff << Self::ANCHOR_EPOCH_SHIFT;
59 const ANCHOR_EPOCH_SHIFT: u64 = 48;
60
61 pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT;
64 pub(crate) const STORAGE_MODE_SHIFT: u64 = 6;
65
66 pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT;
68
69 pub fn new(
74 seed: Word,
75 anchor: AccountIdAnchor,
76 code_commitment: Digest,
77 storage_commitment: Digest,
78 ) -> Result<Self, AccountIdError> {
79 let seed_digest =
80 compute_digest(seed, code_commitment, storage_commitment, anchor.block_hash());
81
82 let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2]
83 .try_into()
84 .expect("we should have sliced off 2 elements");
85
86 felts[1] = shape_suffix(felts[1], anchor.epoch())?;
87
88 account_id_from_felts(felts)
90 }
91
92 pub fn new_unchecked(elements: [Felt; 2]) -> Self {
94 let prefix = elements[0];
95 let suffix = elements[1];
96
97 if cfg!(debug_assertions) {
99 validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix");
100 validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix");
101 }
102
103 Self { prefix, suffix }
104 }
105
106 #[cfg(any(feature = "testing", test))]
108 pub fn dummy(
109 mut bytes: [u8; 15],
110 account_type: AccountType,
111 storage_mode: AccountStorageMode,
112 ) -> AccountIdV0 {
113 let version = AccountIdVersion::Version0 as u8;
114 let low_nibble = (storage_mode as u8) << Self::STORAGE_MODE_SHIFT
115 | (account_type as u8) << Self::TYPE_SHIFT
116 | version;
117
118 bytes[7] = low_nibble;
120
121 bytes[3] &= 0b1111_1110;
123
124 let prefix_bytes =
125 bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes");
126 let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes))
127 .expect("should be a valid felt due to the most significant bit being zero");
128
129 let mut suffix_bytes = [0; 8];
130 suffix_bytes[..7].copy_from_slice(&bytes[8..]);
133 let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes));
135
136 suffix = shape_suffix(suffix, 0).expect("anchor epoch is not u16::MAX");
137
138 let account_id = account_id_from_felts([prefix, suffix])
139 .expect("we should have shaped the felts to produce a valid id");
140
141 debug_assert_eq!(account_id.account_type(), account_type);
142 debug_assert_eq!(account_id.storage_mode(), storage_mode);
143
144 account_id
145 }
146
147 pub fn compute_account_seed(
149 init_seed: [u8; 32],
150 account_type: AccountType,
151 storage_mode: AccountStorageMode,
152 version: AccountIdVersion,
153 code_commitment: Digest,
154 storage_commitment: Digest,
155 anchor_block_hash: Digest,
156 ) -> Result<Word, AccountError> {
157 crate::account::account_id::seed::compute_account_seed(
158 init_seed,
159 account_type,
160 storage_mode,
161 version,
162 code_commitment,
163 storage_commitment,
164 anchor_block_hash,
165 )
166 }
167
168 pub const fn account_type(&self) -> AccountType {
173 extract_type(self.prefix.as_int())
174 }
175
176 pub fn is_faucet(&self) -> bool {
178 self.account_type().is_faucet()
179 }
180
181 pub fn is_regular_account(&self) -> bool {
183 self.account_type().is_regular_account()
184 }
185
186 pub fn storage_mode(&self) -> AccountStorageMode {
188 extract_storage_mode(self.prefix().as_u64())
189 .expect("account ID should have been constructed with a valid storage mode")
190 }
191
192 pub fn is_public(&self) -> bool {
194 self.storage_mode() == AccountStorageMode::Public
195 }
196
197 pub fn version(&self) -> AccountIdVersion {
199 extract_version(self.prefix().as_u64())
200 .expect("account ID should have been constructed with a valid version")
201 }
202
203 pub fn anchor_epoch(&self) -> u16 {
205 extract_anchor_epoch(self.suffix().as_int())
206 }
207
208 pub fn from_hex(hex_str: &str) -> Result<AccountIdV0, AccountIdError> {
210 hex_to_bytes(hex_str)
211 .map_err(AccountIdError::AccountIdHexParseError)
212 .and_then(AccountIdV0::try_from)
213 }
214
215 pub fn to_hex(self) -> String {
217 let mut hex_string =
221 format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_int());
222 hex_string.truncate(32);
223 hex_string
224 }
225
226 pub fn prefix(&self) -> AccountIdPrefixV0 {
230 AccountIdPrefixV0::new_unchecked(self.prefix)
233 }
234
235 pub const fn suffix(&self) -> Felt {
237 self.suffix
238 }
239}
240
241impl From<AccountIdV0> for [Felt; 2] {
245 fn from(id: AccountIdV0) -> Self {
246 [id.prefix, id.suffix]
247 }
248}
249
250impl From<AccountIdV0> for [u8; 15] {
251 fn from(id: AccountIdV0) -> Self {
252 let mut result = [0_u8; 15];
253 result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
254 result[8..].copy_from_slice(&id.suffix().as_int().to_be_bytes()[..7]);
256 result
257 }
258}
259
260impl From<AccountIdV0> for u128 {
261 fn from(id: AccountIdV0) -> Self {
262 let mut le_bytes = [0_u8; 16];
263 le_bytes[..8].copy_from_slice(&id.suffix().as_int().to_le_bytes());
264 le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
265 u128::from_le_bytes(le_bytes)
266 }
267}
268
269impl From<AccountIdV0> for LeafIndex<ACCOUNT_TREE_DEPTH> {
271 fn from(id: AccountIdV0) -> Self {
272 LeafIndex::new_max_depth(id.prefix().as_u64())
273 }
274}
275
276impl TryFrom<[Felt; 2]> for AccountIdV0 {
280 type Error = AccountIdError;
281
282 fn try_from(elements: [Felt; 2]) -> Result<Self, Self::Error> {
285 account_id_from_felts(elements)
286 }
287}
288
289impl TryFrom<[u8; 15]> for AccountIdV0 {
290 type Error = AccountIdError;
291
292 fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
295 bytes[..8].reverse();
298 bytes[8..15].reverse();
300
301 let prefix_slice = &bytes[..8];
302 let suffix_slice = &bytes[8..15];
303
304 let mut suffix_bytes = [0; 8];
307 suffix_bytes[1..8].copy_from_slice(suffix_slice);
308
309 let prefix = Felt::try_from(prefix_slice)
310 .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
311
312 let suffix = Felt::try_from(suffix_bytes.as_slice())
313 .map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?;
314
315 Self::try_from([prefix, suffix])
316 }
317}
318
319impl TryFrom<u128> for AccountIdV0 {
320 type Error = AccountIdError;
321
322 fn try_from(int: u128) -> Result<Self, Self::Error> {
325 let mut bytes: [u8; 15] = [0; 15];
326 bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
327
328 Self::try_from(bytes)
329 }
330}
331
332impl Serializable for AccountIdV0 {
336 fn write_into<W: miden_crypto::utils::ByteWriter>(&self, target: &mut W) {
337 let bytes: [u8; 15] = (*self).into();
338 bytes.write_into(target);
339 }
340
341 fn get_size_hint(&self) -> usize {
342 Self::SERIALIZED_SIZE
343 }
344}
345
346impl Deserializable for AccountIdV0 {
347 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
348 <[u8; 15]>::read_from(source)?
349 .try_into()
350 .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
351 }
352}
353
354fn account_id_from_felts(elements: [Felt; 2]) -> Result<AccountIdV0, AccountIdError> {
364 validate_prefix(elements[0])?;
365 validate_suffix(elements[1])?;
366
367 Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] })
368}
369
370pub(crate) fn validate_prefix(
373 prefix: Felt,
374) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
375 let prefix = prefix.as_int();
376
377 let storage_mode = extract_storage_mode(prefix)?;
379
380 let version = extract_version(prefix)?;
382
383 let account_type = extract_type(prefix);
384
385 Ok((account_type, storage_mode, version))
386}
387
388const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
392 let suffix = suffix.as_int();
393
394 if extract_anchor_epoch(suffix) == u16::MAX {
395 return Err(AccountIdError::AnchorEpochMustNotBeU16Max);
396 }
397
398 if suffix & 0xff != 0 {
400 return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
401 }
402
403 Ok(())
404}
405
406pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
407 let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
408 match bits as u8 {
410 PUBLIC => Ok(AccountStorageMode::Public),
411 PRIVATE => Ok(AccountStorageMode::Private),
412 _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
413 }
414}
415
416pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
417 let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
420 AccountIdVersion::try_from(version)
421}
422
423pub(crate) const fn extract_type(prefix: u64) -> AccountType {
424 let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
425 match bits as u8 {
427 REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
428 REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
429 FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
430 NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
431 _ => {
432 panic!("type mask contains only 2 bits and we've covered all 4 possible options")
434 },
435 }
436}
437
438const fn extract_anchor_epoch(suffix: u64) -> u16 {
439 ((suffix & AccountIdV0::ANCHOR_EPOCH_MASK) >> AccountIdV0::ANCHOR_EPOCH_SHIFT) as u16
440}
441
442fn shape_suffix(suffix: Felt, anchor_epoch: u16) -> Result<Felt, AccountIdError> {
445 if anchor_epoch == u16::MAX {
446 return Err(AccountIdError::AnchorEpochMustNotBeU16Max);
447 }
448
449 let mut suffix = suffix.as_int();
450
451 suffix &= 0x0000_ffff_ffff_ff00;
453
454 suffix |= (anchor_epoch as u64) << AccountIdV0::ANCHOR_EPOCH_SHIFT;
456
457 Ok(Felt::try_from(suffix).expect("epoch is never all ones so felt should be valid"))
460}
461
462impl PartialOrd for AccountIdV0 {
466 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
467 Some(self.cmp(other))
468 }
469}
470
471impl Ord for AccountIdV0 {
472 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
473 u128::from(*self).cmp(&u128::from(*other))
474 }
475}
476
477impl fmt::Display for AccountIdV0 {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 write!(f, "{}", self.to_hex())
480 }
481}
482
483pub(crate) fn compute_digest(
486 seed: Word,
487 code_commitment: Digest,
488 storage_commitment: Digest,
489 anchor_block_hash: Digest,
490) -> Digest {
491 let mut elements = Vec::with_capacity(16);
492 elements.extend(seed);
493 elements.extend(*code_commitment);
494 elements.extend(*storage_commitment);
495 elements.extend(*anchor_block_hash);
496 Hasher::hash_elements(&elements)
497}
498
499#[cfg(test)]
503mod tests {
504
505 use super::*;
506 use crate::{
507 account::AccountIdPrefix,
508 testing::account_id::{
509 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN,
510 ACCOUNT_ID_OFF_CHAIN_SENDER, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN,
511 ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN,
512 },
513 };
514
515 #[test]
516 fn test_account_id_from_seed_with_epoch() {
517 let code_commitment: Digest = Digest::default();
518 let storage_commitment: Digest = Digest::default();
519 let anchor_block_hash: Digest = Digest::default();
520
521 let seed = AccountIdV0::compute_account_seed(
522 [10; 32],
523 AccountType::FungibleFaucet,
524 AccountStorageMode::Public,
525 AccountIdVersion::Version0,
526 code_commitment,
527 storage_commitment,
528 anchor_block_hash,
529 )
530 .unwrap();
531
532 for anchor_epoch in [0, u16::MAX - 1, 5000] {
533 let anchor = AccountIdAnchor::new_unchecked(anchor_epoch, anchor_block_hash);
534 let id = AccountIdV0::new(seed, anchor, code_commitment, storage_commitment).unwrap();
535 assert_eq!(id.anchor_epoch(), anchor_epoch, "failed for account ID: {id}");
536 }
537 }
538
539 #[test]
540 fn account_id_from_felts_with_high_pop_count() {
541 let valid_suffix = Felt::try_from(0xfffe_ffff_ffff_ff00u64).unwrap();
542 let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
543
544 let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
545 assert_eq!(id1.account_type(), AccountType::RegularAccountImmutableCode);
546 assert_eq!(id1.storage_mode(), AccountStorageMode::Public);
547 assert_eq!(id1.version(), AccountIdVersion::Version0);
548 assert_eq!(id1.anchor_epoch(), u16::MAX - 1);
549 }
550
551 #[test]
552 fn account_id_construction() {
553 for input in [[0xff; 15], [0; 15]] {
558 for account_type in [
559 AccountType::FungibleFaucet,
560 AccountType::NonFungibleFaucet,
561 AccountType::RegularAccountImmutableCode,
562 AccountType::RegularAccountUpdatableCode,
563 ] {
564 for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
565 let id = AccountIdV0::dummy(input, account_type, storage_mode);
566 assert_eq!(id.account_type(), account_type);
567 assert_eq!(id.storage_mode(), storage_mode);
568 assert_eq!(id.version(), AccountIdVersion::Version0);
569 assert_eq!(id.anchor_epoch(), 0);
570
571 let serialized_id = id.to_bytes();
573 AccountIdV0::read_from_bytes(&serialized_id).unwrap();
574 assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
575 }
576 }
577 }
578 }
579
580 #[test]
581 fn account_id_prefix_serialization_compatibility() {
582 let account_id = AccountIdV0::try_from(ACCOUNT_ID_OFF_CHAIN_SENDER).unwrap();
584 let id_bytes = account_id.to_bytes();
585 assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
586
587 let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
588 assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
589
590 assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
592 }
593
594 #[test]
598 fn test_account_id_conversion_roundtrip() {
599 for (idx, account_id) in [
600 ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN,
601 ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN,
602 ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN,
603 ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN,
604 ACCOUNT_ID_OFF_CHAIN_SENDER,
605 ]
606 .into_iter()
607 .enumerate()
608 {
609 let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
610 assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
611 assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
612 assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
613 assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
616 assert_eq!(account_id, u128::from(id), "failed in {idx}");
617 }
618 }
619
620 #[test]
621 fn test_account_id_tag_identifiers() {
622 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN)
623 .expect("valid account ID");
624 assert!(account_id.is_regular_account());
625 assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
626 assert!(account_id.is_public());
627
628 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN)
629 .expect("valid account ID");
630 assert!(account_id.is_regular_account());
631 assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
632 assert!(!account_id.is_public());
633
634 let account_id =
635 AccountIdV0::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).expect("valid account ID");
636 assert!(account_id.is_faucet());
637 assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
638 assert!(account_id.is_public());
639
640 let account_id = AccountIdV0::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN)
641 .expect("valid account ID");
642 assert!(account_id.is_faucet());
643 assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
644 assert!(!account_id.is_public());
645 }
646}