miden_objects/address/
mod.rs1mod r#type;
2use alloc::string::ToString;
3
4pub use r#type::AddressType;
5
6mod routing_parameters;
7use alloc::borrow::ToOwned;
8
9pub use routing_parameters::RoutingParameters;
10
11mod interface;
12mod network_id;
13use alloc::string::String;
14
15pub use interface::AddressInterface;
16use miden_processor::DeserializationError;
17pub use network_id::{CustomNetworkId, NetworkId};
18
19use crate::AddressError;
20use crate::account::AccountStorageMode;
21use crate::crypto::ies::SealingKey;
22use crate::note::NoteTag;
23use crate::utils::serde::{ByteWriter, Deserializable, Serializable};
24
25mod address_id;
26pub use address_id::AddressId;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Address {
61 id: AddressId,
62 routing_params: Option<RoutingParameters>,
63}
64
65impl Address {
66 pub const SEPARATOR: char = '_';
71
72 pub fn new(id: impl Into<AddressId>) -> Self {
79 Self { id: id.into(), routing_params: None }
80 }
81
82 pub fn with_routing_parameters(
91 mut self,
92 routing_params: RoutingParameters,
93 ) -> Result<Self, AddressError> {
94 if let Some(tag_len) = routing_params.note_tag_len() {
95 match self.id {
96 AddressId::AccountId(account_id) => {
97 if account_id.storage_mode() == AccountStorageMode::Network
98 && tag_len != NoteTag::DEFAULT_NETWORK_TAG_LENGTH
99 {
100 return Err(AddressError::CustomTagLengthNotAllowedForNetworkAccounts(
101 tag_len,
102 ));
103 }
104 },
105 }
106 }
107
108 self.routing_params = Some(routing_params);
109
110 Ok(self)
111 }
112
113 pub fn id(&self) -> AddressId {
118 self.id
119 }
120
121 pub fn interface(&self) -> Option<AddressInterface> {
123 self.routing_params.as_ref().map(RoutingParameters::interface)
124 }
125
126 pub fn note_tag_len(&self) -> u8 {
131 self.routing_params
132 .as_ref()
133 .and_then(RoutingParameters::note_tag_len)
134 .unwrap_or(self.id.default_note_tag_len())
135 }
136
137 pub fn to_note_tag(&self) -> NoteTag {
139 let note_tag_len = self.note_tag_len();
140
141 match self.id {
142 AddressId::AccountId(id) => {
143 match id.storage_mode() {
144 AccountStorageMode::Network => NoteTag::from_network_account_id(id),
145 AccountStorageMode::Private | AccountStorageMode::Public => {
146 NoteTag::from_local_account_id(id, note_tag_len)
147 .expect("address should validate that tag len does not exceed MAX_LOCAL_TAG_LENGTH bits")
148 }
149 }
150 },
151 }
152 }
153
154 pub fn encryption_key(&self) -> Option<&SealingKey> {
158 self.routing_params.as_ref().and_then(RoutingParameters::encryption_key)
159 }
160
161 pub fn encode(&self, network_id: NetworkId) -> String {
171 let mut encoded = match self.id {
172 AddressId::AccountId(id) => id.to_bech32(network_id),
173 };
174
175 if let Some(routing_params) = &self.routing_params {
176 encoded.push(Self::SEPARATOR);
177 encoded.push_str(&routing_params.encode_to_string());
178 }
179
180 encoded
181 }
182
183 pub fn decode(address_str: &str) -> Result<(NetworkId, Self), AddressError> {
188 if address_str.ends_with(Self::SEPARATOR) {
189 return Err(AddressError::TrailingSeparator);
190 }
191
192 let mut split = address_str.split(Self::SEPARATOR);
193 let encoded_identifier = split
194 .next()
195 .ok_or_else(|| AddressError::decode_error("identifier missing in address string"))?;
196
197 let (network_id, identifier) = AddressId::decode(encoded_identifier)?;
198
199 let mut address = Address::new(identifier);
200
201 if let Some(encoded_routing_params) = split.next() {
202 let routing_params = RoutingParameters::decode(encoded_routing_params.to_owned())?;
203 address = address.with_routing_parameters(routing_params)?;
204 }
205
206 Ok((network_id, address))
207 }
208}
209
210impl Serializable for Address {
211 fn write_into<W: ByteWriter>(&self, target: &mut W) {
212 self.id.write_into(target);
213 self.routing_params.write_into(target);
214 }
215}
216
217impl Deserializable for Address {
218 fn read_from<R: miden_core::utils::ByteReader>(
219 source: &mut R,
220 ) -> Result<Self, DeserializationError> {
221 let identifier: AddressId = source.read()?;
222 let routing_params: Option<RoutingParameters> = source.read()?;
223
224 let mut address = Self::new(identifier);
225
226 if let Some(routing_params) = routing_params {
227 address = address
228 .with_routing_parameters(routing_params)
229 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
230 }
231
232 Ok(address)
233 }
234}
235
236#[cfg(test)]
240mod tests {
241 use alloc::boxed::Box;
242 use alloc::str::FromStr;
243
244 use assert_matches::assert_matches;
245 use bech32::{Bech32, Bech32m, NoChecksum};
246
247 use super::*;
248 use crate::AccountIdError;
249 use crate::account::{AccountId, AccountType};
250 use crate::address::CustomNetworkId;
251 use crate::errors::Bech32Error;
252 use crate::testing::account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, AccountIdBuilder};
253
254 #[test]
256 fn address_encode_decode_roundtrip() -> anyhow::Result<()> {
257 let longest_possible_hrp =
260 "01234567890123456789012345678901234567890123456789012345678901234567890123456789012";
261 assert_eq!(longest_possible_hrp.len(), 83);
262
263 let rng = &mut rand::rng();
264 for network_id in [
265 NetworkId::Mainnet,
266 NetworkId::Custom(Box::new(CustomNetworkId::from_str("custom").unwrap())),
267 NetworkId::Custom(Box::new(CustomNetworkId::from_str(longest_possible_hrp).unwrap())),
268 ] {
269 for (idx, account_id) in [
270 AccountIdBuilder::new()
271 .account_type(AccountType::FungibleFaucet)
272 .build_with_rng(rng),
273 AccountIdBuilder::new()
274 .account_type(AccountType::NonFungibleFaucet)
275 .build_with_rng(rng),
276 AccountIdBuilder::new()
277 .account_type(AccountType::RegularAccountImmutableCode)
278 .build_with_rng(rng),
279 AccountIdBuilder::new()
280 .account_type(AccountType::RegularAccountUpdatableCode)
281 .build_with_rng(rng),
282 ]
283 .into_iter()
284 .enumerate()
285 {
286 let mut address = Address::new(account_id);
288
289 let bech32_string = address.encode(network_id.clone());
290 assert!(
291 !bech32_string.contains(Address::SEPARATOR),
292 "separator should not be present in address without routing params"
293 );
294 let (decoded_network_id, decoded_address) = Address::decode(&bech32_string)?;
295
296 assert_eq!(network_id, decoded_network_id, "network id failed in {idx}");
297 assert_eq!(address, decoded_address, "address failed in {idx}");
298
299 let AddressId::AccountId(decoded_account_id) = address.id();
300 assert_eq!(account_id, decoded_account_id);
301
302 address = address.with_routing_parameters(
304 RoutingParameters::new(AddressInterface::BasicWallet)
305 .with_note_tag_len(NoteTag::DEFAULT_NETWORK_TAG_LENGTH)?,
306 )?;
307
308 let bech32_string = address.encode(network_id.clone());
309 assert!(
310 bech32_string.contains(Address::SEPARATOR),
311 "separator should be present in address without routing params"
312 );
313 let (decoded_network_id, decoded_address) = Address::decode(&bech32_string)?;
314
315 assert_eq!(network_id, decoded_network_id, "network id failed in {idx}");
316 assert_eq!(address, decoded_address, "address failed in {idx}");
317
318 let AddressId::AccountId(decoded_account_id) = address.id();
319 assert_eq!(account_id, decoded_account_id);
320 }
321 }
322
323 Ok(())
324 }
325
326 #[test]
327 fn address_decoding_fails_on_trailing_separator() -> anyhow::Result<()> {
328 let id = AccountIdBuilder::new()
329 .account_type(AccountType::FungibleFaucet)
330 .build_with_rng(&mut rand::rng());
331
332 let address = Address::new(id);
333 let mut encoded_address = address.encode(NetworkId::Devnet);
334 encoded_address.push(Address::SEPARATOR);
335
336 let err = Address::decode(&encoded_address).unwrap_err();
337 assert_matches!(err, AddressError::TrailingSeparator);
338
339 Ok(())
340 }
341
342 #[test]
344 fn bech32_invalid_checksum() -> anyhow::Result<()> {
345 let network_id = NetworkId::Mainnet;
346 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?;
347 let address = Address::new(account_id).with_routing_parameters(
348 RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(14)?,
349 )?;
350
351 let bech32_string = address.encode(network_id);
352 let mut invalid_bech32_1 = bech32_string.clone();
353 invalid_bech32_1.remove(0);
354 let mut invalid_bech32_2 = bech32_string.clone();
355 invalid_bech32_2.remove(7);
356
357 let error = Address::decode(&invalid_bech32_1).unwrap_err();
358 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
359
360 let error = Address::decode(&invalid_bech32_2).unwrap_err();
361 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
362
363 Ok(())
364 }
365
366 #[test]
368 fn bech32_unknown_address_type() {
369 let invalid_bech32_address =
370 bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &[250]).unwrap();
371
372 let error = Address::decode(&invalid_bech32_address).unwrap_err();
373 assert_matches!(
374 error,
375 AddressError::Bech32DecodeError(Bech32Error::UnknownAddressType(250))
376 );
377 }
378
379 #[test]
381 fn bech32_invalid_other_checksum() {
382 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
383 let address_id_bytes = AddressId::from(account_id).to_bytes();
384
385 let invalid_bech32_regular =
387 bech32::encode::<Bech32>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
388 let error = Address::decode(&invalid_bech32_regular).unwrap_err();
389 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
390
391 let invalid_bech32_no_checksum =
393 bech32::encode::<NoChecksum>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
394 let error = Address::decode(&invalid_bech32_no_checksum).unwrap_err();
395 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
396 }
397
398 #[test]
400 fn bech32_invalid_length() {
401 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
402 let mut address_id_bytes = AddressId::from(account_id).to_bytes();
403 address_id_bytes.push(5);
405
406 let invalid_bech32 =
407 bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
408
409 let error = Address::decode(&invalid_bech32).unwrap_err();
410 assert_matches!(
411 error,
412 AddressError::AccountIdDecodeError(AccountIdError::Bech32DecodeError(
413 Bech32Error::InvalidDataLength { .. }
414 ))
415 );
416 }
417
418 #[test]
420 fn address_serialization() -> anyhow::Result<()> {
421 let rng = &mut rand::rng();
422
423 for account_type in [
424 AccountType::FungibleFaucet,
425 AccountType::NonFungibleFaucet,
426 AccountType::RegularAccountImmutableCode,
427 AccountType::RegularAccountUpdatableCode,
428 ]
429 .into_iter()
430 {
431 let account_id = AccountIdBuilder::new().account_type(account_type).build_with_rng(rng);
432 let address = Address::new(account_id).with_routing_parameters(
433 RoutingParameters::new(AddressInterface::BasicWallet)
434 .with_note_tag_len(NoteTag::DEFAULT_NETWORK_TAG_LENGTH)?,
435 )?;
436
437 let serialized = address.to_bytes();
438 let deserialized = Address::read_from_bytes(&serialized)?;
439 assert_eq!(address, deserialized);
440 }
441
442 Ok(())
443 }
444
445 #[test]
447 fn address_with_encryption_key() -> anyhow::Result<()> {
448 use crate::crypto::dsa::eddsa_25519::SecretKey;
449 use crate::crypto::ies::{SealingKey, UnsealingKey};
450
451 let rng = &mut rand::rng();
452 let account_id = AccountIdBuilder::new()
453 .account_type(AccountType::FungibleFaucet)
454 .build_with_rng(rng);
455
456 let secret_key = SecretKey::with_rng(rng);
458 let public_key = secret_key.public_key();
459 let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key.clone());
460 let unsealing_key = UnsealingKey::X25519XChaCha20Poly1305(secret_key.clone());
461
462 let address = Address::new(account_id).with_routing_parameters(
464 RoutingParameters::new(AddressInterface::BasicWallet)
465 .with_encryption_key(sealing_key.clone()),
466 )?;
467
468 let retrieved_key =
470 address.encryption_key().expect("encryption key should be present").clone();
471 assert_eq!(retrieved_key, sealing_key);
472
473 let plaintext = b"hello world";
475 let sealed_message =
476 retrieved_key.seal_bytes(rng, plaintext).expect("sealing should succeed");
477 let decrypted =
478 unsealing_key.unseal_bytes(sealed_message).expect("unsealing should succeed");
479 assert_eq!(decrypted.as_slice(), plaintext);
480
481 Ok(())
482 }
483
484 #[test]
486 fn address_encryption_key_encode_decode() -> anyhow::Result<()> {
487 use crate::crypto::dsa::eddsa_25519::SecretKey;
488
489 let rng = &mut rand::rng();
490 let account_id = AccountIdBuilder::new()
493 .account_type(AccountType::RegularAccountImmutableCode)
494 .storage_mode(AccountStorageMode::Public)
495 .build_with_rng(rng);
496
497 let secret_key = SecretKey::with_rng(rng);
499 let public_key = secret_key.public_key();
500 let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key);
501
502 let address = Address::new(account_id).with_routing_parameters(
504 RoutingParameters::new(AddressInterface::BasicWallet)
505 .with_encryption_key(sealing_key.clone()),
506 )?;
507
508 let encoded = address.encode(NetworkId::Mainnet);
510 let (decoded_network, decoded_address) = Address::decode(&encoded)?;
511
512 assert_eq!(decoded_network, NetworkId::Mainnet);
513 assert_eq!(address, decoded_address);
514
515 let decoded_key = decoded_address
517 .encryption_key()
518 .expect("encryption key should be present")
519 .clone();
520 assert_eq!(decoded_key, sealing_key);
521
522 Ok(())
523 }
524}