Skip to main content

tronz_primitives/
address.rs

1//! TRON address type.
2//!
3//! A TRON address is 21 bytes: a single `0x41` prefix byte followed by the
4//! 20-byte EVM-style address (`keccak256(pubkey)[12..]`). It is most commonly
5//! displayed in base58check form (the familiar `T...` string).
6
7use core::{fmt, str::FromStr};
8
9use alloy_primitives::keccak256;
10use k256::ecdsa::VerifyingKey;
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13use crate::error::AddressError;
14
15/// The TRON mainnet address prefix byte.
16pub const ADDRESS_PREFIX: u8 = 0x41;
17
18/// Length of a raw TRON address in bytes (prefix + 20-byte body).
19pub const ADDRESS_LEN: usize = 21;
20
21/// Length of the EVM-style address body (without the `0x41` prefix).
22pub const EVM_ADDRESS_LEN: usize = 20;
23
24/// A TRON network address (`0x41` prefix + 20-byte body).
25#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub struct Address([u8; ADDRESS_LEN]);
27
28impl Address {
29    /// The zero address (`0x41` prefix followed by 20 zero bytes).
30    pub const ZERO: Self = {
31        let mut bytes = [0u8; ADDRESS_LEN];
32        bytes[0] = ADDRESS_PREFIX;
33        Self(bytes)
34    };
35
36    /// Construct from the full 21-byte representation, validating the prefix.
37    pub fn from_bytes(bytes: [u8; ADDRESS_LEN]) -> Result<Self, AddressError> {
38        if bytes[0] != ADDRESS_PREFIX {
39            return Err(AddressError::BadPrefix(bytes[0]));
40        }
41        Ok(Self(bytes))
42    }
43
44    /// Construct from a 21-byte slice, validating length and prefix.
45    pub fn from_slice(slice: &[u8]) -> Result<Self, AddressError> {
46        let bytes: [u8; ADDRESS_LEN] = slice
47            .try_into()
48            .map_err(|_| AddressError::BadLength { expected: ADDRESS_LEN, got: slice.len() })?;
49        Self::from_bytes(bytes)
50    }
51
52    /// Construct from the 20-byte EVM-style body, prepending the `0x41` prefix.
53    pub fn from_evm_bytes(evm: [u8; EVM_ADDRESS_LEN]) -> Self {
54        let mut bytes = [0u8; ADDRESS_LEN];
55        bytes[0] = ADDRESS_PREFIX;
56        bytes[1..].copy_from_slice(&evm);
57        Self(bytes)
58    }
59
60    /// Derive the address from a secp256k1 public key.
61    ///
62    /// `address = 0x41 || keccak256(uncompressed_pubkey[1..])[12..]`
63    pub fn from_public_key(key: &VerifyingKey) -> Self {
64        let point = key.to_encoded_point(false);
65        // Uncompressed SEC1 encoding is `0x04 || X(32) || Y(32)`; hash the 64
66        // coordinate bytes, skipping the `0x04` tag.
67        let hash = keccak256(&point.as_bytes()[1..]);
68        let mut evm = [0u8; EVM_ADDRESS_LEN];
69        evm.copy_from_slice(&hash[12..]);
70        Self::from_evm_bytes(evm)
71    }
72
73    /// Parse a base58check (`T...`) address string.
74    pub fn from_base58(s: &str) -> Result<Self, AddressError> {
75        let decoded = bs58::decode(s).with_check(None).into_vec()?;
76        Self::from_slice(&decoded)
77    }
78
79    /// Parse a hex address string with an optional `0x` prefix.
80    ///
81    /// The encoded bytes must include the TRON `0x41` address prefix.
82    pub fn from_hex(s: &str) -> Result<Self, AddressError> {
83        let s = s.strip_prefix("0x").unwrap_or(s);
84        let bytes = hex::decode(s)?;
85        Self::from_slice(&bytes)
86    }
87
88    /// The full 21-byte representation, including the `0x41` prefix.
89    pub fn as_bytes(&self) -> &[u8; ADDRESS_LEN] {
90        &self.0
91    }
92
93    /// The 20-byte EVM-style body (prefix stripped). Use this when bridging to
94    /// `alloy` / ABI encoding.
95    pub fn as_evm_bytes(&self) -> &[u8; EVM_ADDRESS_LEN] {
96        self.0[1..].try_into().expect("address body is always 20 bytes")
97    }
98
99    /// Encode as a base58check (`T...`) string.
100    pub fn to_base58(&self) -> String {
101        bs58::encode(&self.0).with_check().into_string()
102    }
103
104    /// Encode as a lowercase hex string including the `0x41` prefix (no `0x`).
105    pub fn to_hex(&self) -> String {
106        hex::encode(self.0)
107    }
108}
109
110impl TryFrom<[u8; ADDRESS_LEN]> for Address {
111    type Error = AddressError;
112
113    fn try_from(bytes: [u8; ADDRESS_LEN]) -> Result<Self, Self::Error> {
114        Self::from_bytes(bytes)
115    }
116}
117
118impl TryFrom<&[u8]> for Address {
119    type Error = AddressError;
120
121    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
122        Self::from_slice(bytes)
123    }
124}
125
126impl From<Address> for [u8; ADDRESS_LEN] {
127    fn from(address: Address) -> Self {
128        address.0
129    }
130}
131
132impl From<&Address> for [u8; ADDRESS_LEN] {
133    fn from(address: &Address) -> Self {
134        address.0
135    }
136}
137
138impl AsRef<[u8]> for Address {
139    fn as_ref(&self) -> &[u8] {
140        &self.0
141    }
142}
143
144impl Default for Address {
145    fn default() -> Self {
146        Self::ZERO
147    }
148}
149
150impl fmt::Display for Address {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        f.write_str(&self.to_base58())
153    }
154}
155
156impl fmt::Debug for Address {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        write!(f, "Address({})", self.to_base58())
159    }
160}
161
162impl FromStr for Address {
163    type Err = AddressError;
164
165    /// Accepts either a base58check (`T...`) or a hex (`41...` / `0x41...`)
166    /// address. Hex is detected when every character is a hex digit and the
167    /// string is the right length for a 21-byte address.
168    fn from_str(s: &str) -> Result<Self, Self::Err> {
169        let hexish = s.strip_prefix("0x").unwrap_or(s);
170        let looks_hex =
171            hexish.len() == ADDRESS_LEN * 2 && hexish.bytes().all(|b| b.is_ascii_hexdigit());
172        if looks_hex { Self::from_hex(s) } else { Self::from_base58(s) }
173    }
174}
175
176// --- alloy bridging ---------------------------------------------------------
177
178impl From<Address> for alloy_primitives::Address {
179    fn from(a: Address) -> Self {
180        alloy_primitives::Address::from(*a.as_evm_bytes())
181    }
182}
183
184impl From<alloy_primitives::Address> for Address {
185    /// Re-attaches the TRON mainnet `0x41` prefix to a 20-byte EVM address.
186    fn from(a: alloy_primitives::Address) -> Self {
187        Address::from_evm_bytes(a.into_array())
188    }
189}
190
191// --- serde ------------------------------------------------------------------
192
193impl Serialize for Address {
194    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
195        serializer.serialize_str(&self.to_base58())
196    }
197}
198
199impl<'de> Deserialize<'de> for Address {
200    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
201        let s = String::deserialize(deserializer)?;
202        s.parse().map_err(serde::de::Error::custom)
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    // Well-known TRON address used widely in docs/tests.
211    const B58: &str = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
212    const HEX: &str = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c";
213
214    #[test]
215    fn base58_roundtrip() {
216        let a = Address::from_base58(B58).unwrap();
217        assert_eq!(a.to_base58(), B58);
218        assert_eq!(a.to_hex(), HEX);
219    }
220
221    #[test]
222    fn hex_roundtrip() {
223        let a = Address::from_hex(HEX).unwrap();
224        assert_eq!(a.to_base58(), B58);
225    }
226
227    #[test]
228    fn fromstr_detects_format() {
229        assert_eq!(B58.parse::<Address>().unwrap().to_hex(), HEX);
230        assert_eq!(HEX.parse::<Address>().unwrap().to_base58(), B58);
231        let with_0x = format!("0x{HEX}");
232        assert_eq!(with_0x.parse::<Address>().unwrap().to_base58(), B58);
233    }
234
235    #[test]
236    fn bad_prefix_rejected() {
237        let mut bytes = [0u8; ADDRESS_LEN];
238        bytes[0] = 0x42;
239        assert!(matches!(Address::from_bytes(bytes), Err(AddressError::BadPrefix(0x42))));
240    }
241
242    #[test]
243    fn alloy_bridge_roundtrip() {
244        let a = Address::from_base58(B58).unwrap();
245        let evm: alloy_primitives::Address = a.into();
246        assert_eq!(evm.as_slice(), a.as_evm_bytes());
247        let back: Address = evm.into();
248        assert_eq!(back, a);
249    }
250
251    #[test]
252    fn evm_bytes_strip_prefix() {
253        let a = Address::from_hex(HEX).unwrap();
254        assert_eq!(a.as_evm_bytes().len(), 20);
255        assert_eq!(&a.as_bytes()[1..], a.as_evm_bytes());
256    }
257
258    #[test]
259    fn standard_byte_conversions() {
260        let address = Address::from_hex(HEX).unwrap();
261        let bytes: [u8; ADDRESS_LEN] = address.into();
262
263        assert_eq!(Address::try_from(bytes).unwrap(), address);
264        assert_eq!(Address::try_from(bytes.as_slice()).unwrap(), address);
265        assert_eq!(<[u8; ADDRESS_LEN]>::from(&address), bytes);
266        assert_eq!(address.as_ref(), bytes.as_slice());
267        assert_eq!(Address::default(), Address::ZERO);
268    }
269}