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