tronz_primitives/
address.rs1use core::fmt;
8use core::str::FromStr;
9
10use alloy_primitives::keccak256;
11use k256::ecdsa::VerifyingKey;
12use serde::{Deserialize, Deserializer, Serialize, Serializer};
13
14use crate::error::AddressError;
15
16pub const ADDRESS_PREFIX: u8 = 0x41;
18
19pub const ADDRESS_LEN: usize = 21;
21
22pub const EVM_ADDRESS_LEN: usize = 20;
24
25#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
27pub struct Address([u8; ADDRESS_LEN]);
28
29impl Address {
30 pub fn from_bytes(bytes: [u8; ADDRESS_LEN]) -> Result<Self, AddressError> {
32 if bytes[0] != ADDRESS_PREFIX {
33 return Err(AddressError::BadPrefix(bytes[0]));
34 }
35 Ok(Self(bytes))
36 }
37
38 pub fn from_slice(slice: &[u8]) -> Result<Self, AddressError> {
40 let bytes: [u8; ADDRESS_LEN] = slice.try_into().map_err(|_| AddressError::BadLength {
41 expected: ADDRESS_LEN,
42 got: slice.len(),
43 })?;
44 Self::from_bytes(bytes)
45 }
46
47 pub fn from_evm_bytes(evm: [u8; EVM_ADDRESS_LEN]) -> Self {
49 let mut bytes = [0u8; ADDRESS_LEN];
50 bytes[0] = ADDRESS_PREFIX;
51 bytes[1..].copy_from_slice(&evm);
52 Self(bytes)
53 }
54
55 pub fn from_public_key(key: &VerifyingKey) -> Self {
59 let point = key.to_encoded_point(false);
60 let hash = keccak256(&point.as_bytes()[1..]);
63 let mut evm = [0u8; EVM_ADDRESS_LEN];
64 evm.copy_from_slice(&hash[12..]);
65 Self::from_evm_bytes(evm)
66 }
67
68 pub fn from_base58(s: &str) -> Result<Self, AddressError> {
70 let decoded = bs58::decode(s).with_check(None).into_vec()?;
71 Self::from_slice(&decoded)
72 }
73
74 pub fn from_hex(s: &str) -> Result<Self, AddressError> {
77 let s = s.strip_prefix("0x").unwrap_or(s);
78 let bytes = hex::decode(s)?;
79 Self::from_slice(&bytes)
80 }
81
82 pub fn as_bytes(&self) -> &[u8; ADDRESS_LEN] {
84 &self.0
85 }
86
87 pub fn as_evm_bytes(&self) -> &[u8; EVM_ADDRESS_LEN] {
90 self.0[1..]
91 .try_into()
92 .expect("address body is always 20 bytes")
93 }
94
95 pub fn to_base58(&self) -> String {
97 bs58::encode(&self.0).with_check().into_string()
98 }
99
100 pub fn to_hex(&self) -> String {
102 hex::encode(self.0)
103 }
104}
105
106impl fmt::Display for Address {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 f.write_str(&self.to_base58())
109 }
110}
111
112impl fmt::Debug for Address {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "Address({})", self.to_base58())
115 }
116}
117
118impl FromStr for Address {
119 type Err = AddressError;
120
121 fn from_str(s: &str) -> Result<Self, Self::Err> {
125 let hexish = s.strip_prefix("0x").unwrap_or(s);
126 let looks_hex = hexish.len() == ADDRESS_LEN * 2
127 && hexish.bytes().all(|b| b.is_ascii_hexdigit());
128 if looks_hex {
129 Self::from_hex(s)
130 } else {
131 Self::from_base58(s)
132 }
133 }
134}
135
136impl From<Address> for alloy_primitives::Address {
139 fn from(a: Address) -> Self {
140 alloy_primitives::Address::from(*a.as_evm_bytes())
141 }
142}
143
144impl From<alloy_primitives::Address> for Address {
145 fn from(a: alloy_primitives::Address) -> Self {
147 Address::from_evm_bytes(a.into_array())
148 }
149}
150
151impl Serialize for Address {
154 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
155 serializer.serialize_str(&self.to_base58())
156 }
157}
158
159impl<'de> Deserialize<'de> for Address {
160 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
161 let s = String::deserialize(deserializer)?;
162 s.parse().map_err(serde::de::Error::custom)
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 const B58: &str = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
172 const HEX: &str = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c";
173
174 #[test]
175 fn base58_roundtrip() {
176 let a = Address::from_base58(B58).unwrap();
177 assert_eq!(a.to_base58(), B58);
178 assert_eq!(a.to_hex(), HEX);
179 }
180
181 #[test]
182 fn hex_roundtrip() {
183 let a = Address::from_hex(HEX).unwrap();
184 assert_eq!(a.to_base58(), B58);
185 }
186
187 #[test]
188 fn fromstr_detects_format() {
189 assert_eq!(B58.parse::<Address>().unwrap().to_hex(), HEX);
190 assert_eq!(HEX.parse::<Address>().unwrap().to_base58(), B58);
191 let with_0x = format!("0x{HEX}");
192 assert_eq!(with_0x.parse::<Address>().unwrap().to_base58(), B58);
193 }
194
195 #[test]
196 fn bad_prefix_rejected() {
197 let mut bytes = [0u8; ADDRESS_LEN];
198 bytes[0] = 0x42;
199 assert!(matches!(
200 Address::from_bytes(bytes),
201 Err(AddressError::BadPrefix(0x42))
202 ));
203 }
204
205 #[test]
206 fn alloy_bridge_roundtrip() {
207 let a = Address::from_base58(B58).unwrap();
208 let evm: alloy_primitives::Address = a.into();
209 assert_eq!(evm.as_slice(), a.as_evm_bytes());
210 let back: Address = evm.into();
211 assert_eq!(back, a);
212 }
213
214 #[test]
215 fn evm_bytes_strip_prefix() {
216 let a = Address::from_hex(HEX).unwrap();
217 assert_eq!(a.as_evm_bytes().len(), 20);
218 assert_eq!(&a.as_bytes()[1..], a.as_evm_bytes());
219 }
220}