miden_protocol/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::account::AccountStorageMode;
20use crate::crypto::ies::SealingKey;
21use crate::errors::AddressError;
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_ACCOUNT_TARGET_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 {
132 self.routing_params
133 .as_ref()
134 .and_then(RoutingParameters::note_tag_len)
135 .unwrap_or(self.id.default_note_tag_len())
136 }
137
138 pub fn to_note_tag(&self) -> NoteTag {
140 let note_tag_len = self.note_tag_len();
141
142 match self.id {
143 AddressId::AccountId(id) => {
144 match id.storage_mode() {
145 AccountStorageMode::Network => NoteTag::from_network_account_id(id),
146 AccountStorageMode::Private | AccountStorageMode::Public => {
147 NoteTag::with_custom_account_target(id, note_tag_len)
148 .expect("address should validate that tag len does not exceed MAX_ACCOUNT_TARGET_TAG_LENGTH bits")
149 }
150 }
151 },
152 }
153 }
154
155 pub fn encryption_key(&self) -> Option<&SealingKey> {
159 self.routing_params.as_ref().and_then(RoutingParameters::encryption_key)
160 }
161
162 pub fn encode(&self, network_id: NetworkId) -> String {
172 let mut encoded = match self.id {
173 AddressId::AccountId(id) => id.to_bech32(network_id),
174 };
175
176 if let Some(routing_params) = &self.routing_params {
177 encoded.push(Self::SEPARATOR);
178 encoded.push_str(&routing_params.encode_to_string());
179 }
180
181 encoded
182 }
183
184 pub fn decode(address_str: &str) -> Result<(NetworkId, Self), AddressError> {
189 if address_str.ends_with(Self::SEPARATOR) {
190 return Err(AddressError::TrailingSeparator);
191 }
192
193 let mut split = address_str.split(Self::SEPARATOR);
194 let encoded_identifier = split
195 .next()
196 .ok_or_else(|| AddressError::decode_error("identifier missing in address string"))?;
197
198 let (network_id, identifier) = AddressId::decode(encoded_identifier)?;
199
200 let mut address = Address::new(identifier);
201
202 if let Some(encoded_routing_params) = split.next() {
203 let routing_params = RoutingParameters::decode(encoded_routing_params.to_owned())?;
204 address = address.with_routing_parameters(routing_params)?;
205 }
206
207 Ok((network_id, address))
208 }
209}
210
211impl Serializable for Address {
212 fn write_into<W: ByteWriter>(&self, target: &mut W) {
213 self.id.write_into(target);
214 self.routing_params.write_into(target);
215 }
216}
217
218impl Deserializable for Address {
219 fn read_from<R: miden_core::utils::ByteReader>(
220 source: &mut R,
221 ) -> Result<Self, DeserializationError> {
222 let identifier: AddressId = source.read()?;
223 let routing_params: Option<RoutingParameters> = source.read()?;
224
225 let mut address = Self::new(identifier);
226
227 if let Some(routing_params) = routing_params {
228 address = address
229 .with_routing_parameters(routing_params)
230 .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
231 }
232
233 Ok(address)
234 }
235}
236
237#[cfg(test)]
241mod tests {
242 use alloc::boxed::Box;
243 use alloc::str::FromStr;
244
245 use assert_matches::assert_matches;
246 use bech32::{Bech32, Bech32m, NoChecksum};
247
248 use super::*;
249 use crate::account::{AccountId, AccountType};
250 use crate::address::CustomNetworkId;
251 use crate::errors::{AccountIdError, 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_ACCOUNT_TARGET_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_ACCOUNT_TARGET_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_sha512::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_sha512::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}