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, 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().account_type(AccountType::Private).build_with_rng(rng),
240 AccountIdBuilder::new().account_type(AccountType::Public).build_with_rng(rng),
241 ]
242 .into_iter()
243 .enumerate()
244 {
245 let mut address = Address::new(account_id);
247
248 let bech32_string = address.encode(network_id.clone());
249 assert!(
250 !bech32_string.contains(Address::SEPARATOR),
251 "separator should not be present in address without routing params"
252 );
253 let (decoded_network_id, decoded_address) = Address::decode(&bech32_string)?;
254
255 assert_eq!(network_id, decoded_network_id, "network id failed in {idx}");
256 assert_eq!(address, decoded_address, "address failed in {idx}");
257
258 let AddressId::AccountId(decoded_account_id) = address.id();
259 assert_eq!(account_id, decoded_account_id);
260
261 address = address.with_routing_parameters(
263 RoutingParameters::new(AddressInterface::BasicWallet)
264 .with_note_tag_len(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)?,
265 );
266
267 let bech32_string = address.encode(network_id.clone());
268 assert!(
269 bech32_string.contains(Address::SEPARATOR),
270 "separator should be present in address without routing params"
271 );
272 let (decoded_network_id, decoded_address) = Address::decode(&bech32_string)?;
273
274 assert_eq!(network_id, decoded_network_id, "network id failed in {idx}");
275 assert_eq!(address, decoded_address, "address failed in {idx}");
276
277 let AddressId::AccountId(decoded_account_id) = address.id();
278 assert_eq!(account_id, decoded_account_id);
279 }
280 }
281
282 Ok(())
283 }
284
285 #[test]
286 fn address_decoding_fails_on_trailing_separator() -> anyhow::Result<()> {
287 let id = AccountIdBuilder::new().build_with_rng(&mut rand::rng());
288
289 let address = Address::new(id);
290 let mut encoded_address = address.encode(NetworkId::Devnet);
291 encoded_address.push(Address::SEPARATOR);
292
293 let err = Address::decode(&encoded_address).unwrap_err();
294 assert_matches!(err, AddressError::TrailingSeparator);
295
296 Ok(())
297 }
298
299 #[test]
301 fn bech32_invalid_checksum() -> anyhow::Result<()> {
302 let network_id = NetworkId::Mainnet;
303 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?;
304 let address = Address::new(account_id).with_routing_parameters(
305 RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(14)?,
306 );
307
308 let bech32_string = address.encode(network_id);
309 let mut invalid_bech32_1 = bech32_string.clone();
310 invalid_bech32_1.remove(0);
311 let mut invalid_bech32_2 = bech32_string.clone();
312 invalid_bech32_2.remove(7);
313
314 let error = Address::decode(&invalid_bech32_1).unwrap_err();
315 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
316
317 let error = Address::decode(&invalid_bech32_2).unwrap_err();
318 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
319
320 Ok(())
321 }
322
323 #[test]
325 fn bech32_unknown_address_type() {
326 let invalid_bech32_address =
327 bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &[250]).unwrap();
328
329 let error = Address::decode(&invalid_bech32_address).unwrap_err();
330 assert_matches!(
331 error,
332 AddressError::Bech32DecodeError(Bech32Error::UnknownAddressType(250))
333 );
334 }
335
336 #[test]
338 fn bech32_invalid_other_checksum() {
339 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
340 let address_id_bytes = AddressId::from(account_id).to_bytes();
341
342 let invalid_bech32_regular =
344 bech32::encode::<Bech32>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
345 let error = Address::decode(&invalid_bech32_regular).unwrap_err();
346 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
347
348 let invalid_bech32_no_checksum =
350 bech32::encode::<NoChecksum>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
351 let error = Address::decode(&invalid_bech32_no_checksum).unwrap_err();
352 assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
353 }
354
355 #[test]
357 fn bech32_invalid_length() {
358 let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
359 let mut address_id_bytes = AddressId::from(account_id).to_bytes();
360 address_id_bytes.push(5);
362
363 let invalid_bech32 =
364 bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
365
366 let error = Address::decode(&invalid_bech32).unwrap_err();
367 assert_matches!(
368 error,
369 AddressError::AccountIdDecodeError(AccountIdError::Bech32DecodeError(
370 Bech32Error::InvalidDataLength { .. }
371 ))
372 );
373 }
374
375 #[test]
377 fn address_serialization() -> anyhow::Result<()> {
378 let rng = &mut rand::rng();
379
380 for account_type in [AccountType::Private, AccountType::Public].into_iter() {
381 let account_id = AccountIdBuilder::new().account_type(account_type).build_with_rng(rng);
382 let address = Address::new(account_id).with_routing_parameters(
383 RoutingParameters::new(AddressInterface::BasicWallet)
384 .with_note_tag_len(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)?,
385 );
386
387 let serialized = address.to_bytes();
388 let deserialized = Address::read_from_bytes(&serialized)?;
389 assert_eq!(address, deserialized);
390 }
391
392 Ok(())
393 }
394
395 #[test]
397 fn address_with_encryption_key() -> anyhow::Result<()> {
398 use crate::crypto::dsa::eddsa_25519_sha512::KeyExchangeKey;
399 use crate::crypto::ies::{SealingKey, UnsealingKey};
400
401 let rng = &mut rand::rng();
402 let account_id = AccountIdBuilder::new().build_with_rng(rng);
403
404 let secret_key = KeyExchangeKey::with_rng(rng);
406 let public_key = secret_key.public_key();
407 let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key.clone());
408 let unsealing_key = UnsealingKey::X25519XChaCha20Poly1305(secret_key.clone());
409
410 let address = Address::new(account_id).with_routing_parameters(
412 RoutingParameters::new(AddressInterface::BasicWallet)
413 .with_encryption_key(sealing_key.clone()),
414 );
415
416 let retrieved_key =
418 address.encryption_key().expect("encryption key should be present").clone();
419 assert_eq!(retrieved_key, sealing_key);
420
421 let plaintext = b"hello world";
423 let sealed_message =
424 retrieved_key.seal_bytes(rng, plaintext).expect("sealing should succeed");
425 let decrypted =
426 unsealing_key.unseal_bytes(sealed_message).expect("unsealing should succeed");
427 assert_eq!(decrypted.as_slice(), plaintext);
428
429 Ok(())
430 }
431
432 #[test]
434 fn address_encryption_key_encode_decode() -> anyhow::Result<()> {
435 use crate::crypto::dsa::eddsa_25519_sha512::KeyExchangeKey;
436
437 let rng = &mut rand::rng();
438 let account_id =
439 AccountIdBuilder::new().account_type(AccountType::Public).build_with_rng(rng);
440
441 let secret_key = KeyExchangeKey::with_rng(rng);
443 let public_key = secret_key.public_key();
444 let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key);
445
446 let address = Address::new(account_id).with_routing_parameters(
448 RoutingParameters::new(AddressInterface::BasicWallet)
449 .with_encryption_key(sealing_key.clone()),
450 );
451
452 let encoded = address.encode(NetworkId::Mainnet);
454 let (decoded_network, decoded_address) = Address::decode(&encoded)?;
455
456 assert_eq!(decoded_network, NetworkId::Mainnet);
457 assert_eq!(address, decoded_address);
458
459 let decoded_key = decoded_address
461 .encryption_key()
462 .expect("encryption key should be present")
463 .clone();
464 assert_eq!(decoded_key, sealing_key);
465
466 Ok(())
467 }
468
469 #[test]
470 fn address_allows_max_note_tag_len() -> anyhow::Result<()> {
471 let account_id = AccountIdBuilder::new().build_with_rng(&mut rand::rng());
472
473 let address = Address::new(account_id).with_routing_parameters(
474 RoutingParameters::new(AddressInterface::BasicWallet)
475 .with_note_tag_len(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)?,
476 );
477
478 assert_eq!(address.note_tag_len(), NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH);
479
480 Ok(())
481 }
482}