Skip to main content

sui_sdk_types/
address.rs

1/// Unique identifier for an Account on the Sui blockchain.
2///
3/// An `Address` is a 32-byte pseudonymous identifier used to uniquely identify an account and
4/// asset-ownership on the Sui blockchain. Often, human-readable addresses are encoded in
5/// hexadecimal with a `0x` prefix. For example, this is a valid Sui address:
6/// `0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331`.
7///
8/// ```
9/// use sui_sdk_types::Address;
10///
11/// let hex = "0x02a212de6a9dfa3a69e22387acfbafbb1a9e591bd9d636e7895dcfc8de05f331";
12/// let address = Address::from_hex(hex).unwrap();
13/// println!("Address: {}", address);
14/// assert_eq!(hex, address.to_string());
15/// ```
16///
17/// # Deriving an account Address
18///
19/// Account addresses are cryptographically derived from a number of user account authenticators,
20/// the simplest of which is an [`Ed25519PublicKey`](crate::Ed25519PublicKey).
21///
22/// Deriving an address consists of the Blake2b256 hash of the sequence of bytes of its
23/// corresponding authenticator, prefixed with a domain-separator. For each authenticator, this
24/// domain-separator is the single byte-value of its [`SignatureScheme`](crate::SignatureScheme)
25/// flag. E.g. `hash(signature schema flag || authenticator bytes)`.
26///
27/// Each authenticator includes a convince method for deriving its `Address` as well as
28/// documentation for the specifics of how the derivation is done. See
29/// [`Ed25519PublicKey::derive_address`] for an example.
30///
31/// [`Ed25519PublicKey::derive_address`]: crate::Ed25519PublicKey::derive_address
32///
33/// # Usage as ObjectIds
34///
35/// `Address`es are also used as a way to uniquely identify an [`Object`]. When an `Address` is
36/// used as an object identifierit can also be referred to as an `ObjectId`. `ObjectId`s and
37/// account `Address`es share the same 32-byte addressable space but are derived leveraging
38/// different domain-separator values to ensure, cryptographically, that there won't be any
39/// overlap, e.g. there can't be a valid `Object` whose `ObjectId` is equal to that of the
40/// `Address` of a user account.
41///
42/// [`Object`]: crate::Object
43///
44/// # BCS
45///
46/// An `Address`'s BCS serialized form is defined by the following:
47///
48/// ```text
49/// address = 32OCTET
50/// ```
51#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
52#[cfg_attr(
53    feature = "serde",
54    derive(serde_derive::Serialize, serde_derive::Deserialize)
55)]
56#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
57#[doc(alias = "ObjectId")]
58pub struct Address(
59    #[cfg_attr(
60        feature = "serde",
61        serde(with = "::serde_with::As::<::serde_with::IfIsHumanReadable<ReadableAddress>>")
62    )]
63    [u8; Self::LENGTH],
64);
65
66impl Address {
67    pub const LENGTH: usize = 32;
68    pub const ZERO: Self = Self([0u8; Self::LENGTH]);
69    pub const TWO: Self = Self::from_u8(2);
70    pub const THREE: Self = Self::from_u8(3);
71
72    pub const fn new(bytes: [u8; Self::LENGTH]) -> Self {
73        Self(bytes)
74    }
75
76    const fn from_u8(byte: u8) -> Self {
77        let mut address = Self::ZERO;
78        address.0[31] = byte;
79        address
80    }
81
82    #[cfg(feature = "rand")]
83    #[cfg_attr(doc_cfg, doc(cfg(feature = "rand")))]
84    pub fn generate<R>(mut rng: R) -> Self
85    where
86        R: rand_core::RngCore + rand_core::CryptoRng,
87    {
88        let mut buf: [u8; Self::LENGTH] = [0; Self::LENGTH];
89        rng.fill_bytes(&mut buf);
90        Self::new(buf)
91    }
92
93    /// Return the underlying byte array of a Address.
94    pub const fn into_inner(self) -> [u8; Self::LENGTH] {
95        self.0
96    }
97
98    pub const fn inner(&self) -> &[u8; Self::LENGTH] {
99        &self.0
100    }
101
102    pub const fn as_bytes(&self) -> &[u8] {
103        &self.0
104    }
105
106    /// Decodes an address from a hex encoded string.
107    pub fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, AddressParseError> {
108        let hex = hex.as_ref();
109
110        hex_address_bytes(hex)
111            .map(Self)
112            .map_err(|e| AddressParseError { hex_error: Some(e) })
113    }
114
115    /// Decodes an address from a hex encoded &'static str.
116    ///
117    /// Similar to `from_hex` except any errors are unwrapped, turning them into panics.
118    pub const fn from_static(hex: &'static str) -> Self {
119        match hex_address_bytes(hex.as_bytes()) {
120            Ok(address) => Self(address),
121            Err(e) => e.const_panic(),
122        }
123    }
124
125    pub fn to_hex(&self) -> String {
126        self.to_string()
127    }
128
129    pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, AddressParseError> {
130        <[u8; Self::LENGTH]>::try_from(bytes.as_ref())
131            .map_err(|_| AddressParseError { hex_error: None })
132            .map(Self)
133    }
134}
135
136impl std::str::FromStr for Address {
137    type Err = AddressParseError;
138
139    fn from_str(s: &str) -> Result<Self, Self::Err> {
140        Self::from_hex(s)
141    }
142}
143
144impl AsRef<[u8]> for Address {
145    fn as_ref(&self) -> &[u8] {
146        &self.0
147    }
148}
149
150impl AsRef<[u8; 32]> for Address {
151    fn as_ref(&self) -> &[u8; 32] {
152        &self.0
153    }
154}
155
156impl From<Address> for [u8; 32] {
157    fn from(address: Address) -> Self {
158        address.into_inner()
159    }
160}
161
162impl From<[u8; 32]> for Address {
163    fn from(address: [u8; 32]) -> Self {
164        Self::new(address)
165    }
166}
167
168impl From<Address> for Vec<u8> {
169    fn from(value: Address) -> Self {
170        value.0.to_vec()
171    }
172}
173
174impl From<&Address> for Vec<u8> {
175    fn from(value: &Address) -> Self {
176        value.0.to_vec()
177    }
178}
179
180impl From<Address> for String {
181    fn from(value: Address) -> Self {
182        value.to_string()
183    }
184}
185
186impl From<&Address> for String {
187    fn from(value: &Address) -> Self {
188        value.to_string()
189    }
190}
191
192impl std::fmt::Display for Address {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        write!(f, "0x")?;
195        for byte in &self.0 {
196            write!(f, "{byte:02x}")?;
197        }
198
199        Ok(())
200    }
201}
202
203impl std::fmt::Debug for Address {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        f.debug_tuple("Address")
206            .field(&format_args!("\"{self}\""))
207            .finish()
208    }
209}
210
211#[cfg(feature = "serde")]
212#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
213struct ReadableAddress;
214
215#[cfg(feature = "serde")]
216#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
217impl serde_with::SerializeAs<[u8; Address::LENGTH]> for ReadableAddress {
218    fn serialize_as<S>(source: &[u8; Address::LENGTH], serializer: S) -> Result<S::Ok, S::Error>
219    where
220        S: serde::Serializer,
221    {
222        let address = Address::new(*source);
223        serde_with::DisplayFromStr::serialize_as(&address, serializer)
224    }
225}
226
227#[cfg(feature = "serde")]
228#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
229impl<'de> serde_with::DeserializeAs<'de, [u8; Address::LENGTH]> for ReadableAddress {
230    fn deserialize_as<D>(deserializer: D) -> Result<[u8; Address::LENGTH], D::Error>
231    where
232        D: serde::Deserializer<'de>,
233    {
234        let address: Address = serde_with::DisplayFromStr::deserialize_as(deserializer)?;
235        Ok(address.into_inner())
236    }
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq)]
240pub struct AddressParseError {
241    hex_error: Option<HexDecodeError>,
242}
243
244impl std::fmt::Display for AddressParseError {
245    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
246        write!(
247            f,
248            "Unable to parse Address (must be hex string of length {})",
249            Address::LENGTH
250        )
251    }
252}
253
254impl std::error::Error for AddressParseError {}
255
256#[allow(unused)]
257#[derive(Clone, Copy, Debug, PartialEq, Eq)]
258enum HexDecodeError {
259    EmptyInput,
260    InputTooLong(usize),
261    InvalidHexCharacter(u8),
262}
263
264impl HexDecodeError {
265    const fn const_panic(&self) -> ! {
266        match self {
267            HexDecodeError::EmptyInput => panic!("input hex string must be non-empty"),
268            HexDecodeError::InputTooLong(_) => panic!("input hex string is too long for address"),
269            HexDecodeError::InvalidHexCharacter(_) => {
270                panic!("input hex string has wrong character")
271            }
272        }
273    }
274}
275
276/// 32-byte address from a hex byte vector, optionally `0x`-prefixed.
277const fn hex_address_bytes(bytes: &[u8]) -> Result<[u8; Address::LENGTH], HexDecodeError> {
278    if bytes.is_empty() {
279        return Err(HexDecodeError::EmptyInput);
280    }
281    let hex = remove_0x_prefix(bytes);
282    // A bare `"0x"` is semantically the same as an empty input: there are
283    // no hex digits to decode. Without this check the function would
284    // silently return `Address::ZERO`, which is inconsistent with the
285    // already-rejected empty-input case and turns a typo (e.g. embedding
286    // `Address::from_static("0x")` instead of the intended literal) into
287    // a silent zero address rather than a compile-time panic.
288    if hex.is_empty() {
289        return Err(HexDecodeError::EmptyInput);
290    }
291    if hex.len() > 64 {
292        return Err(HexDecodeError::InputTooLong(hex.len()));
293    }
294
295    // Decode the hex input from back to front
296    let mut buffer = [0; Address::LENGTH];
297    let mut i = hex.len();
298    let mut j = buffer.len();
299    while i >= 2 {
300        let lo = HEX_DECODE_LUT[hex[i - 1] as usize];
301        let hi = HEX_DECODE_LUT[hex[i - 2] as usize];
302        if lo == NIL {
303            return Err(HexDecodeError::InvalidHexCharacter(hex[i - 1]));
304        }
305        if hi == NIL {
306            return Err(HexDecodeError::InvalidHexCharacter(hex[i - 2]));
307        }
308        buffer[j - 1] = (hi << 4) | lo;
309        i -= 2;
310        j -= 1;
311    }
312    if i == 1 {
313        let lo = HEX_DECODE_LUT[hex[0] as usize];
314        if lo == NIL {
315            return Err(HexDecodeError::InvalidHexCharacter(hex[0]));
316        }
317        buffer[j - 1] = lo;
318    }
319    Ok(buffer)
320}
321
322/// Removes initial "0x" prefix if any.
323const fn remove_0x_prefix(hex: &[u8]) -> &[u8] {
324    if let Some((two, hex2)) = hex.split_first_chunk::<2>()
325        && two[0] == b'0'
326        && two[1] == b'x'
327    {
328        return hex2;
329    }
330    hex
331}
332
333/// The lookup table of hex byte to value, used for hex decoding.
334///
335/// [`NIL`] is used for invalid values.
336const HEX_DECODE_LUT: &[u8; 256] = &make_decode_lut();
337
338/// Represents an invalid value in the [`HEX_DECODE_LUT`] table.
339const NIL: u8 = u8::MAX;
340
341const fn make_decode_lut() -> [u8; 256] {
342    let mut lut = [0; 256];
343    let mut i = 0u8;
344    loop {
345        lut[i as usize] = match i {
346            b'0'..=b'9' => i - b'0',
347            b'A'..=b'F' => i - b'A' + 10,
348            b'a'..=b'f' => i - b'a' + 10,
349            // use max value for invalid characters
350            _ => NIL,
351        };
352        if i == NIL {
353            break;
354        }
355        i += 1;
356    }
357    lut
358}
359
360#[cfg(test)]
361mod test {
362    use super::*;
363    use test_strategy::proptest;
364
365    #[cfg(target_arch = "wasm32")]
366    use wasm_bindgen_test::wasm_bindgen_test as test;
367
368    #[test]
369    fn hex_parsing() {
370        let actual = Address::from_hex("0x2").unwrap();
371        let expected = "0x0000000000000000000000000000000000000000000000000000000000000002";
372
373        assert_eq!(actual.to_string(), expected);
374    }
375
376    #[test]
377    #[cfg(feature = "serde")]
378    fn formats() {
379        let actual = Address::from_hex("0x2").unwrap();
380
381        println!("{}", serde_json::to_string(&actual).unwrap());
382        println!("{:?}", bcs::to_bytes(&actual).unwrap());
383        let a: Address = serde_json::from_str("\"0x2\"").unwrap();
384        println!("{a}");
385    }
386
387    #[proptest]
388    fn roundtrip_display_fromstr(address: Address) {
389        let s = address.to_string();
390        let a = s.parse::<Address>().unwrap();
391        assert_eq!(address, a);
392    }
393
394    // Regression: a bare `"0x"` used to decode to `Address::ZERO` while the
395    // empty string `""` was rejected with `EmptyInput`. The two inputs are
396    // semantically identical (both contain zero hex digits) and so should
397    // produce the same outcome. Without this check, a typo such as
398    // `Address::from_static("0x")` — meant to be a real address literal but
399    // accidentally truncated — would silently produce the zero address
400    // instead of a clear compile-time panic, and `Address::from_hex("0x")`
401    // would silently succeed at runtime.
402    #[test]
403    fn rejects_empty_after_strip() {
404        let bare_prefix = Address::from_hex("0x");
405        let empty = Address::from_hex("");
406        assert!(
407            bare_prefix.is_err(),
408            "`0x` must be rejected like empty input"
409        );
410        assert!(empty.is_err());
411        assert_eq!(bare_prefix.unwrap_err(), empty.unwrap_err());
412    }
413
414    #[test]
415    fn rejects_empty_input() {
416        assert!(Address::from_hex("").is_err());
417    }
418
419    // `Address::from_static` is a `const fn` that unwraps the result of
420    // `hex_address_bytes`. A bare `"0x"` literal must therefore panic at
421    // const-evaluation rather than silently produce `Address::ZERO`.
422    #[test]
423    #[should_panic(expected = "input hex string must be non-empty")]
424    fn from_static_bare_prefix_panics() {
425        let _ = Address::from_static("0x");
426    }
427
428    // Sanity: real hex literals (including odd-length and minimal) still
429    // round-trip as before.
430    #[test]
431    fn short_hex_inputs_still_parse() {
432        for s in ["0x0", "0x1", "0x00", "0xf", "0xff"] {
433            assert!(Address::from_hex(s).is_ok(), "expected {s} to parse");
434        }
435    }
436}