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
5/// Unique identifier for an Account on the IOTA blockchain.
6///
7/// An `Address` is a 32-byte pseudonymous identifier used to uniquely identify
8/// an account and asset-ownership on the IOTA blockchain. Often, human-readable
9/// addresses are encoded in hexadecimal with a `0x` prefix. For example, this
10/// is a valid IOTA address:
11/// `0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331`.
12///
13/// ```
14/// use iota_sdk_types::Address;
15///
16/// let hex = "0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331";
17/// let address = Address::from_hex(hex).unwrap();
18/// println!("Address: {}", address);
19/// assert_eq!(hex, address.to_string());
20/// ```
21///
22/// # Deriving an Address
23///
24/// Addresses are cryptographically derived from a number of user account
25/// authenticators, the simplest of which is an
26/// [`Ed25519PublicKey`](crate::Ed25519PublicKey).
27///
28/// Deriving an address consists of the Blake2b256 hash of the sequence of bytes
29/// of its corresponding authenticator, prefixed with a domain-separator (except
30/// ed25519, for compatibility reasons). For each other authenticator, this
31/// domain-separator is the single byte-value of its
32/// [`SignatureScheme`](crate::SignatureScheme) flag. E.g. `hash(signature
33/// schema flag || authenticator bytes)`.
34///
35/// Each authenticator has a method for deriving its `Address` as well as
36/// documentation for the specifics of how the derivation is done. See
37/// [`Ed25519PublicKey::derive_address`] for an example.
38///
39/// [`Ed25519PublicKey::derive_address`]: crate::Ed25519PublicKey::derive_address
40///
41/// ## Relationship to ObjectIds
42///
43/// [`ObjectId`]s and [`Address`]es share the same 32-byte addressable space but
44/// are derived leveraging different domain-separator values to ensure that,
45/// cryptographically, there won't be any overlap, e.g. there can't be a
46/// valid `Object` who's `ObjectId` is equal to that of the `Address` of a user
47/// account.
48///
49/// [`ObjectId`]: crate::ObjectId
50///
51/// # BCS
52///
53/// An `Address`'s BCS serialized form is defined by the following:
54///
55/// ```text
56/// address = 32OCTET
57/// ```
58#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
61pub struct Address(
62    #[cfg_attr(
63        feature = "serde",
64        serde(with = "::serde_with::As::<::serde_with::IfIsHumanReadable<ReadableAddress>>")
65    )]
66    [u8; Self::LENGTH],
67);
68
69impl Address {
70    pub const LENGTH: usize = 32;
71    pub const ZERO: Self = Self([0u8; Self::LENGTH]);
72    pub const STD_LIB: Self = Self::from_u8(1);
73    pub const FRAMEWORK: Self = Self::from_u8(2);
74    pub const SYSTEM: Self = Self::from_u8(3);
75
76    pub const fn new(bytes: [u8; Self::LENGTH]) -> Self {
77        Self(bytes)
78    }
79
80    pub(crate) const fn from_u8(byte: u8) -> Self {
81        let mut address = Self::ZERO;
82        address.0[31] = byte;
83        address
84    }
85
86    #[cfg(feature = "rand")]
87    #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
88    pub fn generate<R>(mut rng: R) -> Self
89    where
90        R: rand_core::RngCore + rand_core::CryptoRng,
91    {
92        let mut buf: [u8; Self::LENGTH] = [0; Self::LENGTH];
93        rng.fill_bytes(&mut buf);
94        Self::new(buf)
95    }
96
97    /// Return the underlying byte array of a Address.
98    pub const fn into_inner(self) -> [u8; Self::LENGTH] {
99        self.0
100    }
101
102    pub const fn inner(&self) -> &[u8; Self::LENGTH] {
103        &self.0
104    }
105
106    pub const fn as_bytes(&self) -> &[u8] {
107        &self.0
108    }
109
110    pub fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, AddressParseError> {
111        let hex = hex.as_ref();
112
113        if !hex.starts_with(b"0x") {
114            return Err(AddressParseError);
115        }
116
117        let hex = &hex[2..];
118
119        // If the string is too short we'll need to pad with 0's
120        if hex.len() < Self::LENGTH * 2 {
121            let mut buf = [b'0'; Self::LENGTH * 2];
122            let pad_length = (Self::LENGTH * 2) - hex.len();
123
124            buf[pad_length..].copy_from_slice(hex);
125
126            <[u8; Self::LENGTH] as hex::FromHex>::from_hex(buf)
127        } else {
128            <[u8; Self::LENGTH] as hex::FromHex>::from_hex(hex)
129        }
130        .map(Self)
131        // TODO fix error to contain hex parse error
132        .map_err(|_| AddressParseError)
133    }
134
135    pub fn to_hex(&self) -> String {
136        self.to_string()
137    }
138
139    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, AddressParseError> {
140        <[u8; Self::LENGTH]>::try_from(bytes.as_ref())
141            .map_err(|_| AddressParseError)
142            .map(Self)
143    }
144}
145
146impl std::str::FromStr for Address {
147    type Err = AddressParseError;
148
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        Self::from_hex(s)
151    }
152}
153
154impl AsRef<[u8]> for Address {
155    fn as_ref(&self) -> &[u8] {
156        &self.0
157    }
158}
159
160impl AsRef<[u8; 32]> for Address {
161    fn as_ref(&self) -> &[u8; 32] {
162        &self.0
163    }
164}
165
166impl From<Address> for [u8; 32] {
167    fn from(address: Address) -> Self {
168        address.into_inner()
169    }
170}
171
172impl From<[u8; 32]> for Address {
173    fn from(address: [u8; 32]) -> Self {
174        Self::new(address)
175    }
176}
177
178impl From<Address> for Vec<u8> {
179    fn from(value: Address) -> Self {
180        value.0.to_vec()
181    }
182}
183
184impl From<super::ObjectId> for Address {
185    fn from(value: super::ObjectId) -> Self {
186        Self::new(value.into_inner())
187    }
188}
189
190impl std::fmt::Display for Address {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        write!(f, "0x")?;
193        for byte in &self.0 {
194            write!(f, "{byte:02x}")?;
195        }
196
197        Ok(())
198    }
199}
200
201impl std::fmt::Debug for Address {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        f.debug_tuple("Address")
204            .field(&format_args!("\"{self}\""))
205            .finish()
206    }
207}
208
209#[cfg(feature = "serde")]
210#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
211struct ReadableAddress;
212
213#[cfg(feature = "serde")]
214#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
215impl serde_with::SerializeAs<[u8; Address::LENGTH]> for ReadableAddress {
216    fn serialize_as<S>(source: &[u8; Address::LENGTH], serializer: S) -> Result<S::Ok, S::Error>
217    where
218        S: serde::Serializer,
219    {
220        let address = Address::new(*source);
221        serde_with::DisplayFromStr::serialize_as(&address, serializer)
222    }
223}
224
225#[cfg(feature = "serde")]
226#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
227impl<'de> serde_with::DeserializeAs<'de, [u8; Address::LENGTH]> for ReadableAddress {
228    fn deserialize_as<D>(deserializer: D) -> Result<[u8; Address::LENGTH], D::Error>
229    where
230        D: serde::Deserializer<'de>,
231    {
232        let address: Address = serde_with::DisplayFromStr::deserialize_as(deserializer)?;
233        Ok(address.into_inner())
234    }
235}
236
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
238pub struct AddressParseError;
239
240impl std::fmt::Display for AddressParseError {
241    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
242        write!(
243            f,
244            "Unable to parse Address (must be hex string of length {})",
245            2 * Address::LENGTH
246        )
247    }
248}
249
250impl std::error::Error for AddressParseError {}
251
252#[cfg(feature = "schemars")]
253impl schemars::JsonSchema for Address {
254    fn schema_name() -> String {
255        "Address".to_owned()
256    }
257
258    fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
259        use schemars::schema::{InstanceType, Metadata, SchemaObject, StringValidation};
260
261        let hex_length = Address::LENGTH * 2;
262        SchemaObject {
263            metadata: Some(Box::new(Metadata {
264                title: Some(Self::schema_name()),
265                description: Some("A 32-byte IOTA address, encoded as a hex string.".to_owned()),
266                examples: vec![serde_json::to_value(Address::FRAMEWORK).unwrap()],
267                ..Default::default()
268            })),
269            instance_type: Some(InstanceType::String.into()),
270            format: Some("hex".to_owned()),
271            string: Some(Box::new(StringValidation {
272                max_length: Some((hex_length + 2) as u32),
273                min_length: None,
274                pattern: Some(format!("0x[a-z0-9]{{1,{hex_length}}}")),
275            })),
276            ..Default::default()
277        }
278        .into()
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use test_strategy::proptest;
285    #[cfg(target_arch = "wasm32")]
286    use wasm_bindgen_test::wasm_bindgen_test as test;
287
288    use super::*;
289
290    #[test]
291    fn hex_parsing() {
292        let actual = Address::from_hex("0x2").unwrap();
293        let expected = "0x0000000000000000000000000000000000000000000000000000000000000002";
294
295        assert_eq!(actual.to_string(), expected);
296    }
297
298    #[test]
299    #[cfg(feature = "serde")]
300    fn formats() {
301        let actual = Address::from_hex("0x2").unwrap();
302
303        println!("{}", serde_json::to_string(&actual).unwrap());
304        println!("{:?}", bcs::to_bytes(&actual).unwrap());
305        let a: Address = serde_json::from_str("\"0x2\"").unwrap();
306        println!("{a}");
307    }
308
309    #[proptest]
310    fn roundtrip_display_fromstr(address: Address) {
311        let s = address.to_string();
312        let a = s.parse::<Address>().unwrap();
313        assert_eq!(address, a);
314    }
315}