miden_protocol/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::{AccountError, AccountIdError, Bech32Error};
23use crate::utils::serde::{
24 ByteReader,
25 ByteWriter,
26 Deserializable,
27 DeserializationError,
28 Serializable,
29};
30use crate::{EMPTY_WORD, Felt, Hasher, Word};
31
32#[derive(Debug, Copy, Clone, Eq, PartialEq)]
39pub struct AccountIdV0 {
40 suffix: Felt,
41 prefix: Felt,
42}
43
44impl Hash for AccountIdV0 {
45 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
46 self.prefix.as_canonical_u64().hash(state);
47 self.suffix.as_canonical_u64().hash(state);
48 }
49}
50
51impl AccountIdV0 {
52 const SERIALIZED_SIZE: usize = 15;
57
58 pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT;
60 pub(crate) const TYPE_SHIFT: u64 = 4;
61
62 const VERSION_MASK: u64 = 0b1111;
64
65 pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT;
68 pub(crate) const STORAGE_MODE_SHIFT: u64 = 6;
69
70 pub(crate) const SEED_DIGEST_SUFFIX_ELEMENT_IDX: usize = 0;
73 pub(crate) const SEED_DIGEST_PREFIX_ELEMENT_IDX: usize = 1;
75
76 pub fn new(
81 seed: Word,
82 code_commitment: Word,
83 storage_commitment: Word,
84 ) -> Result<Self, AccountIdError> {
85 let seed_digest = compute_digest(seed, code_commitment, storage_commitment);
86
87 let mut suffix = seed_digest[Self::SEED_DIGEST_SUFFIX_ELEMENT_IDX];
90 let prefix = seed_digest[Self::SEED_DIGEST_PREFIX_ELEMENT_IDX];
91
92 suffix = shape_suffix(suffix);
93
94 Self::try_from_elements(suffix, prefix)
95 }
96
97 pub fn new_unchecked(elements: [Felt; 2]) -> Self {
99 let prefix = elements[0];
100 let suffix = elements[1];
101
102 if cfg!(debug_assertions) {
104 validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix");
105 validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix");
106 }
107
108 Self { prefix, suffix }
109 }
110
111 pub fn try_from_elements(suffix: Felt, prefix: Felt) -> Result<Self, AccountIdError> {
113 validate_suffix(suffix)?;
114 validate_prefix(prefix)?;
115
116 Ok(AccountIdV0 { suffix, prefix })
117 }
118
119 #[cfg(any(feature = "testing", test))]
121 pub fn dummy(
122 mut bytes: [u8; 15],
123 account_type: AccountType,
124 storage_mode: AccountStorageMode,
125 ) -> AccountIdV0 {
126 let version = AccountIdVersion::Version0 as u8;
127 let low_nibble = ((storage_mode as u8) << Self::STORAGE_MODE_SHIFT)
128 | ((account_type as u8) << Self::TYPE_SHIFT)
129 | version;
130
131 bytes[7] = low_nibble;
133
134 bytes[3] &= 0b1111_1110;
136
137 let prefix_bytes =
138 bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes");
139 let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes))
140 .expect("should be a valid felt due to the most significant bit being zero");
141
142 let mut suffix_bytes = [0; 8];
143 suffix_bytes[..7].copy_from_slice(&bytes[8..]);
146
147 let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes));
149
150 suffix = Felt::try_from(suffix.as_canonical_u64() & 0x7fff_ffff_ffff_ffff)
152 .expect("no bits were set so felt should still be valid");
153
154 suffix = shape_suffix(suffix);
155
156 let account_id = Self::try_from_elements(suffix, prefix)
157 .expect("we should have shaped the felts to produce a valid id");
158
159 debug_assert_eq!(account_id.account_type(), account_type);
160 debug_assert_eq!(account_id.storage_mode(), storage_mode);
161
162 account_id
163 }
164
165 pub fn compute_account_seed(
167 init_seed: [u8; 32],
168 account_type: AccountType,
169 storage_mode: AccountStorageMode,
170 version: AccountIdVersion,
171 code_commitment: Word,
172 storage_commitment: Word,
173 ) -> Result<Word, AccountError> {
174 crate::account::account_id::seed::compute_account_seed(
175 init_seed,
176 account_type,
177 storage_mode,
178 version,
179 code_commitment,
180 storage_commitment,
181 )
182 }
183
184 pub fn account_type(&self) -> AccountType {
189 extract_type(self.prefix.as_canonical_u64())
190 }
191
192 pub fn is_faucet(&self) -> bool {
194 self.account_type().is_faucet()
195 }
196
197 pub fn is_regular_account(&self) -> bool {
199 self.account_type().is_regular_account()
200 }
201
202 pub fn storage_mode(&self) -> AccountStorageMode {
204 extract_storage_mode(self.prefix().as_u64())
205 .expect("account ID should have been constructed with a valid storage mode")
206 }
207
208 pub fn is_public(&self) -> bool {
210 self.storage_mode() == AccountStorageMode::Public
211 }
212
213 pub fn version(&self) -> AccountIdVersion {
215 extract_version(self.prefix().as_u64())
216 .expect("account ID should have been constructed with a valid version")
217 }
218
219 pub fn from_hex(hex_str: &str) -> Result<AccountIdV0, AccountIdError> {
221 hex_to_bytes(hex_str)
222 .map_err(AccountIdError::AccountIdHexParseError)
223 .and_then(AccountIdV0::try_from)
224 }
225
226 pub fn to_hex(self) -> String {
228 let mut hex_string =
232 format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_canonical_u64());
233 hex_string.truncate(32);
234 hex_string
235 }
236
237 pub fn to_bech32(&self, network_id: NetworkId) -> String {
239 let id_bytes: [u8; Self::SERIALIZED_SIZE] = (*self).into();
240
241 let mut data = [0; Self::SERIALIZED_SIZE + 1];
242 data[0] = AddressType::AccountId as u8;
243 data[1..16].copy_from_slice(&id_bytes);
244
245 bech32::encode::<Bech32m>(network_id.into_hrp(), &data)
254 .expect("code length of bech32 should not be exceeded")
255 }
256
257 pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AccountIdError> {
259 let checked_string = CheckedHrpstring::new::<Bech32m>(bech32_string).map_err(|source| {
262 AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into()))
267 })?;
268
269 let hrp = checked_string.hrp();
270 let network_id = NetworkId::from_hrp(hrp);
271
272 let mut byte_iter = checked_string.byte_iter();
273
274 if byte_iter.len() != Self::SERIALIZED_SIZE + 1 {
276 return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
277 expected: Self::SERIALIZED_SIZE + 1,
278 actual: byte_iter.len(),
279 }));
280 }
281
282 let address_byte = byte_iter.next().expect("there should be at least one byte");
283 if address_byte != AddressType::AccountId as u8 {
284 return Err(AccountIdError::Bech32DecodeError(Bech32Error::UnknownAddressType(
285 address_byte,
286 )));
287 }
288
289 Self::from_bech32_byte_iter(byte_iter).map(|account_id| (network_id, account_id))
290 }
291
292 pub(crate) fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result<Self, AccountIdError> {
294 if byte_iter.len() != Self::SERIALIZED_SIZE {
296 return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
297 expected: Self::SERIALIZED_SIZE,
298 actual: byte_iter.len(),
299 }));
300 }
301
302 let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE];
305 for (i, byte) in byte_iter.enumerate() {
306 id_bytes[i] = byte;
307 }
308
309 let account_id = Self::try_from(id_bytes)?;
310
311 Ok(account_id)
312 }
313
314 pub fn prefix(&self) -> AccountIdPrefixV0 {
318 AccountIdPrefixV0::new_unchecked(self.prefix)
321 }
322
323 pub const fn suffix(&self) -> Felt {
325 self.suffix
326 }
327}
328
329impl From<AccountIdV0> for [Felt; 2] {
333 fn from(id: AccountIdV0) -> Self {
334 [id.prefix, id.suffix]
335 }
336}
337
338impl From<AccountIdV0> for [u8; 15] {
339 fn from(id: AccountIdV0) -> Self {
340 let mut result = [0_u8; 15];
341 result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
342 result[8..].copy_from_slice(&id.suffix().as_canonical_u64().to_be_bytes()[..7]);
344 result
345 }
346}
347
348impl From<AccountIdV0> for u128 {
349 fn from(id: AccountIdV0) -> Self {
350 let mut le_bytes = [0_u8; 16];
351 le_bytes[..8].copy_from_slice(&id.suffix().as_canonical_u64().to_le_bytes());
352 le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
353 u128::from_le_bytes(le_bytes)
354 }
355}
356
357impl TryFrom<[u8; 15]> for AccountIdV0 {
361 type Error = AccountIdError;
362
363 fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
366 bytes[..8].reverse();
369 bytes[8..15].reverse();
371
372 let prefix_slice = &bytes[..8];
373 let suffix_slice = &bytes[8..15];
374
375 let mut suffix_bytes = [0; 8];
378 suffix_bytes[1..8].copy_from_slice(suffix_slice);
379
380 let prefix = Felt::try_from(u64::from_le_bytes(
381 prefix_slice.try_into().expect("prefix slice should be 8 bytes"),
382 ))
383 .map_err(|err| {
384 AccountIdError::AccountIdInvalidPrefixFieldElement(DeserializationError::InvalidValue(
385 err.to_string(),
386 ))
387 })?;
388
389 let suffix = Felt::try_from(u64::from_le_bytes(suffix_bytes)).map_err(|err| {
390 AccountIdError::AccountIdInvalidSuffixFieldElement(DeserializationError::InvalidValue(
391 err.to_string(),
392 ))
393 })?;
394
395 Self::try_from_elements(suffix, prefix)
396 }
397}
398
399impl TryFrom<u128> for AccountIdV0 {
400 type Error = AccountIdError;
401
402 fn try_from(int: u128) -> Result<Self, Self::Error> {
405 let mut bytes: [u8; 15] = [0; 15];
406 bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
407
408 Self::try_from(bytes)
409 }
410}
411
412impl Serializable for AccountIdV0 {
416 fn write_into<W: ByteWriter>(&self, target: &mut W) {
417 let bytes: [u8; 15] = (*self).into();
418 bytes.write_into(target);
419 }
420
421 fn get_size_hint(&self) -> usize {
422 Self::SERIALIZED_SIZE
423 }
424}
425
426impl Deserializable for AccountIdV0 {
427 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
428 <[u8; 15]>::read_from(source)?
429 .try_into()
430 .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
431 }
432}
433
434pub(crate) fn validate_prefix(
440 prefix: Felt,
441) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> {
442 let prefix = prefix.as_canonical_u64();
443
444 let storage_mode = extract_storage_mode(prefix)?;
446
447 let version = extract_version(prefix)?;
449
450 let account_type = extract_type(prefix);
451
452 Ok((account_type, storage_mode, version))
453}
454
455fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
459 let suffix = suffix.as_canonical_u64();
460
461 if suffix >> 63 != 0 {
463 return Err(AccountIdError::AccountIdSuffixMostSignificantBitMustBeZero);
464 }
465
466 if suffix & 0xff != 0 {
468 return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
469 }
470
471 Ok(())
472}
473
474pub(crate) fn extract_storage_mode(prefix: u64) -> Result<AccountStorageMode, AccountIdError> {
475 let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT;
476 match bits as u8 {
478 PUBLIC => Ok(AccountStorageMode::Public),
479 NETWORK => Ok(AccountStorageMode::Network),
480 PRIVATE => Ok(AccountStorageMode::Private),
481 _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())),
482 }
483}
484
485pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
486 let version = (prefix & AccountIdV0::VERSION_MASK) as u8;
489 AccountIdVersion::try_from(version)
490}
491
492pub(crate) const fn extract_type(prefix: u64) -> AccountType {
493 let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT;
494 match bits as u8 {
496 REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode,
497 REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode,
498 FUNGIBLE_FAUCET => AccountType::FungibleFaucet,
499 NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet,
500 _ => {
501 panic!("type mask contains only 2 bits and we've covered all 4 possible options")
503 },
504 }
505}
506
507fn shape_suffix(suffix: Felt) -> Felt {
510 let mut suffix = suffix.as_canonical_u64();
511
512 suffix &= 0xffff_ffff_ffff_ff00;
514
515 Felt::try_from(suffix).expect("no bits were set so felt should still be valid")
517}
518
519impl PartialOrd for AccountIdV0 {
523 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
524 Some(self.cmp(other))
525 }
526}
527
528impl Ord for AccountIdV0 {
529 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
530 u128::from(*self).cmp(&u128::from(*other))
531 }
532}
533
534impl fmt::Display for AccountIdV0 {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 write!(f, "{}", self.to_hex())
537 }
538}
539
540pub(crate) fn compute_digest(seed: Word, code_commitment: Word, storage_commitment: Word) -> Word {
543 let mut elements = Vec::with_capacity(16);
544 elements.extend(seed);
545 elements.extend(*code_commitment);
546 elements.extend(*storage_commitment);
547 elements.extend(EMPTY_WORD);
548 Hasher::hash_elements(&elements)
549}
550
551#[cfg(test)]
555mod tests {
556
557 use super::*;
558 use crate::account::AccountIdPrefix;
559 use crate::testing::account_id::{
560 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
561 ACCOUNT_ID_PRIVATE_SENDER,
562 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
563 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
564 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
565 };
566
567 #[test]
568 fn account_id_from_felts_with_max_pop_count() {
569 let valid_suffix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
570 let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff70u64).unwrap();
571
572 let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]);
573 assert_eq!(id1.account_type(), AccountType::NonFungibleFaucet);
574 assert_eq!(id1.storage_mode(), AccountStorageMode::Network);
575 assert_eq!(id1.version(), AccountIdVersion::Version0);
576 }
577
578 #[test]
579 fn account_id_dummy_construction() {
580 for input in [[0xff; 15], [0; 15]] {
585 for account_type in [
586 AccountType::FungibleFaucet,
587 AccountType::NonFungibleFaucet,
588 AccountType::RegularAccountImmutableCode,
589 AccountType::RegularAccountUpdatableCode,
590 ] {
591 for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] {
592 let id = AccountIdV0::dummy(input, account_type, storage_mode);
593 assert_eq!(id.account_type(), account_type);
594 assert_eq!(id.storage_mode(), storage_mode);
595 assert_eq!(id.version(), AccountIdVersion::Version0);
596
597 let serialized_id = id.to_bytes();
599 AccountIdV0::read_from_bytes(&serialized_id).unwrap();
600 assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE);
601 }
602 }
603 }
604 }
605
606 #[test]
607 fn account_id_prefix_serialization_compatibility() {
608 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
610 let id_bytes = account_id.to_bytes();
611 assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
612
613 let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
614 assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix);
615
616 assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
618 }
619
620 #[test]
624 fn test_account_id_conversion_roundtrip() {
625 for (idx, account_id) in [
626 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
627 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
628 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
629 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
630 ACCOUNT_ID_PRIVATE_SENDER,
631 ]
632 .into_iter()
633 .enumerate()
634 {
635 let id = AccountIdV0::try_from(account_id).expect("account ID should be valid");
636 assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
637 assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
638 assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}");
639 assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
642 assert_eq!(account_id, u128::from(id), "failed in {idx}");
643 }
644 }
645
646 #[test]
647 fn test_account_id_tag_identifiers() {
648 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
649 .expect("valid account ID");
650 assert!(account_id.is_regular_account());
651 assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode);
652 assert!(account_id.is_public());
653
654 let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
655 .expect("valid account ID");
656 assert!(account_id.is_regular_account());
657 assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode);
658 assert!(!account_id.is_public());
659
660 let account_id =
661 AccountIdV0::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
662 assert!(account_id.is_faucet());
663 assert_eq!(account_id.account_type(), AccountType::FungibleFaucet);
664 assert!(account_id.is_public());
665
666 let account_id = AccountIdV0::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
667 .expect("valid account ID");
668 assert!(account_id.is_faucet());
669 assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet);
670 assert!(!account_id.is_public());
671 }
672}