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, 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().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                // Encode/Decode without routing parameters should be valid.
246                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                // Encode/Decode with routing parameters should be valid.
262                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    /// Tests that an invalid checksum returns an error.
300    #[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    /// Tests that an unknown address type returns an error.
324    #[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    /// Tests that a bech32 using a disallowed checksum returns an error.
337    #[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        // Use Bech32 instead of Bech32m which is disallowed.
343        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        // Use no checksum instead of Bech32m which is disallowed.
349        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    /// Tests that a bech32 string encoding data of an unexpected length returns an error.
356    #[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        // Add one byte to make the length invalid.
361        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    /// Tests that an Address can be serialized and deserialized
376    #[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    /// Tests that an address with encryption key can be created and used.
396    #[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        // Create keypair using rand::rng()
405        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        // Create address with encryption key
411        let address = Address::new(account_id).with_routing_parameters(
412            RoutingParameters::new(AddressInterface::BasicWallet)
413                .with_encryption_key(sealing_key.clone()),
414        );
415
416        // Verify encryption key is present
417        let retrieved_key =
418            address.encryption_key().expect("encryption key should be present").clone();
419        assert_eq!(retrieved_key, sealing_key);
420
421        // Test seal/unseal round-trip
422        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    /// Tests that an address with encryption key can be encoded/decoded.
433    #[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        // Create keypair
442        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        // Create address with encryption key
447        let address = Address::new(account_id).with_routing_parameters(
448            RoutingParameters::new(AddressInterface::BasicWallet)
449                .with_encryption_key(sealing_key.clone()),
450        );
451
452        // Encode and decode
453        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        // Verify encryption key is preserved
460        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}