Skip to main content

miden_protocol/address/
mod.rs

1mod 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/// A user-facing address in Miden.
32///
33/// An address consists of an [`AddressId`] and optional [`RoutingParameters`].
34///
35/// A user who wants to receive a note creates an address and sends it to the sender of the note.
36/// The sender creates a note intended for the holder of this address ID (e.g., it provides
37/// discoverability and potentially access-control) and the routing parameters inform the sender
38/// about various aspects like:
39/// - what kind of note the receiver's account can consume.
40/// - how the receiver discovers the note.
41/// - how to encrypt the note for the receiver.
42///
43/// It can be encoded to a string using [`Self::encode`] and decoded using [`Self::decode`].
44/// If routing parameters are present, the ID and parameters are separated by
45/// [`Address::SEPARATOR`].
46///
47/// ## Example
48///
49/// ```text
50/// # account ID
51/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq
52/// # account ID + routing parameters (interface & note tag length)
53/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq_qruqqypuyph
54/// # account ID + routing parameters (interface, note tag length, encryption key)
55/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq_qruqqqgqjmsgjsh3687mt2w0qtqunxt3th442j48qwdnezl0fv6qm3x9c8zqsv7pku
56/// ```
57///
58/// The encoding of an address without routing parameters matches the encoding of the underlying
59/// identifier exactly (e.g. an account ID). This provides compatibility between identifiers and
60/// addresses and gives end-users a hint that an address is only an extension of the identifier
61/// (e.g. their account's ID) that they are likely to recognize.
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct Address {
64    id: AddressId,
65    routing_params: Option<RoutingParameters>,
66}
67
68impl Address {
69    // CONSTANTS
70    // --------------------------------------------------------------------------------------------
71
72    /// The separator character in an encoded address between the ID and routing parameters.
73    pub const SEPARATOR: char = '_';
74
75    // CONSTRUCTORS
76    // --------------------------------------------------------------------------------------------
77
78    /// Returns a new address from an [`AddressId`] and routing parameters set to `None`.
79    ///
80    /// To set routing parameters, use [`Self::with_routing_parameters`].
81    pub fn new(id: impl Into<AddressId>) -> Self {
82        Self { id: id.into(), routing_params: None }
83    }
84
85    /// Sets the routing parameters of the address.
86    pub fn with_routing_parameters(mut self, routing_params: RoutingParameters) -> Self {
87        self.routing_params = Some(routing_params);
88        self
89    }
90
91    // ACCESSORS
92    // --------------------------------------------------------------------------------------------
93
94    /// Returns the identifier of the address.
95    pub fn id(&self) -> AddressId {
96        self.id
97    }
98
99    /// Returns the [`AddressInterface`] of the account to which the address points.
100    pub fn interface(&self) -> Option<AddressInterface> {
101        self.routing_params.as_ref().map(RoutingParameters::interface)
102    }
103
104    /// Returns the preferred tag length.
105    ///
106    /// This is guaranteed to be in range `0..=32` (i.e. at most
107    /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH `]).
108    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    /// Returns a note tag derived from this address.
116    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    /// Returns the optional public encryption key from routing parameters.
129    ///
130    /// This key can be used for sealed box encryption when sending notes to this address.
131    pub fn encryption_key(&self) -> Option<&SealingKey> {
132        self.routing_params.as_ref().and_then(RoutingParameters::encryption_key)
133    }
134
135    /// Encodes the [`Address`] into a string.
136    ///
137    /// ## Encoding
138    ///
139    /// The encoding of an address into a string is done as follows:
140    /// - Encode the underlying [`AddressId`] to a bech32 string.
141    /// - If routing parameters are present:
142    ///   - Append the [`Address::SEPARATOR`] to that string.
143    ///   - Append the encoded routing parameters to that string.
144    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    /// Decodes an address string into the [`NetworkId`] and an [`Address`].
158    ///
159    /// See [`Address::encode`] for details on the format. The procedure for decoding the string
160    /// into the address are the inverse operations of encoding.
161    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// TESTS
207// ================================================================================================
208
209#[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    /// Tests that an account ID address can be encoded and decoded.
224    #[test]
225    fn address_encode_decode_roundtrip() -> anyhow::Result<()> {
226        // We use this to check that encoding does not panic even when using the longest possible
227        // HRP.
228        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                // Encode/Decode without routing parameters should be valid.
256                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                // Encode/Decode with routing parameters should be valid.
272                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    /// Tests that an invalid checksum returns an error.
312    #[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    /// Tests that an unknown address type returns an error.
336    #[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    /// Tests that a bech32 using a disallowed checksum returns an error.
349    #[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        // Use Bech32 instead of Bech32m which is disallowed.
355        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        // Use no checksum instead of Bech32m which is disallowed.
361        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    /// Tests that a bech32 string encoding data of an unexpected length returns an error.
368    #[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        // Add one byte to make the length invalid.
373        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    /// Tests that an Address can be serialized and deserialized
388    #[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    /// Tests that an address with encryption key can be created and used.
415    #[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        // Create keypair using rand::rng()
426        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        // Create address with encryption key
432        let address = Address::new(account_id).with_routing_parameters(
433            RoutingParameters::new(AddressInterface::BasicWallet)
434                .with_encryption_key(sealing_key.clone()),
435        );
436
437        // Verify encryption key is present
438        let retrieved_key =
439            address.encryption_key().expect("encryption key should be present").clone();
440        assert_eq!(retrieved_key, sealing_key);
441
442        // Test seal/unseal round-trip
443        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    /// Tests that an address with encryption key can be encoded/decoded.
454    #[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        // Use a local account type (RegularAccountImmutableCode) instead of network
460        // (FungibleFaucet)
461        let account_id = AccountIdBuilder::new()
462            .account_type(AccountType::RegularAccountImmutableCode)
463            .storage_mode(AccountStorageMode::Public)
464            .build_with_rng(rng);
465
466        // Create keypair
467        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        // Create address with encryption key
472        let address = Address::new(account_id).with_routing_parameters(
473            RoutingParameters::new(AddressInterface::BasicWallet)
474                .with_encryption_key(sealing_key.clone()),
475        );
476
477        // Encode and decode
478        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        // Verify encryption key is preserved
485        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}