miden_objects/address/
mod.rs

1mod r#type;
2
3mod interface;
4use alloc::string::{String, ToString};
5
6use bech32::Bech32m;
7use bech32::primitives::decode::{ByteIter, CheckedHrpstring};
8pub use interface::AddressInterface;
9pub use r#type::AddressType;
10
11use crate::AddressError;
12use crate::account::{AccountId, AccountStorageMode, NetworkId};
13use crate::errors::Bech32Error;
14use crate::note::NoteTag;
15
16/// A user-facing address in Miden.
17#[non_exhaustive]
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub enum Address {
20    AccountId(AccountIdAddress),
21}
22
23impl Address {
24    /// Returns a note tag derived from this address.
25    pub fn to_note_tag(&self) -> NoteTag {
26        match self {
27            Address::AccountId(addr) => addr.to_note_tag(),
28        }
29    }
30
31    /// Returns the [`AddressInterface`] of the account to which the address points.
32    pub fn interface(&self) -> AddressInterface {
33        match self {
34            Address::AccountId(account_id_address) => account_id_address.interface(),
35        }
36    }
37
38    /// Encodes the [`Address`] into a [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) string.
39    ///
40    /// ## Encoding
41    ///
42    /// The encoding of an address into bech32 is done as follows:
43    /// - Encode the underlying address to bytes.
44    /// - Into that data, insert the [`AddressType`] byte at index 0, shifting all other elements to
45    ///   the right.
46    /// - Choose an HRP, defined as a [`NetworkId`], e.g. [`NetworkId::Mainnet`] whose string
47    ///   representation is `mm`.
48    /// - Encode the resulting HRP together with the data into a bech32 string using the
49    ///   [`bech32::Bech32m`] checksum algorithm.
50    ///
51    /// This is an example of an address in bech32 representation:
52    ///
53    /// ```text
54    /// mm1qpkdyek2c0ywwvzupakc7zlzty8qn2qnfc
55    /// ```
56    ///
57    /// ## Rationale
58    ///
59    /// The address type is at the very beginning so that it can be decoded first to detect the type
60    /// of the address, without having to decode the entire data. Moreover, since the address type
61    /// is chosen as a multiple of 8, the first character of the bech32 string after the
62    /// `1` separator will be different for every address type. That makes the type of the address
63    /// conveniently human-readable.
64    ///
65    /// The only allowed checksum algorithm is [`Bech32m`] due to being the best available checksum
66    /// algorithm with no known weaknesses (unlike [`Bech32`](bech32::Bech32)). No checksum is
67    /// also not allowed since the intended use of bech32 is to have error
68    /// detection capabilities.
69    pub fn to_bech32(&self, network_id: NetworkId) -> String {
70        match self {
71            Address::AccountId(account_id_address) => account_id_address.to_bech32(network_id),
72        }
73    }
74
75    /// Decodes a [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) string
76    /// into the [`NetworkId`] and an [`Address`].
77    ///
78    /// See [`Address::to_bech32`] for details on the format. The procedure for decoding the bech32
79    /// data into the address are the inverse operations of encoding.
80    pub fn from_bech32(bech32_string: &str) -> Result<(NetworkId, Self), AddressError> {
81        // We use CheckedHrpString with an explicit checksum algorithm so we don't allow the
82        // `Bech32` or `NoChecksum` algorithms.
83        let checked_string = CheckedHrpstring::new::<Bech32m>(bech32_string).map_err(|source| {
84            // The CheckedHrpStringError does not implement core::error::Error, only
85            // std::error::Error, so for now we convert it to a String. Even if it will
86            // implement the trait in the future, we should include it as an opaque
87            // error since the crate does not have a stable release yet.
88            AddressError::Bech32DecodeError(Bech32Error::DecodeError(source.to_string().into()))
89        })?;
90
91        let hrp = checked_string.hrp();
92        let network_id = NetworkId::from_hrp(hrp);
93
94        let mut byte_iter = checked_string.byte_iter();
95
96        // We only know the expected length once we know the address type, but to get the address
97        // type, the length must be at least one.
98        let address_byte = byte_iter.next().ok_or_else(|| {
99            AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength {
100                expected: 1,
101                actual: byte_iter.len(),
102            })
103        })?;
104
105        let address_type = AddressType::try_from(address_byte)?;
106
107        let address = match address_type {
108            AddressType::AccountId => {
109                AccountIdAddress::from_bech32_byte_iter(byte_iter).map(Address::from)?
110            },
111        };
112
113        Ok((network_id, address))
114    }
115}
116
117// ACCOUNT ID ADDRESS
118// ================================================================================================
119
120/// An [`Address`] that targets a specific [`AccountId`] with an explicit tag length preference.
121///
122/// The tag length preference determines how many bits of the account ID are encoded into
123/// [`NoteTag`]s of notes targeted to this address. This lets the owner of the account choose
124/// their level of privacy. A higher tag length makes the account more uniquely identifiable and
125/// reduces privacy, while a shorter length increases privacy at the cost of matching more notes
126/// published onchain.
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
128pub struct AccountIdAddress {
129    id: AccountId,
130    tag_len: u8,
131    interface: AddressInterface,
132}
133
134impl AccountIdAddress {
135    // CONSTANTS
136    // --------------------------------------------------------------------------------------------
137
138    /// The serialized size of an [`AccountIdAddress`] in bytes.
139    pub const SERIALIZED_SIZE: usize = AccountId::SERIALIZED_SIZE + 2;
140
141    // CONSTRUCTORS
142    // --------------------------------------------------------------------------------------------
143
144    /// Creates a new account ID based address with the default tag length.
145    ///
146    /// The tag length defaults to [`NoteTag::DEFAULT_LOCAL_TAG_LENGTH`] for local, and
147    /// [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`] for network accounts.
148    pub fn new(id: AccountId, interface: AddressInterface) -> Self {
149        let tag_len = if id.storage_mode() == AccountStorageMode::Network {
150            NoteTag::DEFAULT_NETWORK_TAG_LENGTH
151        } else {
152            NoteTag::DEFAULT_LOCAL_TAG_LENGTH
153        };
154
155        Self { id, tag_len, interface }
156    }
157
158    // PUBLIC MUTATORS
159    // --------------------------------------------------------------------------------------------
160
161    /// Sets a custom tag length for the address, determining how many bits of the account ID
162    /// are encoded into [`NoteTag`]s.
163    ///
164    /// For local (both public and private) accounts, up to 30 bits can be encoded into the tag.
165    /// For network accounts, the tag length must be set to 30 bits.
166    ///
167    /// # Errors
168    ///
169    /// Returns an error if:
170    /// - The tag length exceeds [`NoteTag::MAX_LOCAL_TAG_LENGTH`] for local accounts.
171    /// - The tag length is not [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`] for network accounts.
172    pub fn with_tag_len(mut self, tag_len: u8) -> Result<Self, AddressError> {
173        if self.id.storage_mode() == AccountStorageMode::Network {
174            if tag_len != NoteTag::DEFAULT_NETWORK_TAG_LENGTH {
175                return Err(AddressError::CustomTagLengthNotAllowedForNetworkAccounts(tag_len));
176            }
177        } else if tag_len > NoteTag::MAX_LOCAL_TAG_LENGTH {
178            return Err(AddressError::TagLengthTooLarge(tag_len));
179        }
180
181        self.tag_len = tag_len;
182        Ok(self)
183    }
184
185    // PUBLIC ACCESSORS
186    // --------------------------------------------------------------------------------------------
187
188    /// Returns the underlying account id.
189    pub fn id(&self) -> AccountId {
190        self.id
191    }
192
193    /// Returns the preferred tag length.
194    ///
195    /// This is guaranteed to be in range `0..=30` (e.g. the maximum of
196    /// [`NoteTag::MAX_LOCAL_TAG_LENGTH`] and [`NoteTag::DEFAULT_NETWORK_TAG_LENGTH`]).
197    pub fn note_tag_len(&self) -> u8 {
198        self.tag_len
199    }
200
201    /// Returns the [`AddressInterface`] of the account to which the address points.
202    pub fn interface(&self) -> AddressInterface {
203        self.interface
204    }
205
206    /// Returns a note tag derived from this address.
207    pub fn to_note_tag(&self) -> NoteTag {
208        match self.id.storage_mode() {
209            AccountStorageMode::Network => NoteTag::from_network_account_id(self.id),
210            AccountStorageMode::Private | AccountStorageMode::Public => {
211                NoteTag::from_local_account_id(self.id, self.tag_len)
212                    .expect("AccountIdAddress validated that tag len does not exceed MAX_LOCAL_TAG_LENGTH bits")
213            },
214        }
215    }
216
217    // PRIVATE HELPERS
218    // ----------------------------------------------------------------------------------------
219
220    /// Encodes the [`AccountIdAddress`] to a bech32 string.
221    ///
222    /// See [`Address::to_bech32`] for more details.
223    fn to_bech32(self, network_id: NetworkId) -> String {
224        let id_bytes: [u8; Self::SERIALIZED_SIZE] = self.into();
225
226        // Create an array that fits the encoded account ID address plus the address type byte.
227        let mut data = [0; Self::SERIALIZED_SIZE + 1];
228        // Encode the address type into index 0.
229        data[0] = AddressType::AccountId as u8;
230        // Encode the 17 account ID address bytes into 1..18.
231        data[1..].copy_from_slice(&id_bytes);
232
233        // SAFETY: Encoding panics if the total length of the hrp + data (encoded in GF(32)) + the
234        // separator + the checksum exceeds Bech32m::CODE_LENGTH, which is 1023.
235        // The total 18 bytes of data we encode result in (18 bytes * 8 bits / 5 bits per base32
236        // symbol) = 29 characters. The hrp is at most 83 in length, so we are guaranteed to be
237        // below the limit.
238        bech32::encode::<Bech32m>(network_id.into_hrp(), &data)
239            .expect("code length of bech32 should not be exceeded")
240    }
241
242    /// Decodes the data from the bech32 byte iterator into an [`AccountIdAddress`].
243    ///
244    /// See [`Address::from_bech32`] for details.
245    fn from_bech32_byte_iter(byte_iter: ByteIter<'_>) -> Result<Self, AddressError> {
246        // The _remaining_ length of the iterator must be the serialized size of the account ID
247        // address.
248        if byte_iter.len() != Self::SERIALIZED_SIZE {
249            return Err(AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength {
250                expected: Self::SERIALIZED_SIZE,
251                actual: byte_iter.len(),
252            }));
253        }
254
255        // Every byte is guaranteed to be overwritten since we've checked the length of the
256        // iterator.
257        let mut id_bytes = [0_u8; Self::SERIALIZED_SIZE];
258        for (i, byte) in byte_iter.enumerate() {
259            id_bytes[i] = byte;
260        }
261
262        let account_id_address = Self::try_from(id_bytes)?;
263
264        Ok(account_id_address)
265    }
266}
267
268impl From<AccountIdAddress> for Address {
269    fn from(addr: AccountIdAddress) -> Self {
270        Address::AccountId(addr)
271    }
272}
273
274impl From<AccountIdAddress> for [u8; AccountIdAddress::SERIALIZED_SIZE] {
275    fn from(account_id_address: AccountIdAddress) -> Self {
276        let mut result = [0_u8; AccountIdAddress::SERIALIZED_SIZE];
277
278        // Encode the account ID into 0..15.
279        let encoded_account_id_address = <[u8; 15]>::from(account_id_address.id);
280        result[..15].copy_from_slice(&encoded_account_id_address);
281
282        let interface = account_id_address.interface as u16;
283        debug_assert_eq!(
284            interface >> 11,
285            0,
286            "address interface should have its upper 5 bits unset"
287        );
288
289        // The interface takes up 11 bits and the tag length 5 bits, so we can merge them together.
290        let tag_len = (account_id_address.tag_len as u16) << 11;
291        let encoded = tag_len | interface;
292        let encoded: [u8; 2] = encoded.to_be_bytes();
293
294        // Encode the interface and tag length into 15..17.
295        result[15] = encoded[0];
296        result[16] = encoded[1];
297
298        result
299    }
300}
301
302impl TryFrom<[u8; AccountIdAddress::SERIALIZED_SIZE]> for AccountIdAddress {
303    type Error = AddressError;
304
305    fn try_from(bytes: [u8; AccountIdAddress::SERIALIZED_SIZE]) -> Result<Self, Self::Error> {
306        let account_id_bytes: [u8; AccountId::SERIALIZED_SIZE] = bytes
307            [..AccountId::SERIALIZED_SIZE]
308            .try_into()
309            .expect("we should have sliced off exactly 15 bytes");
310        let account_id =
311            AccountId::try_from(account_id_bytes).map_err(AddressError::AccountIdDecodeError)?;
312
313        let interface_tag_len = u16::from_be_bytes([bytes[15], bytes[16]]);
314        let tag_len = (interface_tag_len >> 11) as u8;
315        let interface = interface_tag_len & 0b0000_0111_1111_1111;
316        let interface = AddressInterface::try_from(interface)?;
317
318        Self::new(account_id, interface).with_tag_len(tag_len)
319    }
320}
321
322// TESTS
323// ================================================================================================
324
325#[cfg(test)]
326mod tests {
327    use assert_matches::assert_matches;
328    use bech32::{Bech32, Hrp, NoChecksum};
329
330    use super::*;
331    use crate::account::AccountType;
332    use crate::testing::account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, AccountIdBuilder};
333
334    /// Tests that an account ID address can be encoded and decoded.
335    #[test]
336    fn address_bech32_encode_decode_roundtrip() {
337        // We use this to check that encoding does not panic even when using the longest possible
338        // HRP.
339        let longest_possible_hrp =
340            "01234567890123456789012345678901234567890123456789012345678901234567890123456789012";
341        assert_eq!(longest_possible_hrp.len(), 83);
342
343        let rng = &mut rand::rng();
344        for network_id in [
345            NetworkId::Mainnet,
346            NetworkId::Custom(Hrp::parse("custom").unwrap()),
347            NetworkId::Custom(Hrp::parse(longest_possible_hrp).unwrap()),
348        ] {
349            for (idx, account_id) in [
350                AccountIdBuilder::new()
351                    .account_type(AccountType::FungibleFaucet)
352                    .build_with_rng(rng),
353                AccountIdBuilder::new()
354                    .account_type(AccountType::NonFungibleFaucet)
355                    .build_with_rng(rng),
356                AccountIdBuilder::new()
357                    .account_type(AccountType::RegularAccountImmutableCode)
358                    .build_with_rng(rng),
359                AccountIdBuilder::new()
360                    .account_type(AccountType::RegularAccountUpdatableCode)
361                    .build_with_rng(rng),
362            ]
363            .into_iter()
364            .enumerate()
365            {
366                let account_id_address =
367                    AccountIdAddress::new(account_id, AddressInterface::BasicWallet);
368                let address = Address::from(account_id_address);
369
370                let bech32_string = address.to_bech32(network_id);
371                let (decoded_network_id, decoded_address) =
372                    Address::from_bech32(&bech32_string).unwrap();
373
374                assert_eq!(network_id, decoded_network_id, "network id failed in {idx}");
375                assert_eq!(address, decoded_address, "address failed in {idx}");
376
377                let Address::AccountId(decoded_account_id) = address;
378                assert_eq!(account_id, decoded_account_id.id());
379                assert_eq!(account_id_address.note_tag_len(), decoded_account_id.note_tag_len());
380            }
381        }
382    }
383
384    /// Tests that an invalid checksum returns an error.
385    #[test]
386    fn bech32_invalid_checksum() {
387        let network_id = NetworkId::Mainnet;
388        let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
389        let address =
390            Address::from(AccountIdAddress::new(account_id, AddressInterface::BasicWallet));
391
392        let bech32_string = address.to_bech32(network_id);
393        let mut invalid_bech32_1 = bech32_string.clone();
394        invalid_bech32_1.remove(0);
395        let mut invalid_bech32_2 = bech32_string.clone();
396        invalid_bech32_2.remove(7);
397
398        let error = Address::from_bech32(&invalid_bech32_1).unwrap_err();
399        assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
400
401        let error = Address::from_bech32(&invalid_bech32_2).unwrap_err();
402        assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
403    }
404
405    /// Tests that an unknown address type returns an error.
406    #[test]
407    fn bech32_unknown_address_type() {
408        let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
409        let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet);
410        let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec();
411
412        // Set invalid address type.
413        id_address_bytes.insert(0, 250);
414
415        let invalid_bech32 =
416            bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap();
417
418        let error = Address::from_bech32(&invalid_bech32).unwrap_err();
419        assert_matches!(
420            error,
421            AddressError::Bech32DecodeError(Bech32Error::UnknownAddressType(250))
422        );
423    }
424
425    /// Tests that a bech32 using a disallowed checksum returns an error.
426    #[test]
427    fn bech32_invalid_other_checksum() {
428        let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
429        let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet);
430        let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec();
431        id_address_bytes.insert(0, AddressType::AccountId as u8);
432
433        // Use Bech32 instead of Bech32m which is disallowed.
434        let invalid_bech32_regular =
435            bech32::encode::<Bech32>(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap();
436        let error = Address::from_bech32(&invalid_bech32_regular).unwrap_err();
437        assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
438
439        // Use no checksum instead of Bech32m which is disallowed.
440        let invalid_bech32_no_checksum =
441            bech32::encode::<NoChecksum>(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap();
442        let error = Address::from_bech32(&invalid_bech32_no_checksum).unwrap_err();
443        assert_matches!(error, AddressError::Bech32DecodeError(Bech32Error::DecodeError(_)));
444    }
445
446    /// Tests that a bech32 string encoding data of an unexpected length returns an error.
447    #[test]
448    fn bech32_invalid_length() {
449        let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
450        let account_id_address = AccountIdAddress::new(account_id, AddressInterface::BasicWallet);
451        let mut id_address_bytes = <[u8; _]>::from(account_id_address).to_vec();
452        id_address_bytes.insert(0, AddressType::AccountId as u8);
453        // Add one byte to make the length invalid.
454        id_address_bytes.push(5);
455
456        let invalid_bech32 =
457            bech32::encode::<Bech32m>(NetworkId::Mainnet.into_hrp(), &id_address_bytes).unwrap();
458
459        let error = Address::from_bech32(&invalid_bech32).unwrap_err();
460        assert_matches!(
461            error,
462            AddressError::Bech32DecodeError(Bech32Error::InvalidDataLength { .. })
463        );
464    }
465}