miden_objects/address/
mod.rs1mod r#type;
2
3mod interface;
4use alloc::string::{String, ToString};
5
6use bech32::Bech32m;
7use bech32::primitives::decode::{ByteIter, CheckedHrpstring};
8pub use interface::AddressInterface;
9pub use r#type::AddressType;
10
11use crate::AddressError;
12use crate::account::{AccountId, AccountStorageMode, NetworkId};
13use crate::errors::Bech32Error;
14use crate::note::NoteTag;
15
16#[non_exhaustive]
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub enum Address {
20 AccountId(AccountIdAddress),
21}
22
23impl Address {
24 pub fn to_note_tag(&self) -> NoteTag {
26 match self {
27 Address::AccountId(addr) => addr.to_note_tag(),
28 }
29 }
30
31 pub fn interface(&self) -> AddressInterface {
33 match self {
34 Address::AccountId(account_id_address) => account_id_address.interface(),
35 }
36 }
37
38 pub fn to_bech32(&self, network_id: NetworkId) -> String {
70 match self {
71 Address::AccountId(account_id_address) => account_id_address.to_bech32(network_id),
72 }
73 }
74
75 pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AddressError> {
81 let checked_string = CheckedHrpstring::new::<Bech32m>(bech32_string).map_err(|source| {
84 AddressError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into()))
89 })?;
90
91 let hrp = checked_string.hrp();
92 let network_id = NetworkId::from_hrp(hrp);
93
94 let mut byte_iter = checked_string.byte_iter();
95
96 let address_byte = byte_iter.next().ok_or_else(|| {
99 AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength {
100 expected: 1,
101 actual: byte_iter.len(),
102 })
103 })?;
104
105 let address_type = AddressType::try_from(address_byte)?;
106
107 let address = match address_type {
108 AddressType::AccountId => {
109 AccountIdAddress::from_bech32_byte_iter(byte_iter).map(Address::from)?
110 },
111 };
112
113 Ok((network_id, address))
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
128pub struct AccountIdAddress {
129 id: AccountId,
130 tag_len: u8,
131 interface: AddressInterface,
132}
133
134impl AccountIdAddress {
135 pub const SERIALIZED_SIZE: usize = AccountId::SERIALIZED_SIZE + 2;
140
141 pub fn new(id: AccountId, interface: AddressInterface) -> Self {
149 let tag_len = if id.storage_mode() == AccountStorageMode::Network {
150 NoteTag::DEFAULT_NETWORK_TAG_LENGTH
151 } else {
152 NoteTag::DEFAULT_LOCAL_TAG_LENGTH
153 };
154
155 Self { id, tag_len, interface }
156 }
157
158 pub fn with_tag_len(mut self, tag_len: u8) -> Result<Self, AddressError> {
173 if self.id.storage_mode() == AccountStorageMode::Network {
174 if tag_len != NoteTag::DEFAULT_NETWORK_TAG_LENGTH {
175 return Err(AddressError::CustomTagLengthNotAllowedForNetworkAccounts(tag_len));
176 }
177 } else if tag_len > NoteTag::MAX_LOCAL_TAG_LENGTH {
178 return Err(AddressError::TagLengthTooLarge(tag_len));
179 }
180
181 self.tag_len = tag_len;
182 Ok(self)
183 }
184
185 pub fn id(&self) -> AccountId {
190 self.id
191 }
192
193 pub fn note_tag_len(&self) -> u8 {
198 self.tag_len
199 }
200
201 pub fn interface(&self) -> AddressInterface {
203 self.interface
204 }
205
206 pub fn to_note_tag(&self) -> NoteTag {
208 match self.id.storage_mode() {
209 AccountStorageMode::Network => NoteTag::from_network_account_id(self.id),
210 AccountStorageMode::Private | AccountStorageMode::Public => {
211 NoteTag::from_local_account_id(self.id, self.tag_len)
212 .expect("AccountIdAddress validated that tag len does not exceed MAX_LOCAL_TAG_LENGTH bits")
213 },
214 }
215 }
216
217 fn to_bech32(self, network_id: NetworkId) -> String {
224 let id_bytes: [u8; Self::SERIALIZED_SIZE] = self.into();
225
226 let mut data = [0; Self::SERIALIZED_SIZE + 1];
228 data[0] = AddressType::AccountId as u8;
230 data[1..].copy_from_slice(&id_bytes);
232
233 bech32::encode::<Bech32m>(network_id.into_hrp(), &data)
239 .expect("code length of bech32 should not be exceeded")
240 }
241
242 fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result<Self, AddressError> {
246 if byte_iter.len() != Self::SERIALIZED_SIZE {
249 return Err(AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength {
250 expected: Self::SERIALIZED_SIZE,
251 actual: byte_iter.len(),
252 }));
253 }
254
255 let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE];
258 for (i, byte) in byte_iter.enumerate() {
259 id_bytes[i] = byte;
260 }
261
262 let account_id_address = Self::try_from(id_bytes)?;
263
264 Ok(account_id_address)
265 }
266}
267
268impl From<AccountIdAddress> for Address {
269 fn from(addr: AccountIdAddress) -> Self {
270 Address::AccountId(addr)
271 }
272}
273
274impl From<AccountIdAddress> for [u8; AccountIdAddress::SERIALIZED_SIZE] {
275 fn from(account_id_address: AccountIdAddress) -> Self {
276 let mut result = [0_u8; AccountIdAddress::SERIALIZED_SIZE];
277
278 let encoded_account_id_address = <[u8; 15]>::from(account_id_address.id);
280 result[..15].copy_from_slice(&encoded_account_id_address);
281
282 let interface = account_id_address.interface as u16;
283 debug_assert_eq!(
284 interface >> 11,
285 0,
286 "address interface should have its upper 5 bits unset"
287 );
288
289 let tag_len = (account_id_address.tag_len as u16) << 11;
291 let encoded = tag_len | interface;
292 let encoded: [u8; 2] = encoded.to_be_bytes();
293
294 result[15] = encoded[0];
296 result[16] = encoded[1];
297
298 result
299 }
300}
301
302impl TryFrom<[u8; AccountIdAddress::SERIALIZED_SIZE]> for AccountIdAddress {
303 type Error = AddressError;
304
305 fn try_from(bytes: [u8; AccountIdAddress::SERIALIZED_SIZE]) -> Result<Self, Self::Error> {
306 let account_id_bytes: [u8; AccountId::SERIALIZED_SIZE] = bytes
307 [..AccountId::SERIALIZED_SIZE]
308 .try_into()
309 .expect("we should have sliced off exactly 15 bytes");
310 let account_id =
311 AccountId::try_from(account_id_bytes).map_err(AddressError::AccountIdDecodeError)?;
312
313 let interface_tag_len = u16::from_be_bytes([bytes[15], bytes[16]]);
314 let tag_len = (interface_tag_len >> 11) as u8;
315 let interface = interface_tag_len & 0b0000_0111_1111_1111;
316 let interface = AddressInterface::try_from(interface)?;
317
318 Self::new(account_id, interface).with_tag_len(tag_len)
319 }
320}
321
322#[cfg(test)]
326mod tests {
327 use assert_matches::assert_matches;
328 use bech32::{Bech32, Hrp, NoChecksum};
329
330 use super::*;
331 use crate::account::AccountType;
332 use crate::testing::account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, AccountIdBuilder};
333
334 #[test]
336 fn address_bech32_encode_decode_roundtrip() {
337 let longest_possible_hrp =
340 "01234567890123456789012345678901234567890123456789012345678901234567890123456789012";
341 assert_eq!(longest_possible_hrp.len(), 83);
342
343 let rng = &mut rand::rng();
344 for network_id in [
345 NetworkId::Mainnet,
346 NetworkId::Custom(Hrp::parse("custom").unwrap()),
347 NetworkId::Custom(Hrp::parse(longest_possible_hrp).unwrap()),
348 ] {
349 for (idx, account_id) in [
350 AccountIdBuilder::new()
351 .account_type(AccountType::FungibleFaucet)
352 .build_with_rng(rng),
353 AccountIdBuilder::new()
354 .account_type(AccountType::NonFungibleFaucet)
355 .build_with_rng(rng),
356 AccountIdBuilder::new()
357 .account_type(AccountType::RegularAccountImmutableCode)
358 .build_with_rng(rng),
359 AccountIdBuilder::new()
360 .account_type(AccountType::RegularAccountUpdatableCode)
361 .build_with_rng(rng),
362 ]
363 .into_iter()
364 .enumerate()
365 {
366 let account_id_address =
367 AccountIdAddress::new(account_id, AddressInterface::BasicWallet);
368 let address = Address::from(account_id_address);
369
370 let bech32_string = address.to_bech32(network_id);
371 let (decoded_network_id, decoded_address) =
372 Address::from_bech32(&bech32_string).unwrap();
373
374 assert_eq!(network_id, decoded_network_id, "network id failed in {idx}");
375 assert_eq!(address, decoded_address, "address failed in {idx}");
376
377 let Address::AccountId(decoded_account_id) = address;
378 assert_eq!(account_id, decoded_account_id.id());
379 assert_eq!(account_id_address.note_tag_len(), decoded_account_id.note_tag_len());
380 }
381 }
382 }
383
384 #[test]
386 fn bech32_invalid_checksum() {
387 let network_id = NetworkId::Mainnet;
388 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
389 let address =
390 Address::from(AccountIdAddress::new(account_id, AddressInterface::BasicWallet));
391
392 let bech32_string = address.to_bech32(network_id);
393 let mut invalid_bech32_1 = bech32_string.clone();
394 invalid_bech32_1.remove(0);
395 let mut invalid_bech32_2 = bech32_string.clone();
396 invalid_bech32_2.remove(7);
397
398 let error = Address::from_bech32(&invalid_bech32_1).unwrap_err();
399 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
400
401 let error = Address::from_bech32(&invalid_bech32_2).unwrap_err();
402 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
403 }
404
405 #[test]
407 fn bech32_unknown_address_type() {
408 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
409 let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet);
410 let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec();
411
412 id_address_bytes.insert(0, 250);
414
415 let invalid_bech32 =
416 bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap();
417
418 let error = Address::from_bech32(&invalid_bech32).unwrap_err();
419 assert_matches!(
420 error,
421 AddressError::Bech32DecodeError(Bech32Error::UnknownAddressType(250))
422 );
423 }
424
425 #[test]
427 fn bech32_invalid_other_checksum() {
428 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
429 let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet);
430 let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec();
431 id_address_bytes.insert(0, AddressType::AccountId as u8);
432
433 let invalid_bech32_regular =
435 bech32::encode::<Bech32>(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap();
436 let error = Address::from_bech32(&invalid_bech32_regular).unwrap_err();
437 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
438
439 let invalid_bech32_no_checksum =
441 bech32::encode::<NoChecksum>(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap();
442 let error = Address::from_bech32(&invalid_bech32_no_checksum).unwrap_err();
443 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
444 }
445
446 #[test]
448 fn bech32_invalid_length() {
449 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
450 let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet);
451 let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec();
452 id_address_bytes.insert(0, AddressType::AccountId as u8);
453 id_address_bytes.push(5);
455
456 let invalid_bech32 =
457 bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap();
458
459 let error = Address::from_bech32(&invalid_bech32).unwrap_err();
460 assert_matches!(
461 error,
462 AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength { .. })
463 );
464 }
465}