miden_objects/account/account_id/v0/
mod.rs1mod prefix;
2use alloc::{
3 string::{String, ToString},
4 vec::Vec,
5};
6use core::{fmt, hash::Hash};
7
8use bech32::{Bech32m, primitives::decode::CheckedHrpstring};
9use miden_crypto::utils::hex_to_bytes;
10pub use prefix::AccountIdPrefixV0;
11use vm_core::{
12 EMPTY_WORD, Felt, Word,
13 utils::{ByteReader, Deserializable, Serializable},
14};
15use vm_processor::{DeserializationError, Digest};
16
17use crate::{
18 AccountError, Hasher,
19 account::{
20 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 Hash for AccountIdV0 {
47 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
48 self.prefix.inner().hash(state);
49 self.suffix.inner().hash(state);
50 }
51}
52
53impl AccountIdV0 {
54 const SERIALIZED_SIZE: usize = 15;
59
60 pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT;
62 pub(crate) const TYPE_SHIFT: u64 = 4;
63
64 const VERSION_MASK: u64 = 0b1111;
66
67 pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT;
70 pub(crate) const STORAGE_MODE_SHIFT: u64 = 6;
71
72 pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT;
74
75 pub fn new(
80 seed: Word,
81 code_commitment: Digest,
82 storage_commitment: Digest,
83 ) -> Result<Self, AccountIdError> {
84 let seed_digest = compute_digest(seed, code_commitment, storage_commitment);
85
86 let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2]
87 .try_into()
88 .expect("we should have sliced off 2 elements");
89
90 felts[1] = shape_suffix(felts[1]);
91
92 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
137 let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes));
139
140 suffix = Felt::try_from(suffix.as_int() & 0x7fff_ffff_ffff_ffff)
142 .expect("no bits were set so felt should still be valid");
143
144 suffix = shape_suffix(suffix);
145
146 let account_id = account_id_from_felts([prefix, suffix])
147 .expect("we should have shaped the felts to produce a valid id");
148
149 debug_assert_eq!(account_id.account_type(), account_type);
150 debug_assert_eq!(account_id.storage_mode(), storage_mode);
151
152 account_id
153 }
154
155 pub fn compute_account_seed(
157 init_seed: [u8; 32],
158 account_type: AccountType,
159 storage_mode: AccountStorageMode,
160 version: AccountIdVersion,
161 code_commitment: Digest,
162 storage_commitment: Digest,
163 ) -> Result<Word, AccountError> {
164 crate::account::account_id::seed::compute_account_seed(
165 init_seed,
166 account_type,
167 storage_mode,
168 version,
169 code_commitment,
170 storage_commitment,
171 )
172 }
173
174 pub const fn account_type(&self) -> AccountType {
179 extract_type(self.prefix.as_int())
180 }
181
182 pub fn is_faucet(&self) -> bool {
184 self.account_type().is_faucet()
185 }
186
187 pub fn is_regular_account(&self) -> bool {
189 self.account_type().is_regular_account()
190 }
191
192 pub fn storage_mode(&self) -> AccountStorageMode {
194 extract_storage_mode(self.prefix().as_u64())
195 .expect("account ID should have been constructed with a valid storage mode")
196 }
197
198 pub fn is_public(&self) -> bool {
200 self.storage_mode() == AccountStorageMode::Public
201 }
202
203 pub fn version(&self) -> AccountIdVersion {
205 extract_version(self.prefix().as_u64())
206 .expect("account ID should have been constructed with a valid version")
207 }
208
209 pub fn from_hex(hex_str: &str) -> Result<AccountIdV0, AccountIdError> {
211 hex_to_bytes(hex_str)
212 .map_err(AccountIdError::AccountIdHexParseError)
213 .and_then(AccountIdV0::try_from)
214 }
215
216 pub fn to_hex(self) -> String {
218 let mut hex_string =
222 format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_int());
223 hex_string.truncate(32);
224 hex_string
225 }
226
227 pub fn to_bech32(&self, network_id: NetworkId) -> String {
229 let id_bytes: [u8; Self::SERIALIZED_SIZE] = (*self).into();
230
231 let mut data = [0; Self::SERIALIZED_SIZE + 1];
232 data[0] = AddressType::AccountId as u8;
233 data[1..16].copy_from_slice(&id_bytes);
234
235 bech32::encode::<Bech32m>(network_id.into_hrp(), &data)
239 .expect("code length of bech32 should not be exceeded")
240 }
241
242 pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AccountIdError> {
244 let checked_string = CheckedHrpstring::new::<Bech32m>(bech32_string).map_err(|source| {
247 AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into()))
252 })?;
253
254 let hrp = checked_string.hrp();
255 let network_id = NetworkId::from_hrp(hrp);
256
257 let mut byte_iter = checked_string.byte_iter();
258 if byte_iter.len() != Self::SERIALIZED_SIZE + 1 {
260 return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
261 expected: Self::SERIALIZED_SIZE + 1,
262 actual: byte_iter.len(),
263 }));
264 }
265
266 let address_byte = byte_iter.next().expect("there should be at least one byte");
267 if address_byte != AddressType::AccountId as u8 {
268 return Err(AccountIdError::Bech32DecodeError(Bech32Error::UnknownAddressType(
269 address_byte,
270 )));
271 }
272
273 let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE];
276 for (i, byte) in byte_iter.enumerate() {
277 id_bytes[i] = byte;
278 }
279
280 let account_id = Self::try_from(id_bytes)?;
281
282 Ok((network_id, account_id))
283 }
284
285 pub fn prefix(&self) -> AccountIdPrefixV0 {
289 AccountIdPrefixV0::new_unchecked(self.prefix)
292 }
293
294 pub const fn suffix(&self) -> Felt {
296 self.suffix
297 }
298}
299
300impl From<AccountIdV0> for [Felt; 2] {
304 fn from(id: AccountIdV0) -> Self {
305 [id.prefix, id.suffix]
306 }
307}
308
309impl From<AccountIdV0> for [u8; 15] {
310 fn from(id: AccountIdV0) -> Self {
311 let mut result = [0_u8; 15];
312 result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
313 result[8..].copy_from_slice(&id.suffix().as_int().to_be_bytes()[..7]);
315 result
316 }
317}
318
319impl From<AccountIdV0> for u128 {
320 fn from(id: AccountIdV0) -> Self {
321 let mut le_bytes = [0_u8; 16];
322 le_bytes[..8].copy_from_slice(&id.suffix().as_int().to_le_bytes());
323 le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
324 u128::from_le_bytes(le_bytes)
325 }
326}
327
328impl TryFrom<[Felt; 2]> for AccountIdV0 {
332 type Error = AccountIdError;
333
334 fn try_from(elements: [Felt; 2]) -> Result<Self, Self::Error> {
337 account_id_from_felts(elements)
338 }
339}
340
341impl TryFrom<[u8; 15]> for AccountIdV0 {
342 type Error = AccountIdError;
343
344 fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
347 bytes[..8].reverse();
350 bytes[8..15].reverse();
352
353 let prefix_slice = &bytes[..8];
354 let suffix_slice = &bytes[8..15];
355
356 let mut suffix_bytes = [0; 8];
359 suffix_bytes[1..8].copy_from_slice(suffix_slice);
360
361 let prefix = Felt::try_from(prefix_slice)
362 .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
363
364 let suffix = Felt::try_from(suffix_bytes.as_slice())
365 .map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?;
366
367 Self::try_from([prefix, suffix])
368 }
369}
370
371impl TryFrom<u128> for AccountIdV0 {
372 type Error = AccountIdError;
373
374 fn try_from(int: u128) -> Result<Self, Self::Error> {
377 let mut bytes: [u8; 15] = [0; 15];
378 bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
379
380 Self::try_from(bytes)
381 }
382}
383
384impl Serializable for AccountIdV0 {
388 fn write_into<W: miden_crypto::utils::ByteWriter>(&self, target: &mut W) {
389 let bytes: [u8; 15] = (*self).into();
390 bytes.write_into(target);
391 }
392
393 fn get_size_hint(&self) -> usize {
394 Self::SERIALIZED_SIZE
395 }
396}
397
398impl Deserializable for AccountIdV0 {
399 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
400 <[u8; 15]>::read_from(source)?
401 .try_into()
402 .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
403 }
404}
405
406fn account_id_from_felts(elements: [Felt; 2]) -> Result<AccountIdV0, AccountIdError> {
416 validate_prefix(elements[0])?;
417 validate_suffix(elements[1])?;
418
419 Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] })
420}
421
422pub(crate) fn validate_prefix(
425 prefix: Felt,
426) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
427 let prefix = prefix.as_int();
428
429 let storage_mode = extract_storage_mode(prefix)?;
431
432 let version = extract_version(prefix)?;
434
435 let account_type = extract_type(prefix);
436
437 Ok((account_type, storage_mode, version))
438}
439
440const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
444 let suffix = suffix.as_int();
445
446 if suffix >> 63 != 0 {
448 return Err(AccountIdError::AccountIdSuffixMostSignificantBitMustBeZero);
449 }
450
451 if suffix & 0xff != 0 {
453 return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
454 }
455
456 Ok(())
457}
458
459pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
460 let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
461 match bits as u8 {
463 PUBLIC => Ok(AccountStorageMode::Public),
464 NETWORK => Ok(AccountStorageMode::Network),
465 PRIVATE => Ok(AccountStorageMode::Private),
466 _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
467 }
468}
469
470pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
471 let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
474 AccountIdVersion::try_from(version)
475}
476
477pub(crate) const fn extract_type(prefix: u64) -> AccountType {
478 let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
479 match bits as u8 {
481 REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
482 REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
483 FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
484 NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
485 _ => {
486 panic!("type mask contains only 2 bits and we've covered all 4 possible options")
488 },
489 }
490}
491
492fn shape_suffix(suffix: Felt) -> Felt {
495 let mut suffix = suffix.as_int();
496
497 suffix &= 0xffff_ffff_ffff_ff00;
499
500 Felt::try_from(suffix).expect("no bits were set so felt should still be valid")
502}
503
504impl PartialOrd for AccountIdV0 {
508 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
509 Some(self.cmp(other))
510 }
511}
512
513impl Ord for AccountIdV0 {
514 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
515 u128::from(*self).cmp(&u128::from(*other))
516 }
517}
518
519impl fmt::Display for AccountIdV0 {
520 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521 write!(f, "{}", self.to_hex())
522 }
523}
524
525pub(crate) fn compute_digest(
528 seed: Word,
529 code_commitment: Digest,
530 storage_commitment: Digest,
531) -> Digest {
532 let mut elements = Vec::with_capacity(16);
533 elements.extend(seed);
534 elements.extend(*code_commitment);
535 elements.extend(*storage_commitment);
536 elements.extend(EMPTY_WORD);
537 Hasher::hash_elements(&elements)
538}
539
540#[cfg(test)]
544mod tests {
545
546 use super::*;
547 use crate::{
548 account::AccountIdPrefix,
549 testing::account_id::{
550 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET, ACCOUNT_ID_PRIVATE_SENDER,
551 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
552 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
553 },
554 };
555
556 #[test]
557 fn account_id_from_felts_with_max_pop_count() {
558 let valid_suffix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
559 let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff70u64).unwrap();
560
561 let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
562 assert_eq!(id1.account_type(), AccountType::NonFungibleFaucet);
563 assert_eq!(id1.storage_mode(), AccountStorageMode::Network);
564 assert_eq!(id1.version(), AccountIdVersion::Version0);
565 }
566
567 #[test]
568 fn account_id_dummy_construction() {
569 for input in [[0xff; 15], [0; 15]] {
574 for account_type in [
575 AccountType::FungibleFaucet,
576 AccountType::NonFungibleFaucet,
577 AccountType::RegularAccountImmutableCode,
578 AccountType::RegularAccountUpdatableCode,
579 ] {
580 for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
581 let id = AccountIdV0::dummy(input, account_type, storage_mode);
582 assert_eq!(id.account_type(), account_type);
583 assert_eq!(id.storage_mode(), storage_mode);
584 assert_eq!(id.version(), AccountIdVersion::Version0);
585
586 let serialized_id = id.to_bytes();
588 AccountIdV0::read_from_bytes(&serialized_id).unwrap();
589 assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
590 }
591 }
592 }
593 }
594
595 #[test]
596 fn account_id_prefix_serialization_compatibility() {
597 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
599 let id_bytes = account_id.to_bytes();
600 assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
601
602 let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
603 assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
604
605 assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
607 }
608
609 #[test]
613 fn test_account_id_conversion_roundtrip() {
614 for (idx, account_id) in [
615 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
616 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
617 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
618 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
619 ACCOUNT_ID_PRIVATE_SENDER,
620 ]
621 .into_iter()
622 .enumerate()
623 {
624 let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
625 assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
626 assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
627 assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
628 assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
631 assert_eq!(account_id, u128::from(id), "failed in {idx}");
632 }
633 }
634
635 #[test]
636 fn test_account_id_tag_identifiers() {
637 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
638 .expect("valid account ID");
639 assert!(account_id.is_regular_account());
640 assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
641 assert!(account_id.is_public());
642
643 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
644 .expect("valid account ID");
645 assert!(account_id.is_regular_account());
646 assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
647 assert!(!account_id.is_public());
648
649 let account_id =
650 AccountIdV0::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
651 assert!(account_id.is_faucet());
652 assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
653 assert!(account_id.is_public());
654
655 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
656 .expect("valid account ID");
657 assert!(account_id.is_faucet());
658 assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
659 assert!(!account_id.is_public());
660 }
661}