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 bech32::Bech32m;
8use bech32::primitives::decode::{ByteIter, CheckedHrpstring};
9use miden_crypto::utils::hex_to_bytes;
10pub use prefix::AccountIdPrefixV0;
11
12use crate::account::account_id::NetworkId;
13use crate::account::account_id::account_type::{
14 FUNGIBLE_FAUCET,
15 NON_FUNGIBLE_FAUCET,
16 REGULAR_ACCOUNT_IMMUTABLE_CODE,
17 REGULAR_ACCOUNT_UPDATABLE_CODE,
18};
19use crate::account::account_id::storage_mode::{NETWORK, PRIVATE, PUBLIC};
20use crate::account::{AccountIdVersion, AccountStorageMode, AccountType};
21use crate::address::AddressType;
22use crate::errors::{AccountIdError, Bech32Error};
23use crate::utils::{ByteReader, Deserializable, DeserializationError, Serializable};
24use crate::{AccountError, EMPTY_WORD, Felt, Hasher, Word};
25
26#[derive(Debug, Copy, Clone, Eq, PartialEq)]
33pub struct AccountIdV0 {
34 prefix: Felt,
35 suffix: Felt,
36}
37
38impl Hash for AccountIdV0 {
39 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
40 self.prefix.inner().hash(state);
41 self.suffix.inner().hash(state);
42 }
43}
44
45impl AccountIdV0 {
46 const SERIALIZED_SIZE: usize = 15;
51
52 pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT;
54 pub(crate) const TYPE_SHIFT: u64 = 4;
55
56 const VERSION_MASK: u64 = 0b1111;
58
59 pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT;
62 pub(crate) const STORAGE_MODE_SHIFT: u64 = 6;
63
64 pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT;
66
67 pub fn new(
72 seed: Word,
73 code_commitment: Word,
74 storage_commitment: Word,
75 ) -> Result<Self, AccountIdError> {
76 let seed_digest = compute_digest(seed, code_commitment, storage_commitment);
77
78 let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2]
79 .try_into()
80 .expect("we should have sliced off 2 elements");
81
82 felts[1] = shape_suffix(felts[1]);
83
84 account_id_from_felts(felts)
85 }
86
87 pub fn new_unchecked(elements: [Felt; 2]) -> Self {
89 let prefix = elements[0];
90 let suffix = elements[1];
91
92 if cfg!(debug_assertions) {
94 validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix");
95 validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix");
96 }
97
98 Self { prefix, suffix }
99 }
100
101 #[cfg(any(feature = "testing", test))]
103 pub fn dummy(
104 mut bytes: [u8; 15],
105 account_type: AccountType,
106 storage_mode: AccountStorageMode,
107 ) -> AccountIdV0 {
108 let version = AccountIdVersion::Version0 as u8;
109 let low_nibble = ((storage_mode as u8) << Self::STORAGE_MODE_SHIFT)
110 | ((account_type as u8) << Self::TYPE_SHIFT)
111 | version;
112
113 bytes[7] = low_nibble;
115
116 bytes[3] &= 0b1111_1110;
118
119 let prefix_bytes =
120 bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes");
121 let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes))
122 .expect("should be a valid felt due to the most significant bit being zero");
123
124 let mut suffix_bytes = [0; 8];
125 suffix_bytes[..7].copy_from_slice(&bytes[8..]);
128
129 let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes));
131
132 suffix = Felt::try_from(suffix.as_int() & 0x7fff_ffff_ffff_ffff)
134 .expect("no bits were set so felt should still be valid");
135
136 suffix = shape_suffix(suffix);
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: Word,
154 storage_commitment: Word,
155 ) -> Result<Word, AccountError> {
156 crate::account::account_id::seed::compute_account_seed(
157 init_seed,
158 account_type,
159 storage_mode,
160 version,
161 code_commitment,
162 storage_commitment,
163 )
164 }
165
166 pub const fn account_type(&self) -> AccountType {
171 extract_type(self.prefix.as_int())
172 }
173
174 pub fn is_faucet(&self) -> bool {
176 self.account_type().is_faucet()
177 }
178
179 pub fn is_regular_account(&self) -> bool {
181 self.account_type().is_regular_account()
182 }
183
184 pub fn storage_mode(&self) -> AccountStorageMode {
186 extract_storage_mode(self.prefix().as_u64())
187 .expect("account ID should have been constructed with a valid storage mode")
188 }
189
190 pub fn is_public(&self) -> bool {
192 self.storage_mode() == AccountStorageMode::Public
193 }
194
195 pub fn version(&self) -> AccountIdVersion {
197 extract_version(self.prefix().as_u64())
198 .expect("account ID should have been constructed with a valid version")
199 }
200
201 pub fn from_hex(hex_str: &str) -> Result<AccountIdV0, AccountIdError> {
203 hex_to_bytes(hex_str)
204 .map_err(AccountIdError::AccountIdHexParseError)
205 .and_then(AccountIdV0::try_from)
206 }
207
208 pub fn to_hex(self) -> String {
210 let mut hex_string =
214 format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_int());
215 hex_string.truncate(32);
216 hex_string
217 }
218
219 pub fn to_bech32(&self, network_id: NetworkId) -> String {
221 let id_bytes: [u8; Self::SERIALIZED_SIZE] = (*self).into();
222
223 let mut data = [0; Self::SERIALIZED_SIZE + 1];
224 data[0] = AddressType::AccountId as u8;
225 data[1..16].copy_from_slice(&id_bytes);
226
227 bech32::encode::<Bech32m>(network_id.into_hrp(), &data)
236 .expect("code length of bech32 should not be exceeded")
237 }
238
239 pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AccountIdError> {
241 let checked_string = CheckedHrpstring::new::<Bech32m>(bech32_string).map_err(|source| {
244 AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into()))
249 })?;
250
251 let hrp = checked_string.hrp();
252 let network_id = NetworkId::from_hrp(hrp);
253
254 let mut byte_iter = checked_string.byte_iter();
255
256 if byte_iter.len() != Self::SERIALIZED_SIZE + 1 {
258 return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
259 expected: Self::SERIALIZED_SIZE + 1,
260 actual: byte_iter.len(),
261 }));
262 }
263
264 let address_byte = byte_iter.next().expect("there should be at least one byte");
265 if address_byte != AddressType::AccountId as u8 {
266 return Err(AccountIdError::Bech32DecodeError(Bech32Error::UnknownAddressType(
267 address_byte,
268 )));
269 }
270
271 Self::from_bech32_byte_iter(byte_iter).map(|account_id| (network_id, account_id))
272 }
273
274 pub(crate) fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result<Self, AccountIdError> {
276 if byte_iter.len() != Self::SERIALIZED_SIZE {
278 return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
279 expected: Self::SERIALIZED_SIZE,
280 actual: byte_iter.len(),
281 }));
282 }
283
284 let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE];
287 for (i, byte) in byte_iter.enumerate() {
288 id_bytes[i] = byte;
289 }
290
291 let account_id = Self::try_from(id_bytes)?;
292
293 Ok(account_id)
294 }
295
296 pub fn prefix(&self) -> AccountIdPrefixV0 {
300 AccountIdPrefixV0::new_unchecked(self.prefix)
303 }
304
305 pub const fn suffix(&self) -> Felt {
307 self.suffix
308 }
309}
310
311impl From<AccountIdV0> for [Felt; 2] {
315 fn from(id: AccountIdV0) -> Self {
316 [id.prefix, id.suffix]
317 }
318}
319
320impl From<AccountIdV0> for [u8; 15] {
321 fn from(id: AccountIdV0) -> Self {
322 let mut result = [0_u8; 15];
323 result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
324 result[8..].copy_from_slice(&id.suffix().as_int().to_be_bytes()[..7]);
326 result
327 }
328}
329
330impl From<AccountIdV0> for u128 {
331 fn from(id: AccountIdV0) -> Self {
332 let mut le_bytes = [0_u8; 16];
333 le_bytes[..8].copy_from_slice(&id.suffix().as_int().to_le_bytes());
334 le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
335 u128::from_le_bytes(le_bytes)
336 }
337}
338
339impl TryFrom<[Felt; 2]> for AccountIdV0 {
343 type Error = AccountIdError;
344
345 fn try_from(elements: [Felt; 2]) -> Result<Self, Self::Error> {
348 account_id_from_felts(elements)
349 }
350}
351
352impl TryFrom<[u8; 15]> for AccountIdV0 {
353 type Error = AccountIdError;
354
355 fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
358 bytes[..8].reverse();
361 bytes[8..15].reverse();
363
364 let prefix_slice = &bytes[..8];
365 let suffix_slice = &bytes[8..15];
366
367 let mut suffix_bytes = [0; 8];
370 suffix_bytes[1..8].copy_from_slice(suffix_slice);
371
372 let prefix = Felt::try_from(prefix_slice)
373 .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
374
375 let suffix = Felt::try_from(suffix_bytes.as_slice())
376 .map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?;
377
378 Self::try_from([prefix, suffix])
379 }
380}
381
382impl TryFrom<u128> for AccountIdV0 {
383 type Error = AccountIdError;
384
385 fn try_from(int: u128) -> Result<Self, Self::Error> {
388 let mut bytes: [u8; 15] = [0; 15];
389 bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
390
391 Self::try_from(bytes)
392 }
393}
394
395impl Serializable for AccountIdV0 {
399 fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
400 let bytes: [u8; 15] = (*self).into();
401 bytes.write_into(target);
402 }
403
404 fn get_size_hint(&self) -> usize {
405 Self::SERIALIZED_SIZE
406 }
407}
408
409impl Deserializable for AccountIdV0 {
410 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
411 <[u8; 15]>::read_from(source)?
412 .try_into()
413 .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
414 }
415}
416
417fn account_id_from_felts(elements: [Felt; 2]) -> Result<AccountIdV0, AccountIdError> {
427 validate_prefix(elements[0])?;
428 validate_suffix(elements[1])?;
429
430 Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] })
431}
432
433pub(crate) fn validate_prefix(
436 prefix: Felt,
437) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
438 let prefix = prefix.as_int();
439
440 let storage_mode = extract_storage_mode(prefix)?;
442
443 let version = extract_version(prefix)?;
445
446 let account_type = extract_type(prefix);
447
448 Ok((account_type, storage_mode, version))
449}
450
451const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
455 let suffix = suffix.as_int();
456
457 if suffix >> 63 != 0 {
459 return Err(AccountIdError::AccountIdSuffixMostSignificantBitMustBeZero);
460 }
461
462 if suffix & 0xff != 0 {
464 return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
465 }
466
467 Ok(())
468}
469
470pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
471 let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
472 match bits as u8 {
474 PUBLIC => Ok(AccountStorageMode::Public),
475 NETWORK => Ok(AccountStorageMode::Network),
476 PRIVATE => Ok(AccountStorageMode::Private),
477 _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
478 }
479}
480
481pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
482 let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
485 AccountIdVersion::try_from(version)
486}
487
488pub(crate) const fn extract_type(prefix: u64) -> AccountType {
489 let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
490 match bits as u8 {
492 REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
493 REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
494 FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
495 NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
496 _ => {
497 panic!("type mask contains only 2 bits and we've covered all 4 possible options")
499 },
500 }
501}
502
503fn shape_suffix(suffix: Felt) -> Felt {
506 let mut suffix = suffix.as_int();
507
508 suffix &= 0xffff_ffff_ffff_ff00;
510
511 Felt::try_from(suffix).expect("no bits were set so felt should still be valid")
513}
514
515impl PartialOrd for AccountIdV0 {
519 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
520 Some(self.cmp(other))
521 }
522}
523
524impl Ord for AccountIdV0 {
525 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
526 u128::from(*self).cmp(&u128::from(*other))
527 }
528}
529
530impl fmt::Display for AccountIdV0 {
531 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532 write!(f, "{}", self.to_hex())
533 }
534}
535
536pub(crate) fn compute_digest(seed: Word, code_commitment: Word, storage_commitment: Word) -> Word {
539 let mut elements = Vec::with_capacity(16);
540 elements.extend(seed);
541 elements.extend(*code_commitment);
542 elements.extend(*storage_commitment);
543 elements.extend(EMPTY_WORD);
544 Hasher::hash_elements(&elements)
545}
546
547#[cfg(test)]
551mod tests {
552
553 use super::*;
554 use crate::account::AccountIdPrefix;
555 use crate::testing::account_id::{
556 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
557 ACCOUNT_ID_PRIVATE_SENDER,
558 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
559 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
560 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
561 };
562
563 #[test]
564 fn account_id_from_felts_with_max_pop_count() {
565 let valid_suffix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
566 let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff70u64).unwrap();
567
568 let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
569 assert_eq!(id1.account_type(), AccountType::NonFungibleFaucet);
570 assert_eq!(id1.storage_mode(), AccountStorageMode::Network);
571 assert_eq!(id1.version(), AccountIdVersion::Version0);
572 }
573
574 #[test]
575 fn account_id_dummy_construction() {
576 for input in [[0xff; 15], [0; 15]] {
581 for account_type in [
582 AccountType::FungibleFaucet,
583 AccountType::NonFungibleFaucet,
584 AccountType::RegularAccountImmutableCode,
585 AccountType::RegularAccountUpdatableCode,
586 ] {
587 for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
588 let id = AccountIdV0::dummy(input, account_type, storage_mode);
589 assert_eq!(id.account_type(), account_type);
590 assert_eq!(id.storage_mode(), storage_mode);
591 assert_eq!(id.version(), AccountIdVersion::Version0);
592
593 let serialized_id = id.to_bytes();
595 AccountIdV0::read_from_bytes(&serialized_id).unwrap();
596 assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
597 }
598 }
599 }
600 }
601
602 #[test]
603 fn account_id_prefix_serialization_compatibility() {
604 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
606 let id_bytes = account_id.to_bytes();
607 assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
608
609 let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
610 assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
611
612 assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
614 }
615
616 #[test]
620 fn test_account_id_conversion_roundtrip() {
621 for (idx, account_id) in [
622 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
623 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
624 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
625 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
626 ACCOUNT_ID_PRIVATE_SENDER,
627 ]
628 .into_iter()
629 .enumerate()
630 {
631 let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
632 assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
633 assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
634 assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
635 assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
638 assert_eq!(account_id, u128::from(id), "failed in {idx}");
639 }
640 }
641
642 #[test]
643 fn test_account_id_tag_identifiers() {
644 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
645 .expect("valid account ID");
646 assert!(account_id.is_regular_account());
647 assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
648 assert!(account_id.is_public());
649
650 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
651 .expect("valid account ID");
652 assert!(account_id.is_regular_account());
653 assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
654 assert!(!account_id.is_public());
655
656 let account_id =
657 AccountIdV0::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
658 assert!(account_id.is_faucet());
659 assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
660 assert!(account_id.is_public());
661
662 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
663 .expect("valid account ID");
664 assert!(account_id.is_faucet());
665 assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
666 assert!(!account_id.is_public());
667 }
668}