miden_protocol/account/account_id/v1/
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::AccountIdPrefixV1;
11
12use crate::account::account_id::NetworkId;
13use crate::account::{AccountIdVersion, AccountType};
14use crate::address::AddressType;
15use crate::errors::{AccountError, AccountIdError, Bech32Error};
16use crate::utils::serde::{
17 ByteReader,
18 ByteWriter,
19 Deserializable,
20 DeserializationError,
21 Serializable,
22};
23use crate::{EMPTY_WORD, Felt, Hasher, Word};
24
25#[derive(Debug, Copy, Clone, Eq, PartialEq)]
32pub struct AccountIdV1 {
33 suffix: Felt,
34 prefix: Felt,
35}
36
37impl Hash for AccountIdV1 {
38 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
39 self.prefix.as_canonical_u64().hash(state);
40 self.suffix.as_canonical_u64().hash(state);
41 }
42}
43
44impl AccountIdV1 {
45 const SERIALIZED_SIZE: usize = 15;
50
51 const VERSION_MASK: u64 = 0b1111;
53
54 pub(crate) const ACCOUNT_TYPE_MASK: u8 = 0b1 << Self::ACCOUNT_TYPE_SHIFT;
57 pub(crate) const ACCOUNT_TYPE_SHIFT: u64 = 4;
58
59 pub(crate) const SEED_DIGEST_SUFFIX_ELEMENT_IDX: usize = 0;
62 pub(crate) const SEED_DIGEST_PREFIX_ELEMENT_IDX: usize = 1;
64
65 pub fn new(
70 seed: Word,
71 code_commitment: Word,
72 storage_commitment: Word,
73 ) -> Result<Self, AccountIdError> {
74 let seed_digest = compute_digest(seed, code_commitment, storage_commitment);
75
76 let mut suffix = seed_digest[Self::SEED_DIGEST_SUFFIX_ELEMENT_IDX];
79 let prefix = seed_digest[Self::SEED_DIGEST_PREFIX_ELEMENT_IDX];
80
81 suffix = shape_suffix(suffix);
82
83 Self::try_from_elements(suffix, prefix)
84 }
85
86 pub fn new_unchecked(elements: [Felt; 2]) -> Self {
88 let prefix = elements[0];
89 let suffix = elements[1];
90
91 if cfg!(debug_assertions) {
93 validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix");
94 validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix");
95 }
96
97 Self { prefix, suffix }
98 }
99
100 pub fn try_from_elements(suffix: Felt, prefix: Felt) -> Result<Self, AccountIdError> {
102 validate_suffix(suffix)?;
103 validate_prefix(prefix)?;
104
105 Ok(AccountIdV1 { suffix, prefix })
106 }
107
108 #[cfg(any(feature = "testing", test))]
110 pub fn dummy(mut bytes: [u8; 15], account_type: AccountType) -> AccountIdV1 {
111 let version = AccountIdVersion::Version1 as u8;
112 let low_nibble = ((account_type as u8) << Self::ACCOUNT_TYPE_SHIFT) | version;
113
114 bytes[7] = low_nibble;
116
117 bytes[3] &= 0b1111_1110;
119
120 let prefix_bytes =
121 bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes");
122 let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes))
123 .expect("should be a valid felt due to the most significant bit being zero");
124
125 let mut suffix_bytes = [0; 8];
126 suffix_bytes[..7].copy_from_slice(&bytes[8..]);
129
130 let suffix = u64::from_be_bytes(suffix_bytes) & 0x7fff_ffff_ffff_ffff;
132 let mut suffix =
133 Felt::try_from(suffix).expect("no bits were set so felt should still be valid");
134
135 suffix = shape_suffix(suffix);
136
137 let account_id = Self::try_from_elements(suffix, prefix)
138 .expect("we should have shaped the felts to produce a valid id");
139
140 debug_assert_eq!(account_id.account_type(), account_type);
141
142 account_id
143 }
144
145 pub fn compute_account_seed(
147 init_seed: [u8; 32],
148 account_type: AccountType,
149 version: AccountIdVersion,
150 code_commitment: Word,
151 storage_commitment: Word,
152 ) -> Result<Word, AccountError> {
153 crate::account::account_id::seed::compute_account_seed(
154 init_seed,
155 account_type,
156 version,
157 code_commitment,
158 storage_commitment,
159 )
160 }
161
162 pub fn account_type(&self) -> AccountType {
167 extract_account_type(self.prefix().as_u64())
168 }
169
170 pub fn is_public(&self) -> bool {
172 self.account_type() == AccountType::Public
173 }
174
175 pub fn version(&self) -> AccountIdVersion {
177 extract_version(self.prefix().as_u64())
178 .expect("account ID should have been constructed with a valid version")
179 }
180
181 pub fn from_hex(hex_str: &str) -> Result<AccountIdV1, AccountIdError> {
183 hex_to_bytes(hex_str)
184 .map_err(AccountIdError::AccountIdHexParseError)
185 .and_then(AccountIdV1::try_from)
186 }
187
188 pub fn to_hex(self) -> String {
190 let mut hex_string =
194 format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_canonical_u64());
195 hex_string.truncate(32);
196 hex_string
197 }
198
199 pub fn to_bech32(&self, network_id: NetworkId) -> String {
201 let id_bytes: [u8; Self::SERIALIZED_SIZE] = (*self).into();
202
203 let mut data = [0; Self::SERIALIZED_SIZE + 1];
204 data[0] = AddressType::AccountId as u8;
205 data[1..16].copy_from_slice(&id_bytes);
206
207 bech32::encode::<Bech32m>(network_id.into_hrp(), &data)
216 .expect("code length of bech32 should not be exceeded")
217 }
218
219 pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AccountIdError> {
221 let checked_string = CheckedHrpstring::new::<Bech32m>(bech32_string).map_err(|source| {
224 AccountIdError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into()))
229 })?;
230
231 let hrp = checked_string.hrp();
232 let network_id = NetworkId::from_hrp(hrp);
233
234 let mut byte_iter = checked_string.byte_iter();
235
236 if byte_iter.len() != Self::SERIALIZED_SIZE + 1 {
238 return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
239 expected: Self::SERIALIZED_SIZE + 1,
240 actual: byte_iter.len(),
241 }));
242 }
243
244 let address_byte = byte_iter.next().expect("there should be at least one byte");
245 if address_byte != AddressType::AccountId as u8 {
246 return Err(AccountIdError::Bech32DecodeError(Bech32Error::UnknownAddressType(
247 address_byte,
248 )));
249 }
250
251 Self::from_bech32_byte_iter(byte_iter).map(|account_id| (network_id, account_id))
252 }
253
254 pub(crate) fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result<Self, AccountIdError> {
256 if byte_iter.len() != Self::SERIALIZED_SIZE {
258 return Err(AccountIdError::Bech32DecodeError(Bech32Error::InvalidDataLength {
259 expected: Self::SERIALIZED_SIZE,
260 actual: byte_iter.len(),
261 }));
262 }
263
264 let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE];
267 for (i, byte) in byte_iter.enumerate() {
268 id_bytes[i] = byte;
269 }
270
271 let account_id = Self::try_from(id_bytes)?;
272
273 Ok(account_id)
274 }
275
276 pub fn prefix(&self) -> AccountIdPrefixV1 {
280 AccountIdPrefixV1::new_unchecked(self.prefix)
283 }
284
285 pub const fn suffix(&self) -> Felt {
287 self.suffix
288 }
289}
290
291impl From<AccountIdV1> for [Felt; 2] {
295 fn from(id: AccountIdV1) -> Self {
296 [id.prefix, id.suffix]
297 }
298}
299
300impl From<AccountIdV1> for [u8; 15] {
301 fn from(id: AccountIdV1) -> Self {
302 let mut result = [0_u8; 15];
303 result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes());
304 result[8..].copy_from_slice(&id.suffix().as_canonical_u64().to_be_bytes()[..7]);
306 result
307 }
308}
309
310impl From<AccountIdV1> for u128 {
311 fn from(id: AccountIdV1) -> Self {
312 let mut le_bytes = [0_u8; 16];
313 le_bytes[..8].copy_from_slice(&id.suffix().as_canonical_u64().to_le_bytes());
314 le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes());
315 u128::from_le_bytes(le_bytes)
316 }
317}
318
319impl TryFrom<[u8; 15]> for AccountIdV1 {
323 type Error = AccountIdError;
324
325 fn try_from(mut bytes: [u8; 15]) -> Result<Self, Self::Error> {
328 bytes[..8].reverse();
331 bytes[8..15].reverse();
333
334 let prefix_slice = &bytes[..8];
335 let suffix_slice = &bytes[8..15];
336
337 let mut suffix_bytes = [0; 8];
340 suffix_bytes[1..8].copy_from_slice(suffix_slice);
341
342 let prefix = Felt::try_from(u64::from_le_bytes(
343 prefix_slice.try_into().expect("prefix slice should be 8 bytes"),
344 ))
345 .map_err(|err| {
346 AccountIdError::AccountIdInvalidPrefixFieldElement(DeserializationError::InvalidValue(
347 err.to_string(),
348 ))
349 })?;
350
351 let suffix = Felt::try_from(u64::from_le_bytes(suffix_bytes)).map_err(|err| {
352 AccountIdError::AccountIdInvalidSuffixFieldElement(DeserializationError::InvalidValue(
353 err.to_string(),
354 ))
355 })?;
356
357 Self::try_from_elements(suffix, prefix)
358 }
359}
360
361impl TryFrom<u128> for AccountIdV1 {
362 type Error = AccountIdError;
363
364 fn try_from(int: u128) -> Result<Self, Self::Error> {
367 let mut bytes: [u8; 15] = [0; 15];
368 bytes.copy_from_slice(&int.to_be_bytes()[0..15]);
369
370 Self::try_from(bytes)
371 }
372}
373
374impl Serializable for AccountIdV1 {
378 fn write_into<W: ByteWriter>(&self, target: &mut W) {
379 let bytes: [u8; 15] = (*self).into();
380 bytes.write_into(target);
381 }
382
383 fn get_size_hint(&self) -> usize {
384 Self::SERIALIZED_SIZE
385 }
386}
387
388impl Deserializable for AccountIdV1 {
389 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
390 <[u8; 15]>::read_from(source)?
391 .try_into()
392 .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string()))
393 }
394}
395
396pub(crate) fn validate_prefix(
401 prefix: Felt,
402) -> Result<(AccountType, AccountIdVersion), AccountIdError> {
403 let prefix = prefix.as_canonical_u64();
404
405 let account_type = extract_account_type(prefix);
406
407 let version = extract_version(prefix)?;
409
410 Ok((account_type, version))
411}
412
413fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> {
417 let suffix = suffix.as_canonical_u64();
418
419 if suffix >> 63 != 0 {
421 return Err(AccountIdError::AccountIdSuffixMostSignificantBitMustBeZero);
422 }
423
424 if suffix & 0xff != 0 {
426 return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero);
427 }
428
429 Ok(())
430}
431
432pub(crate) fn extract_account_type(prefix: u64) -> AccountType {
433 let bits = (prefix & AccountIdV1::ACCOUNT_TYPE_MASK as u64) >> AccountIdV1::ACCOUNT_TYPE_SHIFT;
434 match bits as u8 {
436 AccountType::PRIVATE => AccountType::Private,
437 AccountType::PUBLIC => AccountType::Public,
438 _ => unreachable!("account type mask is 1 bit so every value is covered above"),
439 }
440}
441
442pub(crate) fn extract_version(prefix: u64) -> Result<AccountIdVersion, AccountIdError> {
443 let version = (prefix & AccountIdV1::VERSION_MASK) as u8;
446 AccountIdVersion::try_from(version)
447}
448
449fn shape_suffix(suffix: Felt) -> Felt {
452 let mut suffix = suffix.as_canonical_u64();
453
454 suffix &= 0xffff_ffff_ffff_ff00;
456
457 Felt::try_from(suffix).expect("no bits were set so felt should still be valid")
459}
460
461impl PartialOrd for AccountIdV1 {
465 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
466 Some(self.cmp(other))
467 }
468}
469
470impl Ord for AccountIdV1 {
471 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
472 u128::from(*self).cmp(&u128::from(*other))
473 }
474}
475
476impl fmt::Display for AccountIdV1 {
477 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478 write!(f, "{}", self.to_hex())
479 }
480}
481
482pub(crate) fn compute_digest(seed: Word, code_commitment: Word, storage_commitment: Word) -> Word {
485 let mut elements = Vec::with_capacity(16);
486 elements.extend(seed);
487 elements.extend(*code_commitment);
488 elements.extend(*storage_commitment);
489 elements.extend(EMPTY_WORD);
490 Hasher::hash_elements(&elements)
491}
492
493#[cfg(test)]
497mod tests {
498
499 use rstest::rstest;
500
501 use super::*;
502 use crate::account::AccountIdPrefix;
503 use crate::testing::account_id::{
504 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
505 ACCOUNT_ID_PRIVATE_SENDER,
506 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
507 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
508 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
509 };
510
511 #[test]
512 fn account_id_from_felts_with_max_pop_count() {
513 let valid_suffix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap();
514 let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_fff1u64).unwrap();
515
516 let id1 = AccountIdV1::new_unchecked([valid_prefix, valid_suffix]);
517 assert_eq!(id1.account_type(), AccountType::Public);
518 assert_eq!(id1.version(), AccountIdVersion::Version1);
519 }
520
521 #[rstest]
522 fn account_id_serde_roundtrip(
523 #[values([0xff; 15], [0; 15])] input: [u8; 15],
528 #[values(AccountType::Private, AccountType::Public)] account_type: AccountType,
529 ) {
530 let id = AccountIdV1::dummy(input, account_type);
531 assert_eq!(id.account_type(), account_type);
532 assert_eq!(id.version(), AccountIdVersion::Version1);
533
534 let serialized_id = id.to_bytes();
536 AccountIdV1::read_from_bytes(&serialized_id).unwrap();
537 assert_eq!(serialized_id.len(), AccountIdV1::SERIALIZED_SIZE);
538
539 let serialized_prefix = id.prefix().to_bytes();
540 AccountIdPrefix::read_from_bytes(&serialized_prefix).unwrap();
541 assert_eq!(serialized_prefix.len(), AccountIdPrefix::SERIALIZED_SIZE);
542 }
543
544 #[test]
545 fn account_id_prefix_serialization_compatibility() {
546 let account_id = AccountIdV1::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
548 let id_bytes = account_id.to_bytes();
549 assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]);
550
551 let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap();
552 assert_eq!(AccountIdPrefix::V1(account_id.prefix()), deserialized_prefix);
553
554 assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex()));
556 }
557
558 #[test]
562 fn test_account_id_conversion_roundtrip() {
563 for (idx, account_id) in [
564 ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE,
565 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
566 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
567 ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET,
568 ACCOUNT_ID_PRIVATE_SENDER,
569 ]
570 .into_iter()
571 .enumerate()
572 {
573 let id = AccountIdV1::try_from(account_id).expect("account ID should be valid");
574 assert_eq!(id, AccountIdV1::from_hex(&id.to_hex()).unwrap(), "failed in {idx}");
575 assert_eq!(id, AccountIdV1::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}");
576 assert_eq!(id, AccountIdV1::try_from(u128::from(id)).unwrap(), "failed in {idx}");
577 assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id));
580 assert_eq!(account_id, u128::from(id), "failed in {idx}");
581 }
582 }
583
584 #[test]
585 fn test_account_id_accessors() {
586 let account_id = AccountIdV1::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE)
587 .expect("valid account ID");
588 assert!(account_id.is_public());
589
590 let account_id = AccountIdV1::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE)
591 .expect("valid account ID");
592 assert!(!account_id.is_public());
593
594 let account_id =
595 AccountIdV1::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).expect("valid account ID");
596 assert!(account_id.is_public());
597
598 let account_id = AccountIdV1::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET)
599 .expect("valid account ID");
600 assert!(!account_id.is_public());
601 }
602}