miden_objects/address/
mod.rs

1mod r#type;
2use alloc::string::ToString;
3
4pub use r#type::AddressType;
5
6mod routing_parameters;
7use alloc::borrow::ToOwned;
8
9pub use routing_parameters::RoutingParameters;
10
11mod interface;
12mod network_id;
13use alloc::string::String;
14
15pub use interface::AddressInterface;
16use miden_processor::DeserializationError;
17pub use network_id::{CustomNetworkId, NetworkId};
18
19use crate::AddressError;
20use crate::account::AccountStorageMode;
21use crate::crypto::ies::SealingKey;
22use crate::note::NoteTag;
23use crate::utils::serde::{ByteWriter, Deserializable, Serializable};
24
25mod address_id;
26pub use address_id::AddressId;
27
28/// A user-facing address in Miden.
29///
30/// An address consists of an [`AddressId`] and optional [`RoutingParameters`].
31///
32/// A user who wants to receive a note creates an address and sends it to the sender of the note.
33/// The sender creates a note intended for the holder of this address ID (e.g., it provides
34/// discoverability and potentially access-control) and the routing parameters inform the sender
35/// about various aspects like:
36/// - what kind of note the receiver's account can consume.
37/// - how the receiver discovers the note.
38/// - how to encrypt the note for the receiver.
39///
40/// It can be encoded to a string using [`Self::encode`] and decoded using [`Self::decode`].
41/// If routing parameters are present, the ID and parameters are separated by
42/// [`Address::SEPARATOR`].
43///
44/// ## Example
45///
46/// ```text
47/// # account ID
48/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq
49/// # account ID + routing parameters (interface & note tag length)
50/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq_qruqqypuyph
51/// # account ID + routing parameters (interface, note tag length, encryption key)
52/// mm1apt3l475qemeqqp57xjycfdwcvw0sfhq_qruqqqgqjmsgjsh3687mt2w0qtqunxt3th442j48qwdnezl0fv6qm3x9c8zqsv7pku
53/// ```
54///
55/// The encoding of an address without routing parameters matches the encoding of the underlying
56/// identifier exactly (e.g. an account ID). This provides compatibility between identifiers and
57/// addresses and gives end-users a hint that an address is only an extension of the identifier
58/// (e.g. their account's ID) that they are likely to recognize.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Address {
61    id: AddressId,
62    routing_params: Option<RoutingParameters>,
63}
64
65impl Address {
66    // CONSTANTS
67    // --------------------------------------------------------------------------------------------
68
69    /// The separator character in an encoded address between the ID and routing parameters.
70    pub const SEPARATOR: char = '_';
71
72    // CONSTRUCTORS
73    // --------------------------------------------------------------------------------------------
74
75    /// Returns a new address from an [`AddressId`] and routing parameters set to `None`.
76    ///
77    /// To set routing parameters, use [`Self::with_routing_parameters`].
78    pub fn new(id: impl Into<AddressId>) -> Self {
79        Self { id: id.into(), routing_params: None }
80    }
81
82    /// For local (both public and private) accounts, up to 30 bits can be encoded into the tag.
83    /// For network accounts, the tag length must be set to 30 bits.
84    ///
85    /// # Errors
86    ///
87    /// Returns an error if:
88    /// - The tag length routing parameter is not [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`] for
89    ///   network accounts.
90    pub fn with_routing_parameters(
91        mut self,
92        routing_params: RoutingParameters,
93    ) -> Result<Self, AddressError> {
94        if let Some(tag_len) = routing_params.note_tag_len() {
95            match self.id {
96                AddressId::AccountId(account_id) => {
97                    if account_id.storage_mode() == AccountStorageMode::Network
98                        && tag_len != NoteTag::DEFAULT_NETWORK_TAG_LENGTH
99                    {
100                        return Err(AddressError::CustomTagLengthNotAllowedForNetworkAccounts(
101                            tag_len,
102                        ));
103                    }
104                },
105            }
106        }
107
108        self.routing_params = Some(routing_params);
109
110        Ok(self)
111    }
112
113    // ACCESSORS
114    // --------------------------------------------------------------------------------------------
115
116    /// Returns the identifier of the address.
117    pub fn id(&self) -> AddressId {
118        self.id
119    }
120
121    /// Returns the [`AddressInterface`] of the account to which the address points.
122    pub fn interface(&self) -> Option<AddressInterface> {
123        self.routing_params.as_ref().map(RoutingParameters::interface)
124    }
125
126    /// Returns the preferred tag length.
127    ///
128    /// This is guaranteed to be in range `0..=30` (e.g. the maximum of
129    /// [`NoteTag::MAX_LOCAL_TAG_LENGTH`] and [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`]).
130    pub fn note_tag_len(&self) -> u8 {
131        self.routing_params
132            .as_ref()
133            .and_then(RoutingParameters::note_tag_len)
134            .unwrap_or(self.id.default_note_tag_len())
135    }
136
137    /// Returns a note tag derived from this address.
138    pub fn to_note_tag(&self) -> NoteTag {
139        let note_tag_len = self.note_tag_len();
140
141        match self.id {
142            AddressId::AccountId(id) => {
143                match id.storage_mode() {
144                  AccountStorageMode::Network => NoteTag::from_network_account_id(id),
145                  AccountStorageMode::Private | AccountStorageMode::Public => {
146                      NoteTag::from_local_account_id(id, note_tag_len)
147                          .expect("address should validate that tag len does not exceed MAX_LOCAL_TAG_LENGTH bits")
148                    }
149                }
150            },
151        }
152    }
153
154    /// Returns the optional public encryption key from routing parameters.
155    ///
156    /// This key can be used for sealed box encryption when sending notes to this address.
157    pub fn encryption_key(&self) -> Option<&SealingKey> {
158        self.routing_params.as_ref().and_then(RoutingParameters::encryption_key)
159    }
160
161    /// Encodes the [`Address`] into a string.
162    ///
163    /// ## Encoding
164    ///
165    /// The encoding of an address into a string is done as follows:
166    /// - Encode the underlying [`AddressId`] to a bech32 string.
167    /// - If routing parameters are present:
168    ///   - Append the [`Address::SEPARATOR`] to that string.
169    ///   - Append the encoded routing parameters to that string.
170    pub fn encode(&self, network_id: NetworkId) -> String {
171        let mut encoded = match self.id {
172            AddressId::AccountId(id) => id.to_bech32(network_id),
173        };
174
175        if let Some(routing_params) = &self.routing_params {
176            encoded.push(Self::SEPARATOR);
177            encoded.push_str(&routing_params.encode_to_string());
178        }
179
180        encoded
181    }
182
183    /// Decodes an address string into the [`NetworkId`] and an [`Address`].
184    ///
185    /// See [`Address::encode`] for details on the format. The procedure for decoding the string
186    /// into the address are the inverse operations of encoding.
187    pub fn decode(address_str: &str) -> Result<(NetworkId, Self), AddressError> {
188        if address_str.ends_with(Self::SEPARATOR) {
189            return Err(AddressError::TrailingSeparator);
190        }
191
192        let mut split = address_str.split(Self::SEPARATOR);
193        let encoded_identifier = split
194            .next()
195            .ok_or_else(|| AddressError::decode_error("identifier missing in address string"))?;
196
197        let (network_id, identifier) = AddressId::decode(encoded_identifier)?;
198
199        let mut address = Address::new(identifier);
200
201        if let Some(encoded_routing_params) = split.next() {
202            let routing_params = RoutingParameters::decode(encoded_routing_params.to_owned())?;
203            address = address.with_routing_parameters(routing_params)?;
204        }
205
206        Ok((network_id, address))
207    }
208}
209
210impl Serializable for Address {
211    fn write_into<W: ByteWriter>(&self, target: &mut W) {
212        self.id.write_into(target);
213        self.routing_params.write_into(target);
214    }
215}
216
217impl Deserializable for Address {
218    fn read_from<R: miden_core::utils::ByteReader>(
219        source: &mut R,
220    ) -> Result<Self, DeserializationError> {
221        let identifier: AddressId = source.read()?;
222        let routing_params: Option<RoutingParameters> = source.read()?;
223
224        let mut address = Self::new(identifier);
225
226        if let Some(routing_params) = routing_params {
227            address = address
228                .with_routing_parameters(routing_params)
229                .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
230        }
231
232        Ok(address)
233    }
234}
235
236// TESTS
237// ================================================================================================
238
239#[cfg(test)]
240mod tests {
241    use alloc::boxed::Box;
242    use alloc::str::FromStr;
243
244    use assert_matches::assert_matches;
245    use bech32::{Bech32, Bech32m, NoChecksum};
246
247    use super::*;
248    use crate::AccountIdError;
249    use crate::account::{AccountId, AccountType};
250    use crate::address::CustomNetworkId;
251    use crate::errors::Bech32Error;
252    use crate::testing::account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, AccountIdBuilder};
253
254    /// Tests that an account ID address can be encoded and decoded.
255    #[test]
256    fn address_encode_decode_roundtrip() -> anyhow::Result<()> {
257        // We use this to check that encoding does not panic even when using the longest possible
258        // HRP.
259        let longest_possible_hrp =
260            "01234567890123456789012345678901234567890123456789012345678901234567890123456789012";
261        assert_eq!(longest_possible_hrp.len(), 83);
262
263        let rng = &mut rand::rng();
264        for network_id in [
265            NetworkId::Mainnet,
266            NetworkId::Custom(Box::new(CustomNetworkId::from_str("custom").unwrap())),
267            NetworkId::Custom(Box::new(CustomNetworkId::from_str(longest_possible_hrp).unwrap())),
268        ] {
269            for (idx, account_id) in [
270                AccountIdBuilder::new()
271                    .account_type(AccountType::FungibleFaucet)
272                    .build_with_rng(rng),
273                AccountIdBuilder::new()
274                    .account_type(AccountType::NonFungibleFaucet)
275                    .build_with_rng(rng),
276                AccountIdBuilder::new()
277                    .account_type(AccountType::RegularAccountImmutableCode)
278                    .build_with_rng(rng),
279                AccountIdBuilder::new()
280                    .account_type(AccountType::RegularAccountUpdatableCode)
281                    .build_with_rng(rng),
282            ]
283            .into_iter()
284            .enumerate()
285            {
286                // Encode/Decode without routing parameters should be valid.
287                let mut address = Address::new(account_id);
288
289                let bech32_string = address.encode(network_id.clone());
290                assert!(
291                    !bech32_string.contains(Address::SEPARATOR),
292                    "separator should not be present in address without routing params"
293                );
294                let (decoded_network_id, decoded_address) = Address::decode(&bech32_string)?;
295
296                assert_eq!(network_id, decoded_network_id, "network id failed in {idx}");
297                assert_eq!(address, decoded_address, "address failed in {idx}");
298
299                let AddressId::AccountId(decoded_account_id) = address.id();
300                assert_eq!(account_id, decoded_account_id);
301
302                // Encode/Decode with routing parameters should be valid.
303                address = address.with_routing_parameters(
304                    RoutingParameters::new(AddressInterface::BasicWallet)
305                        .with_note_tag_len(NoteTag::DEFAULT_NETWORK_TAG_LENGTH)?,
306                )?;
307
308                let bech32_string = address.encode(network_id.clone());
309                assert!(
310                    bech32_string.contains(Address::SEPARATOR),
311                    "separator should be present in address without routing params"
312                );
313                let (decoded_network_id, decoded_address) = Address::decode(&bech32_string)?;
314
315                assert_eq!(network_id, decoded_network_id, "network id failed in {idx}");
316                assert_eq!(address, decoded_address, "address failed in {idx}");
317
318                let AddressId::AccountId(decoded_account_id) = address.id();
319                assert_eq!(account_id, decoded_account_id);
320            }
321        }
322
323        Ok(())
324    }
325
326    #[test]
327    fn address_decoding_fails_on_trailing_separator() -> anyhow::Result<()> {
328        let id = AccountIdBuilder::new()
329            .account_type(AccountType::FungibleFaucet)
330            .build_with_rng(&mut rand::rng());
331
332        let address = Address::new(id);
333        let mut encoded_address = address.encode(NetworkId::Devnet);
334        encoded_address.push(Address::SEPARATOR);
335
336        let err = Address::decode(&encoded_address).unwrap_err();
337        assert_matches!(err, AddressError::TrailingSeparator);
338
339        Ok(())
340    }
341
342    /// Tests that an invalid checksum returns an error.
343    #[test]
344    fn bech32_invalid_checksum() -> anyhow::Result<()> {
345        let network_id = NetworkId::Mainnet;
346        let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?;
347        let address = Address::new(account_id).with_routing_parameters(
348            RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(14)?,
349        )?;
350
351        let bech32_string = address.encode(network_id);
352        let mut invalid_bech32_1 = bech32_string.clone();
353        invalid_bech32_1.remove(0);
354        let mut invalid_bech32_2 = bech32_string.clone();
355        invalid_bech32_2.remove(7);
356
357        let error = Address::decode(&invalid_bech32_1).unwrap_err();
358        assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
359
360        let error = Address::decode(&invalid_bech32_2).unwrap_err();
361        assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
362
363        Ok(())
364    }
365
366    /// Tests that an unknown address type returns an error.
367    #[test]
368    fn bech32_unknown_address_type() {
369        let invalid_bech32_address =
370            bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &[250]).unwrap();
371
372        let error = Address::decode(&invalid_bech32_address).unwrap_err();
373        assert_matches!(
374            error,
375            AddressError::Bech32DecodeError(Bech32Error::UnknownAddressType(250))
376        );
377    }
378
379    /// Tests that a bech32 using a disallowed checksum returns an error.
380    #[test]
381    fn bech32_invalid_other_checksum() {
382        let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
383        let address_id_bytes = AddressId::from(account_id).to_bytes();
384
385        // Use Bech32 instead of Bech32m which is disallowed.
386        let invalid_bech32_regular =
387            bech32::encode::<Bech32>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
388        let error = Address::decode(&invalid_bech32_regular).unwrap_err();
389        assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
390
391        // Use no checksum instead of Bech32m which is disallowed.
392        let invalid_bech32_no_checksum =
393            bech32::encode::<NoChecksum>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
394        let error = Address::decode(&invalid_bech32_no_checksum).unwrap_err();
395        assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
396    }
397
398    /// Tests that a bech32 string encoding data of an unexpected length returns an error.
399    #[test]
400    fn bech32_invalid_length() {
401        let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
402        let mut address_id_bytes = AddressId::from(account_id).to_bytes();
403        // Add one byte to make the length invalid.
404        address_id_bytes.push(5);
405
406        let invalid_bech32 =
407            bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &address_id_bytes).unwrap();
408
409        let error = Address::decode(&invalid_bech32).unwrap_err();
410        assert_matches!(
411            error,
412            AddressError::AccountIdDecodeError(AccountIdError::Bech32DecodeError(
413                Bech32Error::InvalidDataLength { .. }
414            ))
415        );
416    }
417
418    /// Tests that an Address can be serialized and deserialized
419    #[test]
420    fn address_serialization() -> anyhow::Result<()> {
421        let rng = &mut rand::rng();
422
423        for account_type in [
424            AccountType::FungibleFaucet,
425            AccountType::NonFungibleFaucet,
426            AccountType::RegularAccountImmutableCode,
427            AccountType::RegularAccountUpdatableCode,
428        ]
429        .into_iter()
430        {
431            let account_id = AccountIdBuilder::new().account_type(account_type).build_with_rng(rng);
432            let address = Address::new(account_id).with_routing_parameters(
433                RoutingParameters::new(AddressInterface::BasicWallet)
434                    .with_note_tag_len(NoteTag::DEFAULT_NETWORK_TAG_LENGTH)?,
435            )?;
436
437            let serialized = address.to_bytes();
438            let deserialized = Address::read_from_bytes(&serialized)?;
439            assert_eq!(address, deserialized);
440        }
441
442        Ok(())
443    }
444
445    /// Tests that an address with encryption key can be created and used.
446    #[test]
447    fn address_with_encryption_key() -> anyhow::Result<()> {
448        use crate::crypto::dsa::eddsa_25519::SecretKey;
449        use crate::crypto::ies::{SealingKey, UnsealingKey};
450
451        let rng = &mut rand::rng();
452        let account_id = AccountIdBuilder::new()
453            .account_type(AccountType::FungibleFaucet)
454            .build_with_rng(rng);
455
456        // Create keypair using rand::rng()
457        let secret_key = SecretKey::with_rng(rng);
458        let public_key = secret_key.public_key();
459        let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key.clone());
460        let unsealing_key = UnsealingKey::X25519XChaCha20Poly1305(secret_key.clone());
461
462        // Create address with encryption key
463        let address = Address::new(account_id).with_routing_parameters(
464            RoutingParameters::new(AddressInterface::BasicWallet)
465                .with_encryption_key(sealing_key.clone()),
466        )?;
467
468        // Verify encryption key is present
469        let retrieved_key =
470            address.encryption_key().expect("encryption key should be present").clone();
471        assert_eq!(retrieved_key, sealing_key);
472
473        // Test seal/unseal round-trip
474        let plaintext = b"hello world";
475        let sealed_message =
476            retrieved_key.seal_bytes(rng, plaintext).expect("sealing should succeed");
477        let decrypted =
478            unsealing_key.unseal_bytes(sealed_message).expect("unsealing should succeed");
479        assert_eq!(decrypted.as_slice(), plaintext);
480
481        Ok(())
482    }
483
484    /// Tests that an address with encryption key can be encoded/decoded.
485    #[test]
486    fn address_encryption_key_encode_decode() -> anyhow::Result<()> {
487        use crate::crypto::dsa::eddsa_25519::SecretKey;
488
489        let rng = &mut rand::rng();
490        // Use a local account type (RegularAccountImmutableCode) instead of network
491        // (FungibleFaucet)
492        let account_id = AccountIdBuilder::new()
493            .account_type(AccountType::RegularAccountImmutableCode)
494            .storage_mode(AccountStorageMode::Public)
495            .build_with_rng(rng);
496
497        // Create keypair
498        let secret_key = SecretKey::with_rng(rng);
499        let public_key = secret_key.public_key();
500        let sealing_key = SealingKey::X25519XChaCha20Poly1305(public_key);
501
502        // Create address with encryption key
503        let address = Address::new(account_id).with_routing_parameters(
504            RoutingParameters::new(AddressInterface::BasicWallet)
505                .with_encryption_key(sealing_key.clone()),
506        )?;
507
508        // Encode and decode
509        let encoded = address.encode(NetworkId::Mainnet);
510        let (decoded_network, decoded_address) = Address::decode(&encoded)?;
511
512        assert_eq!(decoded_network, NetworkId::Mainnet);
513        assert_eq!(address, decoded_address);
514
515        // Verify encryption key is preserved
516        let decoded_key = decoded_address
517            .encryption_key()
518            .expect("encryption key should be present")
519            .clone();
520        assert_eq!(decoded_key, sealing_key);
521
522        Ok(())
523    }
524}