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