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 miden_crypto::utils::hex_to_bytes;
8pub use prefix::AccountIdPrefixV0;
9
10use crate::account::account_id::account_type::{
11 FUNGIBLE_FAUCET,
12 NON_FUNGIBLE_FAUCET,
13 REGULAR_ACCOUNT_IMMUTABLE_CODE,
14 REGULAR_ACCOUNT_UPDATABLE_CODE,
15};
16use crate::account::account_id::storage_mode::{NETWORK, PRIVATE, PUBLIC};
17use crate::account::{AccountIdVersion, AccountStorageMode, AccountType};
18use crate::errors::AccountIdError;
19use crate::utils::{ByteReader, Deserializable, DeserializationError, Serializable};
20use crate::{AccountError, EMPTY_WORD, Felt, Hasher, Word};
21
22#[derive(Debug, Copy, Clone, Eq, PartialEq)]
29pub struct AccountIdV0 {
30 prefix: Felt,
31 suffix: Felt,
32}
33
34impl Hash for AccountIdV0 {
35 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
36 self.prefix.inner().hash(state);
37 self.suffix.inner().hash(state);
38 }
39}
40
41impl AccountIdV0 {
42 const SERIALIZED_SIZE: usize = 15;
47
48 pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT;
50 pub(crate) const TYPE_SHIFT: u64 = 4;
51
52 const VERSION_MASK: u64 = 0b1111;
54
55 pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT;
58 pub(crate) const STORAGE_MODE_SHIFT: u64 = 6;
59
60 pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT;
62
63 pub fn new(
68 seed: Word,
69 code_commitment: Word,
70 storage_commitment: Word,
71 ) -> Result<Self, AccountIdError> {
72 let seed_digest = compute_digest(seed, code_commitment, storage_commitment);
73
74 let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2]
75 .try_into()
76 .expect("we should have sliced off 2 elements");
77
78 felts[1] = shape_suffix(felts[1]);
79
80 account_id_from_felts(felts)
81 }
82
83 pub fn new_unchecked(elements: [Felt; 2]) -> Self {
85 let prefix = elements[0];
86 let suffix = elements[1];
87
88 if cfg!(debug_assertions) {
90 validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix");
91 validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix");
92 }
93
94 Self { prefix, suffix }
95 }
96
97 #[cfg(any(feature = "testing", test))]
99 pub fn dummy(
100 mut bytes: [u8; 15],
101 account_type: AccountType,
102 storage_mode: AccountStorageMode,
103 ) -> AccountIdV0 {
104 let version = AccountIdVersion::Version0 as u8;
105 let low_nibble = ((storage_mode as u8) << Self::STORAGE_MODE_SHIFT)
106 | ((account_type as u8) << Self::TYPE_SHIFT)
107 | version;
108
109 bytes[7] = low_nibble;
111
112 bytes[3] &= 0b1111_1110;
114
115 let prefix_bytes =
116 bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes");
117 let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes))
118 .expect("should be a valid felt due to the most significant bit being zero");
119
120 let mut suffix_bytes = [0; 8];
121 suffix_bytes[..7].copy_from_slice(&bytes[8..]);
124
125 let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes));
127
128 suffix = Felt::try_from(suffix.as_int() & 0x7fff_ffff_ffff_ffff)
130 .expect("no bits were set so felt should still be valid");
131
132 suffix = shape_suffix(suffix);
133
134 let account_id = account_id_from_felts([prefix, suffix])
135 .expect("we should have shaped the felts to produce a valid id");
136
137 debug_assert_eq!(account_id.account_type(), account_type);
138 debug_assert_eq!(account_id.storage_mode(), storage_mode);
139
140 account_id
141 }
142
143 pub fn compute_account_seed(
145 init_seed: [u8; 32],
146 account_type: AccountType,
147 storage_mode: AccountStorageMode,
148 version: AccountIdVersion,
149 code_commitment: Word,
150 storage_commitment: Word,
151 ) -> Result<Word, AccountError> {
152 crate::account::account_id::seed::compute_account_seed(
153 init_seed,
154 account_type,
155 storage_mode,
156 version,
157 code_commitment,
158 storage_commitment,
159 )
160 }
161
162 pub const fn account_type(&self) -> AccountType {
167 extract_type(self.prefix.as_int())
168 }
169
170 pub fn is_faucet(&self) -> bool {
172 self.account_type().is_faucet()
173 }
174
175 pub fn is_regular_account(&self) -> bool {
177 self.account_type().is_regular_account()
178 }
179
180 pub fn storage_mode(&self) -> AccountStorageMode {
182 extract_storage_mode(self.prefix().as_u64())
183 .expect("account ID should have been constructed with a valid storage mode")
184 }
185
186 pub fn is_public(&self) -> bool {
188 self.storage_mode() == AccountStorageMode::Public
189 }
190
191 pub fn version(&self) -> AccountIdVersion {
193 extract_version(self.prefix().as_u64())
194 .expect("account ID should have been constructed with a valid version")
195 }
196
197 pub fn from_hex(hex_str: &str) -> Result<AccountIdV0, AccountIdError> {
199 hex_to_bytes(hex_str)
200 .map_err(AccountIdError::AccountIdHexParseError)
201 .and_then(AccountIdV0::try_from)
202 }
203
204 pub fn to_hex(self) -> String {
206 let mut hex_string =
210 format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_int());
211 hex_string.truncate(32);
212 hex_string
213 }
214
215 pub fn prefix(&self) -> AccountIdPrefixV0 {
219 AccountIdPrefixV0::new_unchecked(self.prefix)
222 }
223
224 pub const fn suffix(&self) -> Felt {
226 self.suffix
227 }
228}
229
230impl From<AccountIdV0> for [Felt; 2] {
234 fn from(id: AccountIdV0) -> Self {
235 [id.prefix, id.suffix]
236 }
237}
238
239impl From<AccountIdV0> for [u8; 15] {
240 fn from(id: AccountIdV0) -> Self {
241 let mut result = [0_u8; 15];
242 result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
243 result[8..].copy_from_slice(&id.suffix().as_int().to_be_bytes()[..7]);
245 result
246 }
247}
248
249impl From<AccountIdV0> for u128 {
250 fn from(id: AccountIdV0) -> Self {
251 let mut le_bytes = [0_u8; 16];
252 le_bytes[..8].copy_from_slice(&id.suffix().as_int().to_le_bytes());
253 le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
254 u128::from_le_bytes(le_bytes)
255 }
256}
257
258impl TryFrom<[Felt; 2]> for AccountIdV0 {
262 type Error = AccountIdError;
263
264 fn try_from(elements: [Felt; 2]) -> Result<Self, Self::Error> {
267 account_id_from_felts(elements)
268 }
269}
270
271impl TryFrom<[u8; 15]> for AccountIdV0 {
272 type Error = AccountIdError;
273
274 fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
277 bytes[..8].reverse();
280 bytes[8..15].reverse();
282
283 let prefix_slice = &bytes[..8];
284 let suffix_slice = &bytes[8..15];
285
286 let mut suffix_bytes = [0; 8];
289 suffix_bytes[1..8].copy_from_slice(suffix_slice);
290
291 let prefix = Felt::try_from(prefix_slice)
292 .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?;
293
294 let suffix = Felt::try_from(suffix_bytes.as_slice())
295 .map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?;
296
297 Self::try_from([prefix, suffix])
298 }
299}
300
301impl TryFrom<u128> for AccountIdV0 {
302 type Error = AccountIdError;
303
304 fn try_from(int: u128) -> Result<Self, Self::Error> {
307 let mut bytes: [u8; 15] = [0; 15];
308 bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
309
310 Self::try_from(bytes)
311 }
312}
313
314impl Serializable for AccountIdV0 {
318 fn write_into<W: miden_core::utils::ByteWriter>(&self, target: &mut W) {
319 let bytes: [u8; 15] = (*self).into();
320 bytes.write_into(target);
321 }
322
323 fn get_size_hint(&self) -> usize {
324 Self::SERIALIZED_SIZE
325 }
326}
327
328impl Deserializable for AccountIdV0 {
329 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
330 <[u8; 15]>::read_from(source)?
331 .try_into()
332 .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
333 }
334}
335
336fn account_id_from_felts(elements: [Felt; 2]) -> Result<AccountIdV0, AccountIdError> {
346 validate_prefix(elements[0])?;
347 validate_suffix(elements[1])?;
348
349 Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] })
350}
351
352pub(crate) fn validate_prefix(
355 prefix: Felt,
356) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
357 let prefix = prefix.as_int();
358
359 let storage_mode = extract_storage_mode(prefix)?;
361
362 let version = extract_version(prefix)?;
364
365 let account_type = extract_type(prefix);
366
367 Ok((account_type, storage_mode, version))
368}
369
370const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
374 let suffix = suffix.as_int();
375
376 if suffix >> 63 != 0 {
378 return Err(AccountIdError::AccountIdSuffixMostSignificantBitMustBeZero);
379 }
380
381 if suffix & 0xff != 0 {
383 return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
384 }
385
386 Ok(())
387}
388
389pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
390 let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
391 match bits as u8 {
393 PUBLIC => Ok(AccountStorageMode::Public),
394 NETWORK => Ok(AccountStorageMode::Network),
395 PRIVATE => Ok(AccountStorageMode::Private),
396 _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
397 }
398}
399
400pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
401 let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
404 AccountIdVersion::try_from(version)
405}
406
407pub(crate) const fn extract_type(prefix: u64) -> AccountType {
408 let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
409 match bits as u8 {
411 REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
412 REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
413 FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
414 NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
415 _ => {
416 panic!("type mask contains only 2 bits and we've covered all 4 possible options")
418 },
419 }
420}
421
422fn shape_suffix(suffix: Felt) -> Felt {
425 let mut suffix = suffix.as_int();
426
427 suffix &= 0xffff_ffff_ffff_ff00;
429
430 Felt::try_from(suffix).expect("no bits were set so felt should still be valid")
432}
433
434impl PartialOrd for AccountIdV0 {
438 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
439 Some(self.cmp(other))
440 }
441}
442
443impl Ord for AccountIdV0 {
444 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
445 u128::from(*self).cmp(&u128::from(*other))
446 }
447}
448
449impl fmt::Display for AccountIdV0 {
450 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451 write!(f, "{}", self.to_hex())
452 }
453}
454
455pub(crate) fn compute_digest(seed: Word, code_commitment: Word, storage_commitment: Word) -> Word {
458 let mut elements = Vec::with_capacity(16);
459 elements.extend(seed);
460 elements.extend(*code_commitment);
461 elements.extend(*storage_commitment);
462 elements.extend(EMPTY_WORD);
463 Hasher::hash_elements(&elements)
464}
465
466#[cfg(test)]
470mod tests {
471
472 use super::*;
473 use crate::account::AccountIdPrefix;
474 use crate::testing::account_id::{
475 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
476 ACCOUNT_ID_PRIVATE_SENDER,
477 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
478 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
479 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
480 };
481
482 #[test]
483 fn account_id_from_felts_with_max_pop_count() {
484 let valid_suffix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
485 let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff70u64).unwrap();
486
487 let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
488 assert_eq!(id1.account_type(), AccountType::NonFungibleFaucet);
489 assert_eq!(id1.storage_mode(), AccountStorageMode::Network);
490 assert_eq!(id1.version(), AccountIdVersion::Version0);
491 }
492
493 #[test]
494 fn account_id_dummy_construction() {
495 for input in [[0xff; 15], [0; 15]] {
500 for account_type in [
501 AccountType::FungibleFaucet,
502 AccountType::NonFungibleFaucet,
503 AccountType::RegularAccountImmutableCode,
504 AccountType::RegularAccountUpdatableCode,
505 ] {
506 for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
507 let id = AccountIdV0::dummy(input, account_type, storage_mode);
508 assert_eq!(id.account_type(), account_type);
509 assert_eq!(id.storage_mode(), storage_mode);
510 assert_eq!(id.version(), AccountIdVersion::Version0);
511
512 let serialized_id = id.to_bytes();
514 AccountIdV0::read_from_bytes(&serialized_id).unwrap();
515 assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
516 }
517 }
518 }
519 }
520
521 #[test]
522 fn account_id_prefix_serialization_compatibility() {
523 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
525 let id_bytes = account_id.to_bytes();
526 assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
527
528 let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
529 assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
530
531 assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
533 }
534
535 #[test]
539 fn test_account_id_conversion_roundtrip() {
540 for (idx, account_id) in [
541 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
542 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
543 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
544 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
545 ACCOUNT_ID_PRIVATE_SENDER,
546 ]
547 .into_iter()
548 .enumerate()
549 {
550 let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
551 assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
552 assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
553 assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
554 assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
557 assert_eq!(account_id, u128::from(id), "failed in {idx}");
558 }
559 }
560
561 #[test]
562 fn test_account_id_tag_identifiers() {
563 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
564 .expect("valid account ID");
565 assert!(account_id.is_regular_account());
566 assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
567 assert!(account_id.is_public());
568
569 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
570 .expect("valid account ID");
571 assert!(account_id.is_regular_account());
572 assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
573 assert!(!account_id.is_public());
574
575 let account_id =
576 AccountIdV0::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
577 assert!(account_id.is_faucet());
578 assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
579 assert!(account_id.is_public());
580
581 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
582 .expect("valid account ID");
583 assert!(account_id.is_faucet());
584 assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
585 assert!(!account_id.is_public());
586 }
587}