Skip to main content

iota_sdk_types/
address.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use crate::ObjectId;
6
7/// Unique identifier for an Account on the IOTA blockchain.
8///
9/// An `Address` is a 32-byte pseudonymous identifier used to uniquely identify
10/// an account and asset-ownership on the IOTA blockchain. Often, human-readable
11/// addresses are encoded in hexadecimal with a `0x` prefix. For example, this
12/// is a valid IOTA address:
13/// `0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331`.
14///
15/// ```
16/// use iota_sdk_types::Address;
17///
18/// let hex = "0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331";
19/// let address = Address::from_hex(hex).unwrap();
20/// println!("Address: {}", address);
21/// assert_eq!(hex, address.to_string());
22/// ```
23///
24/// # Deriving an Address
25///
26/// Addresses are cryptographically derived from a number of user account
27/// authenticators, the simplest of which is an
28/// [`Ed25519PublicKey`](crate::Ed25519PublicKey).
29///
30/// Deriving an address consists of the Blake2b256 hash of the sequence of bytes
31/// of its corresponding authenticator, prefixed with a domain-separator (except
32/// ed25519, for compatibility reasons). For each other authenticator, this
33/// domain-separator is the single byte-value of its
34/// [`SignatureScheme`](crate::SignatureScheme) flag. E.g. `hash(signature
35/// schema flag || authenticator bytes)`.
36///
37/// Each authenticator has a method for deriving its `Address` as well as
38/// documentation for the specifics of how the derivation is done. See
39/// [`Ed25519PublicKey::derive_address`] for an example.
40///
41/// [`Ed25519PublicKey::derive_address`]: crate::Ed25519PublicKey::derive_address
42///
43/// ## Relationship to ObjectIds
44///
45/// [`ObjectId`]s and [`Address`]es share the same 32-byte addressable space but
46/// are derived leveraging different domain-separator values to ensure that,
47/// cryptographically, there won't be any overlap, e.g. there can't be a
48/// valid `Object` who's `ObjectId` is equal to that of the `Address` of a user
49/// account.
50///
51/// [`ObjectId`]: crate::ObjectId
52///
53/// # BCS
54///
55/// An `Address`'s BCS serialized form is defined by the following:
56///
57/// ```text
58/// address = 32OCTET
59/// ```
60#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
61#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
62#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
63#[cfg_attr(
64    feature = "bcs-schema",
65    derive(iota_bcs_schema::BcsSchema),
66    bcs_schema(definition = "32OCTET")
67)]
68pub struct Address(
69    #[cfg_attr(
70        feature = "serde",
71        serde(with = "::serde_with::As::<::serde_with::IfIsHumanReadable<ReadableAddress>>")
72    )]
73    [u8; Self::LENGTH],
74);
75
76impl Address {
77    pub const LENGTH: usize = 32;
78    pub const ZERO: Self = Self([0u8; Self::LENGTH]);
79    pub const MAX: Self = Self([u8::MAX; Self::LENGTH]);
80    pub const STD: Self = Self::from_u16(1);
81    pub const FRAMEWORK: Self = Self::from_u16(2);
82    pub const SYSTEM: Self = Self::from_u16(3);
83    pub const GENESIS_BRIDGE: Self = Self::from_u16(0xb);
84    pub const STARDUST: Self = Self::from_u16(0x107a);
85    pub const SYSTEM_STATE: Self = Self::from_u16(5);
86    pub const CLOCK: Self = Self::from_u16(6);
87    pub const AUTHENTICATOR_STATE: Self = Self::from_u16(7);
88    pub const RANDOMNESS_STATE: Self = Self::from_u16(8);
89    pub const GENESIS_IOTA_BRIDGE: Self = Self::from_u16(9);
90    pub const DENY_LIST: Self = Self::from_u16(0x403);
91    pub const TRANSACTION_DENY_RULES: Self = Self::from_u16(0xde9);
92
93    pub const fn new(bytes: [u8; Self::LENGTH]) -> Self {
94        Self(bytes)
95    }
96
97    /// Creates an `Address` from a `u16` suffix by setting the last two bytes.
98    pub const fn from_u16(suffix: u16) -> Self {
99        let mut address = Self::ZERO;
100        let [hi, lo] = suffix.to_be_bytes();
101        address.0[Address::LENGTH - 2] = hi;
102        address.0[Address::LENGTH - 1] = lo;
103        address
104    }
105
106    /// Checks if the address is one of the system package addresses.
107    /// The system packages are:
108    /// - STD
109    /// - FRAMEWORK
110    /// - SYSTEM
111    /// - GENESIS_BRIDGE
112    /// - STARDUST
113    pub fn is_system_package(&self) -> bool {
114        [
115            Self::STD,
116            Self::FRAMEWORK,
117            Self::SYSTEM,
118            Self::GENESIS_BRIDGE,
119            Self::STARDUST,
120        ]
121        .contains(self)
122    }
123
124    #[cfg(feature = "rand")]
125    #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
126    pub fn random_with<R>(mut rng: R) -> Self
127    where
128        R: rand_core::RngCore + rand_core::CryptoRng,
129    {
130        let mut buf: [u8; Self::LENGTH] = [0; Self::LENGTH];
131        rng.fill_bytes(&mut buf);
132        Self::new(buf)
133    }
134
135    #[cfg(feature = "rand")]
136    #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
137    pub fn random() -> Self {
138        Self::random_with(rand_core::OsRng)
139    }
140
141    /// Return the underlying byte array of a Address.
142    pub const fn into_bytes(self) -> [u8; Self::LENGTH] {
143        self.0
144    }
145
146    pub const fn bytes(&self) -> &[u8; Self::LENGTH] {
147        &self.0
148    }
149
150    pub const fn as_bytes(&self) -> &[u8] {
151        &self.0
152    }
153
154    pub const fn from_object_id(object_id: ObjectId) -> Self {
155        object_id.0
156    }
157
158    /// Parses an Address from a full-length hex string (64 hex characters),
159    /// with or without a `0x` prefix. Will return an error if the string is not
160    /// exactly 64 hex characters long (excluding the `0x` prefix).
161    pub fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, AddressParseError> {
162        let hex = hex.as_ref();
163        let hex = if hex.starts_with(b"0x") {
164            &hex[2..]
165        } else {
166            hex
167        };
168        if hex.len() != Self::LENGTH * 2 {
169            return Err(AddressParseError::FromHex(
170                hex::FromHexError::InvalidStringLength,
171            ));
172        }
173        <[u8; Self::LENGTH] as hex::FromHex>::from_hex(hex)
174            .map(Self)
175            .map_err(AddressParseError::FromHex)
176    }
177
178    /// Parses an Address from a full-length hex string (64 hex characters),
179    /// with a mandatory `0x` prefix. Will return an error if the string is not
180    /// exactly 64 hex characters long (excluding the `0x` prefix).
181    pub fn from_prefixed_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, AddressParseError> {
182        if !hex.as_ref().starts_with(b"0x") {
183            return Err(AddressParseError::MissingPrefix);
184        }
185        Self::from_hex(hex)
186    }
187
188    /// Parses an Address from a full-length hex string (64 hex characters),
189    /// without a `0x` prefix. Will return an error if the string has a `0x`
190    /// prefix or is not exactly 64 hex characters long.
191    pub fn from_raw_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, AddressParseError> {
192        if hex.as_ref().starts_with(b"0x") {
193            return Err(AddressParseError::UnexpectedPrefix);
194        }
195        Self::from_hex(hex)
196    }
197
198    /// Parses an Address from a hex string, with or without a `0x` prefix.
199    /// The string can be of variable length; if it's shorter than 64 hex
200    /// characters, it will be left-padded with `0`s.
201    pub fn from_short_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, AddressParseError> {
202        let hex = hex.as_ref();
203        let hex = if hex.starts_with(b"0x") {
204            &hex[2..]
205        } else {
206            hex
207        };
208
209        // If the string is too short we'll need to pad with 0's
210        if hex.len() < Self::LENGTH * 2 {
211            let mut buf = [b'0'; Self::LENGTH * 2];
212            let pad_length = (Self::LENGTH * 2) - hex.len();
213
214            buf[pad_length..].copy_from_slice(hex);
215
216            <[u8; Self::LENGTH] as hex::FromHex>::from_hex(buf)
217        } else {
218            <[u8; Self::LENGTH] as hex::FromHex>::from_hex(hex)
219        }
220        .map(Self)
221        .map_err(AddressParseError::FromHex)
222    }
223
224    /// Parses an Address from a hex string with a mandatory `0x` prefix.
225    /// The string can be of variable length; if it's shorter than 64 hex
226    /// characters, it will be left-padded with `0`s.
227    pub fn from_prefixed_short_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, AddressParseError> {
228        if !hex.as_ref().starts_with(b"0x") {
229            return Err(AddressParseError::MissingPrefix);
230        }
231        Self::from_short_hex(hex)
232    }
233
234    /// Parses an Address from a hex string without a `0x` prefix.
235    /// The string can be of variable length; if it's shorter than 64 hex
236    /// characters, it will be left-padded with `0`s. Will return an error if
237    /// the string has a `0x` prefix.
238    pub fn from_raw_short_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, AddressParseError> {
239        if hex.as_ref().starts_with(b"0x") {
240            return Err(AddressParseError::UnexpectedPrefix);
241        }
242        Self::from_short_hex(hex)
243    }
244
245    /// Returns the string representation of this address in hex format with
246    /// `0x` prefix.
247    pub fn to_hex(&self) -> String {
248        self.to_canonical_string(true)
249    }
250
251    /// Returns the string representation of this address in hex format without
252    /// `0x` prefix.
253    pub fn to_raw_hex(&self) -> String {
254        self.to_canonical_string(false)
255    }
256
257    /// Returns the shortest possible string representation of the address (i.e.
258    /// with leading zeroes trimmed).
259    pub fn to_short_hex(&self) -> String {
260        format!("0x{}", self.to_raw_short_hex())
261    }
262
263    /// Returns the shortest possible string representation of the address (i.e.
264    /// with leading zeroes trimmed), without `0x` prefix.
265    pub fn to_raw_short_hex(&self) -> String {
266        let full_str = self.to_canonical_string(false);
267        let trimmed = full_str.trim_start_matches('0');
268        let hex_str = if trimmed.is_empty() { "0" } else { trimmed };
269        hex_str.to_owned()
270    }
271
272    /// Returns the string representation of this address using the
273    /// canonical display, with or without a `0x` prefix.
274    pub fn to_canonical_string(&self, with_prefix: bool) -> String {
275        let hex_str = hex::encode(self.0);
276        if with_prefix {
277            format!("0x{hex_str}")
278        } else {
279            hex_str
280        }
281    }
282
283    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, AddressParseError> {
284        let bytes = bytes.as_ref();
285        <[u8; Self::LENGTH]>::try_from(bytes)
286            .map_err(|_| AddressParseError::InvalidByteLength {
287                actual: bytes.len(),
288            })
289            .map(Self)
290    }
291
292    /// Returns the next address in byte-increasing order.
293    pub const fn next_lexicographical(&self) -> Self {
294        Self::new(crate::next_lexicographical_array(self.bytes()))
295    }
296
297    /// Returns the next address in byte-increasing order, or `None` if the
298    /// result would overflow.
299    pub const fn next_lexicographical_opt(&self) -> Option<Self> {
300        match crate::next_lexicographical_array_opt(self.bytes()) {
301            Some(val) => Some(Self::new(val)),
302            None => None,
303        }
304    }
305}
306
307impl std::str::FromStr for Address {
308    type Err = AddressParseError;
309
310    fn from_str(s: &str) -> Result<Self, Self::Err> {
311        // If the string is not prefixed, we only accept full-length hex
312        if s.starts_with("0x") {
313            Self::from_prefixed_short_hex(s)
314        } else {
315            Self::from_hex(s)
316        }
317    }
318}
319
320impl AsRef<[u8]> for Address {
321    fn as_ref(&self) -> &[u8] {
322        &self.0
323    }
324}
325
326impl AsRef<[u8; 32]> for Address {
327    fn as_ref(&self) -> &[u8; 32] {
328        &self.0
329    }
330}
331
332impl From<Address> for [u8; 32] {
333    fn from(address: Address) -> Self {
334        address.into_bytes()
335    }
336}
337
338impl From<[u8; 32]> for Address {
339    fn from(address: [u8; 32]) -> Self {
340        Self::new(address)
341    }
342}
343
344impl From<Address> for Vec<u8> {
345    fn from(value: Address) -> Self {
346        value.0.to_vec()
347    }
348}
349
350impl From<super::ObjectId> for Address {
351    fn from(value: super::ObjectId) -> Self {
352        Self::from_object_id(value)
353    }
354}
355
356impl std::fmt::Display for Address {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358        self.to_canonical_string(true).fmt(f)
359    }
360}
361
362impl std::fmt::Debug for Address {
363    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
364        f.debug_tuple("Address")
365            .field(&format_args!("\"{self}\""))
366            .finish()
367    }
368}
369
370#[cfg(feature = "serde")]
371#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
372struct ReadableAddress;
373
374#[cfg(feature = "serde")]
375#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
376impl serde_with::SerializeAs<[u8; Address::LENGTH]> for ReadableAddress {
377    fn serialize_as<S>(source: &[u8; Address::LENGTH], serializer: S) -> Result<S::Ok, S::Error>
378    where
379        S: serde::Serializer,
380    {
381        let address = Address::new(*source);
382        serde_with::DisplayFromStr::serialize_as(&address, serializer)
383    }
384}
385
386#[cfg(feature = "serde")]
387#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
388impl<'de> serde_with::DeserializeAs<'de, [u8; Address::LENGTH]> for ReadableAddress {
389    fn deserialize_as<D>(deserializer: D) -> Result<[u8; Address::LENGTH], D::Error>
390    where
391        D: serde::Deserializer<'de>,
392    {
393        let address: Address = serde_with::DisplayFromStr::deserialize_as(deserializer)?;
394        Ok(address.into_bytes())
395    }
396}
397
398#[derive(Clone, Debug, PartialEq, thiserror::Error)]
399#[non_exhaustive]
400pub enum AddressParseError {
401    #[error("address must be hex string of length {}", Address::LENGTH * 2)]
402    FromHex(#[from] hex::FromHexError),
403    #[error(
404        "invalid address byte length: expected {}, got {actual}",
405        Address::LENGTH
406    )]
407    InvalidByteLength { actual: usize },
408    #[error("address hex string missing `0x` prefix")]
409    MissingPrefix,
410    #[error("address hex string has unexpected `0x` prefix")]
411    UnexpectedPrefix,
412}
413
414#[cfg(test)]
415mod tests {
416    #[cfg(feature = "proptest")]
417    mod proptests {
418        use test_strategy::proptest;
419
420        use super::super::Address;
421
422        #[proptest]
423        fn roundtrip_display_fromstr(address: Address) {
424            assert_eq!(address, address.to_string().parse().unwrap());
425        }
426    }
427
428    #[cfg(target_arch = "wasm32")]
429    use wasm_bindgen_test::wasm_bindgen_test as test;
430
431    use super::{Address, AddressParseError};
432
433    #[test]
434    fn parse_address_with_0x_prefix() {
435        let hex = "0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331";
436        let address = Address::from_short_hex(hex).unwrap();
437        assert_eq!(address.to_string(), hex);
438    }
439
440    #[test]
441    fn parse_address_without_0x_prefix() {
442        let hex = "02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331";
443        let address = Address::from_short_hex(hex).unwrap();
444        assert_eq!(
445            address.to_string(),
446            "0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331"
447        );
448    }
449
450    #[test]
451    fn parse_short_address_single_digit() {
452        let address = Address::from_short_hex("0x1").unwrap();
453        assert_eq!(address, Address::STD);
454        assert_eq!(
455            address.to_string(),
456            "0x0000000000000000000000000000000000000000000000000000000000000001"
457        );
458    }
459
460    #[test]
461    fn parse_short_address_without_prefix() {
462        let address = Address::from_short_hex("3").unwrap();
463        assert_eq!(address, Address::SYSTEM);
464    }
465
466    #[test]
467    fn parse_zero_address() {
468        let address = Address::from_short_hex("0x0").unwrap();
469        assert_eq!(address, Address::ZERO);
470
471        let address = Address::from_short_hex("0").unwrap();
472        assert_eq!(address, Address::ZERO);
473
474        let address = Address::from_short_hex(
475            "0x0000000000000000000000000000000000000000000000000000000000000000",
476        )
477        .unwrap();
478        assert_eq!(address, Address::ZERO);
479    }
480
481    #[test]
482    fn parse_address_invalid_hex_char() {
483        let result = Address::from_short_hex("0xGGGG");
484        assert!(result.is_err());
485        assert!(matches!(
486            result,
487            Err(AddressParseError::FromHex(
488                hex::FromHexError::InvalidHexCharacter { .. }
489            ))
490        ));
491    }
492
493    #[test]
494    fn parse_address_too_long() {
495        // 65 hex chars (one more than allowed 64)
496        let result = Address::from_short_hex(
497            "0x002a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331",
498        );
499        assert!(matches!(
500            result,
501            Err(AddressParseError::FromHex(hex::FromHexError::OddLength))
502        ));
503
504        // 66 hex chars (two more than allowed 64)
505        let result = Address::from_short_hex(
506            "0x002a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f3316",
507        );
508        assert!(matches!(
509            result,
510            Err(AddressParseError::FromHex(
511                hex::FromHexError::InvalidStringLength
512            ))
513        ));
514    }
515
516    #[test]
517    fn parse_raw_hex() {
518        let hex = "02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331";
519        let address = Address::from_raw_hex(hex).unwrap();
520        assert_eq!(address.to_raw_hex(), hex);
521
522        let result = Address::from_raw_hex(format!("0x{hex}"));
523        assert!(matches!(result, Err(AddressParseError::UnexpectedPrefix)));
524    }
525
526    #[test]
527    fn parse_raw_short_hex() {
528        let address = Address::from_raw_short_hex("2").unwrap();
529        assert_eq!(address, Address::FRAMEWORK);
530
531        let result = Address::from_raw_short_hex("0x2");
532        assert!(matches!(result, Err(AddressParseError::UnexpectedPrefix)));
533    }
534
535    #[test]
536    fn from_bytes_valid() {
537        let bytes = [0u8; 32];
538        let address = Address::from_bytes(bytes).unwrap();
539        assert_eq!(address, Address::ZERO);
540    }
541
542    #[test]
543    fn from_bytes_invalid_length() {
544        let bytes = [0u8; 31];
545        let result = Address::from_bytes(bytes);
546        assert!(matches!(
547            result,
548            Err(AddressParseError::InvalidByteLength { actual: 31 })
549        ));
550
551        let bytes = [0u8; 33];
552        let result = Address::from_bytes(bytes);
553        assert!(matches!(
554            result,
555            Err(AddressParseError::InvalidByteLength { actual: 33 })
556        ));
557    }
558
559    #[test]
560    fn to_short_string_formats() {
561        let address = Address::from_short_hex("0x2").unwrap();
562        assert_eq!(address.to_short_hex(), "0x2");
563        assert_eq!(address.to_raw_short_hex(), "2");
564
565        let zero = Address::ZERO;
566        assert_eq!(zero.to_short_hex(), "0x0");
567        assert_eq!(zero.to_raw_short_hex(), "0");
568    }
569
570    #[test]
571    fn to_canonical_string_formats() {
572        let address = Address::from_short_hex("0x2").unwrap();
573        assert_eq!(
574            address.to_canonical_string(true),
575            "0x0000000000000000000000000000000000000000000000000000000000000002"
576        );
577        assert_eq!(
578            address.to_canonical_string(false),
579            "0000000000000000000000000000000000000000000000000000000000000002"
580        );
581    }
582
583    #[test]
584    #[cfg(feature = "serde")]
585    fn formats() {
586        let actual = Address::from_short_hex("0x2").unwrap();
587
588        println!("{}", serde_json::to_string(&actual).unwrap());
589        println!("{:?}", bcs::to_bytes(&actual).unwrap());
590        let a: Address = serde_json::from_str("\"0x2\"").unwrap();
591        println!("{a}");
592    }
593}