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