tronz_primitives/
address.rs1use 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
15pub const ADDRESS_PREFIX: u8 = 0x41;
17
18pub const ADDRESS_LEN: usize = 21;
20
21pub const EVM_ADDRESS_LEN: usize = 20;
23
24#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub struct Address([u8; ADDRESS_LEN]);
27
28impl Address {
29 pub fn from_bytes(bytes: [u8; ADDRESS_LEN]) -> Result<Self, AddressError> {
31 if bytes[0] != ADDRESS_PREFIX {
32 return Err(AddressError::BadPrefix(bytes[0]));
33 }
34 Ok(Self(bytes))
35 }
36
37 pub fn from_slice(slice: &[u8]) -> Result<Self, AddressError> {
39 let bytes: [u8; ADDRESS_LEN] = slice
40 .try_into()
41 .map_err(|_| AddressError::BadLength { expected: ADDRESS_LEN, got: slice.len() })?;
42 Self::from_bytes(bytes)
43 }
44
45 pub fn from_evm_bytes(evm: [u8; EVM_ADDRESS_LEN]) -> Self {
47 let mut bytes = [0u8; ADDRESS_LEN];
48 bytes[0] = ADDRESS_PREFIX;
49 bytes[1..].copy_from_slice(&evm);
50 Self(bytes)
51 }
52
53 pub fn from_public_key(key: &VerifyingKey) -> Self {
57 let point = key.to_encoded_point(false);
58 let hash = keccak256(&point.as_bytes()[1..]);
61 let mut evm = [0u8; EVM_ADDRESS_LEN];
62 evm.copy_from_slice(&hash[12..]);
63 Self::from_evm_bytes(evm)
64 }
65
66 pub fn from_base58(s: &str) -> Result<Self, AddressError> {
68 let decoded = bs58::decode(s).with_check(None).into_vec()?;
69 Self::from_slice(&decoded)
70 }
71
72 pub fn from_hex(s: &str) -> Result<Self, AddressError> {
75 let s = s.strip_prefix("0x").unwrap_or(s);
76 let bytes = hex::decode(s)?;
77 Self::from_slice(&bytes)
78 }
79
80 pub fn as_bytes(&self) -> &[u8; ADDRESS_LEN] {
82 &self.0
83 }
84
85 pub fn as_evm_bytes(&self) -> &[u8; EVM_ADDRESS_LEN] {
88 self.0[1..].try_into().expect("address body is always 20 bytes")
89 }
90
91 pub fn to_base58(&self) -> String {
93 bs58::encode(&self.0).with_check().into_string()
94 }
95
96 pub fn to_hex(&self) -> String {
98 hex::encode(self.0)
99 }
100}
101
102impl fmt::Display for Address {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 f.write_str(&self.to_base58())
105 }
106}
107
108impl fmt::Debug for Address {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 write!(f, "Address({})", self.to_base58())
111 }
112}
113
114impl FromStr for Address {
115 type Err = AddressError;
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 let hexish = s.strip_prefix("0x").unwrap_or(s);
122 let looks_hex =
123 hexish.len() == ADDRESS_LEN * 2 && hexish.bytes().all(|b| b.is_ascii_hexdigit());
124 if looks_hex { Self::from_hex(s) } else { Self::from_base58(s) }
125 }
126}
127
128impl From<Address> for alloy_primitives::Address {
131 fn from(a: Address) -> Self {
132 alloy_primitives::Address::from(*a.as_evm_bytes())
133 }
134}
135
136impl From<alloy_primitives::Address> for Address {
137 fn from(a: alloy_primitives::Address) -> Self {
139 Address::from_evm_bytes(a.into_array())
140 }
141}
142
143impl Serialize for Address {
146 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
147 serializer.serialize_str(&self.to_base58())
148 }
149}
150
151impl<'de> Deserialize<'de> for Address {
152 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
153 let s = String::deserialize(deserializer)?;
154 s.parse().map_err(serde::de::Error::custom)
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 const B58: &str = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
164 const HEX: &str = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c";
165
166 #[test]
167 fn base58_roundtrip() {
168 let a = Address::from_base58(B58).unwrap();
169 assert_eq!(a.to_base58(), B58);
170 assert_eq!(a.to_hex(), HEX);
171 }
172
173 #[test]
174 fn hex_roundtrip() {
175 let a = Address::from_hex(HEX).unwrap();
176 assert_eq!(a.to_base58(), B58);
177 }
178
179 #[test]
180 fn fromstr_detects_format() {
181 assert_eq!(B58.parse::<Address>().unwrap().to_hex(), HEX);
182 assert_eq!(HEX.parse::<Address>().unwrap().to_base58(), B58);
183 let with_0x = format!("0x{HEX}");
184 assert_eq!(with_0x.parse::<Address>().unwrap().to_base58(), B58);
185 }
186
187 #[test]
188 fn bad_prefix_rejected() {
189 let mut bytes = [0u8; ADDRESS_LEN];
190 bytes[0] = 0x42;
191 assert!(matches!(Address::from_bytes(bytes), Err(AddressError::BadPrefix(0x42))));
192 }
193
194 #[test]
195 fn alloy_bridge_roundtrip() {
196 let a = Address::from_base58(B58).unwrap();
197 let evm: alloy_primitives::Address = a.into();
198 assert_eq!(evm.as_slice(), a.as_evm_bytes());
199 let back: Address = evm.into();
200 assert_eq!(back, a);
201 }
202
203 #[test]
204 fn evm_bytes_strip_prefix() {
205 let a = Address::from_hex(HEX).unwrap();
206 assert_eq!(a.as_evm_bytes().len(), 20);
207 assert_eq!(&a.as_bytes()[1..], a.as_evm_bytes());
208 }
209}