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.try_into().map_err(|_| AddressError::BadLength {
40 expected: ADDRESS_LEN,
41 got: slice.len(),
42 })?;
43 Self::from_bytes(bytes)
44 }
45
46 pub fn from_evm_bytes(evm: [u8; EVM_ADDRESS_LEN]) -> Self {
48 let mut bytes = [0u8; ADDRESS_LEN];
49 bytes[0] = ADDRESS_PREFIX;
50 bytes[1..].copy_from_slice(&evm);
51 Self(bytes)
52 }
53
54 pub fn from_public_key(key: &VerifyingKey) -> Self {
58 let point = key.to_encoded_point(false);
59 let hash = keccak256(&point.as_bytes()[1..]);
62 let mut evm = [0u8; EVM_ADDRESS_LEN];
63 evm.copy_from_slice(&hash[12..]);
64 Self::from_evm_bytes(evm)
65 }
66
67 pub fn from_base58(s: &str) -> Result<Self, AddressError> {
69 let decoded = bs58::decode(s).with_check(None).into_vec()?;
70 Self::from_slice(&decoded)
71 }
72
73 pub fn from_hex(s: &str) -> Result<Self, AddressError> {
76 let s = s.strip_prefix("0x").unwrap_or(s);
77 let bytes = hex::decode(s)?;
78 Self::from_slice(&bytes)
79 }
80
81 pub fn as_bytes(&self) -> &[u8; ADDRESS_LEN] {
83 &self.0
84 }
85
86 pub fn as_evm_bytes(&self) -> &[u8; EVM_ADDRESS_LEN] {
89 self.0[1..]
90 .try_into()
91 .expect("address body is always 20 bytes")
92 }
93
94 pub fn to_base58(&self) -> String {
96 bs58::encode(&self.0).with_check().into_string()
97 }
98
99 pub fn to_hex(&self) -> String {
101 hex::encode(self.0)
102 }
103}
104
105impl fmt::Display for Address {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 f.write_str(&self.to_base58())
108 }
109}
110
111impl fmt::Debug for Address {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "Address({})", self.to_base58())
114 }
115}
116
117impl FromStr for Address {
118 type Err = AddressError;
119
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
124 let hexish = s.strip_prefix("0x").unwrap_or(s);
125 let looks_hex =
126 hexish.len() == ADDRESS_LEN * 2 && hexish.bytes().all(|b| b.is_ascii_hexdigit());
127 if looks_hex {
128 Self::from_hex(s)
129 } else {
130 Self::from_base58(s)
131 }
132 }
133}
134
135impl From<Address> for alloy_primitives::Address {
138 fn from(a: Address) -> Self {
139 alloy_primitives::Address::from(*a.as_evm_bytes())
140 }
141}
142
143impl From<alloy_primitives::Address> for Address {
144 fn from(a: alloy_primitives::Address) -> Self {
146 Address::from_evm_bytes(a.into_array())
147 }
148}
149
150impl Serialize for Address {
153 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
154 serializer.serialize_str(&self.to_base58())
155 }
156}
157
158impl<'de> Deserialize<'de> for Address {
159 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
160 let s = String::deserialize(deserializer)?;
161 s.parse().map_err(serde::de::Error::custom)
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 const B58: &str = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
171 const HEX: &str = "41a614f803b6fd780986a42c78ec9c7f77e6ded13c";
172
173 #[test]
174 fn base58_roundtrip() {
175 let a = Address::from_base58(B58).unwrap();
176 assert_eq!(a.to_base58(), B58);
177 assert_eq!(a.to_hex(), HEX);
178 }
179
180 #[test]
181 fn hex_roundtrip() {
182 let a = Address::from_hex(HEX).unwrap();
183 assert_eq!(a.to_base58(), B58);
184 }
185
186 #[test]
187 fn fromstr_detects_format() {
188 assert_eq!(B58.parse::<Address>().unwrap().to_hex(), HEX);
189 assert_eq!(HEX.parse::<Address>().unwrap().to_base58(), B58);
190 let with_0x = format!("0x{HEX}");
191 assert_eq!(with_0x.parse::<Address>().unwrap().to_base58(), B58);
192 }
193
194 #[test]
195 fn bad_prefix_rejected() {
196 let mut bytes = [0u8; ADDRESS_LEN];
197 bytes[0] = 0x42;
198 assert!(matches!(
199 Address::from_bytes(bytes),
200 Err(AddressError::BadPrefix(0x42))
201 ));
202 }
203
204 #[test]
205 fn alloy_bridge_roundtrip() {
206 let a = Address::from_base58(B58).unwrap();
207 let evm: alloy_primitives::Address = a.into();
208 assert_eq!(evm.as_slice(), a.as_evm_bytes());
209 let back: Address = evm.into();
210 assert_eq!(back, a);
211 }
212
213 #[test]
214 fn evm_bytes_strip_prefix() {
215 let a = Address::from_hex(HEX).unwrap();
216 assert_eq!(a.as_evm_bytes().len(), 20);
217 assert_eq!(&a.as_bytes()[1..], a.as_evm_bytes());
218 }
219}