1use std::{fmt, io};
4
5use crate::{
6 parameters::NetworkKind,
7 serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
8 transparent::{opcodes::OpCode, Script},
9};
10
11#[cfg(test)]
12use proptest::prelude::*;
13use zcash_address::{ToAddress, ZcashAddress};
14
15#[derive(
28 Clone, Eq, PartialEq, Hash, serde_with::SerializeDisplay, serde_with::DeserializeFromStr,
29)]
30pub enum Address {
31 PayToScriptHash {
33 network_kind: NetworkKind,
35 script_hash: [u8; 20],
37 },
38
39 PayToPublicKeyHash {
41 network_kind: NetworkKind,
43 pub_key_hash: [u8; 20],
46 },
47
48 Tex {
52 network_kind: NetworkKind,
54 validating_key_hash: [u8; 20],
56 },
57}
58
59impl From<Address> for ZcashAddress {
60 fn from(taddr: Address) -> Self {
61 match taddr {
62 Address::PayToScriptHash {
63 network_kind,
64 script_hash,
65 } => ZcashAddress::from_transparent_p2sh(network_kind.into(), script_hash),
66 Address::PayToPublicKeyHash {
67 network_kind,
68 pub_key_hash,
69 } => ZcashAddress::from_transparent_p2pkh(network_kind.into(), pub_key_hash),
70 Address::Tex {
71 network_kind,
72 validating_key_hash,
73 } => ZcashAddress::from_tex(network_kind.into(), validating_key_hash),
74 }
75 }
76}
77
78impl fmt::Debug for Address {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 let mut debug_struct = f.debug_struct("TransparentAddress");
81
82 match self {
83 Address::PayToScriptHash {
84 network_kind,
85 script_hash,
86 } => debug_struct
87 .field("network_kind", network_kind)
88 .field("script_hash", &hex::encode(script_hash))
89 .finish(),
90 Address::PayToPublicKeyHash {
91 network_kind,
92 pub_key_hash,
93 } => debug_struct
94 .field("network_kind", network_kind)
95 .field("pub_key_hash", &hex::encode(pub_key_hash))
96 .finish(),
97 Address::Tex {
98 network_kind,
99 validating_key_hash,
100 } => debug_struct
101 .field("network_kind", network_kind)
102 .field("validating_key_hash", &hex::encode(validating_key_hash))
103 .finish(),
104 }
105 }
106}
107
108impl fmt::Display for Address {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 let mut bytes = io::Cursor::new(Vec::new());
111 let _ = self.zcash_serialize(&mut bytes);
112
113 f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string())
114 }
115}
116
117impl std::str::FromStr for Address {
118 type Err = SerializationError;
119
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 if let Ok(data) = bs58::decode(s).with_check(None).into_vec() {
123 return Address::zcash_deserialize(&data[..]);
124 }
125
126 let (hrp, payload) =
128 bech32::decode(s).map_err(|_| SerializationError::Parse("invalid Bech32 encoding"))?;
129
130 if payload.len() != 20 {
138 return Err(SerializationError::Parse("unexpected payload length"));
139 }
140
141 let mut hash_bytes = [0u8; 20];
142 hash_bytes.copy_from_slice(&payload);
143
144 match hrp.as_str() {
145 zcash_primitives::constants::mainnet::HRP_TEX_ADDRESS => Ok(Address::Tex {
146 network_kind: NetworkKind::Mainnet,
147 validating_key_hash: hash_bytes,
148 }),
149
150 zcash_primitives::constants::testnet::HRP_TEX_ADDRESS => Ok(Address::Tex {
151 network_kind: NetworkKind::Testnet,
152 validating_key_hash: hash_bytes,
153 }),
154
155 _ => Err(SerializationError::Parse("unknown Bech32 HRP")),
156 }
157 }
158}
159
160impl ZcashSerialize for Address {
161 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
162 match self {
163 Address::PayToScriptHash {
164 network_kind,
165 script_hash,
166 } => {
167 writer.write_all(&network_kind.b58_script_address_prefix())?;
168 writer.write_all(script_hash)?
169 }
170 Address::PayToPublicKeyHash {
171 network_kind,
172 pub_key_hash,
173 } => {
174 writer.write_all(&network_kind.b58_pubkey_address_prefix())?;
175 writer.write_all(pub_key_hash)?
176 }
177 Address::Tex {
178 network_kind,
179 validating_key_hash,
180 } => {
181 writer.write_all(&network_kind.tex_address_prefix())?;
182 writer.write_all(validating_key_hash)?
183 }
184 }
185
186 Ok(())
187 }
188}
189
190impl ZcashDeserialize for Address {
191 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
192 let mut version_bytes = [0; 2];
193 reader.read_exact(&mut version_bytes)?;
194
195 let mut hash_bytes = [0; 20];
196 reader.read_exact(&mut hash_bytes)?;
197
198 match version_bytes {
199 zcash_primitives::constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX => {
200 Ok(Address::PayToScriptHash {
201 network_kind: NetworkKind::Mainnet,
202 script_hash: hash_bytes,
203 })
204 }
205 zcash_primitives::constants::testnet::B58_SCRIPT_ADDRESS_PREFIX => {
206 Ok(Address::PayToScriptHash {
207 network_kind: NetworkKind::Testnet,
208 script_hash: hash_bytes,
209 })
210 }
211 zcash_primitives::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX => {
212 Ok(Address::PayToPublicKeyHash {
213 network_kind: NetworkKind::Mainnet,
214 pub_key_hash: hash_bytes,
215 })
216 }
217 zcash_primitives::constants::testnet::B58_PUBKEY_ADDRESS_PREFIX => {
218 Ok(Address::PayToPublicKeyHash {
219 network_kind: NetworkKind::Testnet,
220 pub_key_hash: hash_bytes,
221 })
222 }
223 _ => Err(SerializationError::Parse("bad t-addr version/type")),
224 }
225 }
226}
227
228impl Address {
229 pub fn from_pub_key_hash(network_kind: NetworkKind, pub_key_hash: [u8; 20]) -> Self {
231 Self::PayToPublicKeyHash {
232 network_kind,
233 pub_key_hash,
234 }
235 }
236
237 pub fn from_script_hash(network_kind: NetworkKind, script_hash: [u8; 20]) -> Self {
239 Self::PayToScriptHash {
240 network_kind,
241 script_hash,
242 }
243 }
244
245 pub fn network_kind(&self) -> NetworkKind {
247 match self {
248 Address::PayToScriptHash { network_kind, .. } => *network_kind,
249 Address::PayToPublicKeyHash { network_kind, .. } => *network_kind,
250 Address::Tex { network_kind, .. } => *network_kind,
251 }
252 }
253
254 pub fn is_script_hash(&self) -> bool {
256 matches!(self, Address::PayToScriptHash { .. })
257 }
258
259 pub fn hash_bytes(&self) -> [u8; 20] {
265 match *self {
266 Address::PayToScriptHash { script_hash, .. } => script_hash,
267 Address::PayToPublicKeyHash { pub_key_hash, .. } => pub_key_hash,
268 Address::Tex {
269 validating_key_hash,
270 ..
271 } => validating_key_hash,
272 }
273 }
274
275 pub fn script(&self) -> Script {
279 let mut script_bytes = Vec::new();
280
281 match self {
282 Address::PayToScriptHash { .. } => {
284 script_bytes.push(OpCode::Hash160 as u8);
285 script_bytes.push(OpCode::Push20Bytes as u8);
286 script_bytes.extend(self.hash_bytes());
287 script_bytes.push(OpCode::Equal as u8);
288 }
289 Address::PayToPublicKeyHash { .. } => {
291 script_bytes.push(OpCode::Dup as u8);
292 script_bytes.push(OpCode::Hash160 as u8);
293 script_bytes.push(OpCode::Push20Bytes as u8);
294 script_bytes.extend(self.hash_bytes());
295 script_bytes.push(OpCode::EqualVerify as u8);
296 script_bytes.push(OpCode::CheckSig as u8);
297 }
298 Address::Tex { .. } => {}
299 };
300
301 Script::new(&script_bytes)
302 }
303
304 pub fn from_tex(network_kind: NetworkKind, validating_key_hash: [u8; 20]) -> Self {
306 Self::Tex {
307 network_kind,
308 validating_key_hash,
309 }
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use ripemd::{Digest, Ripemd160};
316 use secp256k1::PublicKey;
317 use sha2::Sha256;
318
319 use super::*;
320
321 trait ToAddressWithNetwork {
322 fn to_address(&self, network: NetworkKind) -> Address;
324 }
325
326 impl ToAddressWithNetwork for Script {
327 fn to_address(&self, network_kind: NetworkKind) -> Address {
328 Address::PayToScriptHash {
329 network_kind,
330 script_hash: Address::hash_payload(self.as_raw_bytes()),
331 }
332 }
333 }
334
335 impl ToAddressWithNetwork for PublicKey {
336 fn to_address(&self, network_kind: NetworkKind) -> Address {
337 Address::PayToPublicKeyHash {
338 network_kind,
339 pub_key_hash: Address::hash_payload(&self.serialize()[..]),
340 }
341 }
342 }
343
344 impl Address {
345 #[allow(dead_code)]
353 fn hash_payload(bytes: &[u8]) -> [u8; 20] {
354 let sha_hash = Sha256::digest(bytes);
355 let ripe_hash = Ripemd160::digest(sha_hash);
356 let mut payload = [0u8; 20];
357 payload[..].copy_from_slice(&ripe_hash[..]);
358 payload
359 }
360 }
361
362 #[test]
363 fn pubkey_mainnet() {
364 let _init_guard = zebra_test::init();
365
366 let pub_key = PublicKey::from_slice(&[
367 3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41,
368 111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78,
369 ])
370 .expect("A PublicKey from slice");
371
372 let t_addr = pub_key.to_address(NetworkKind::Mainnet);
373
374 assert_eq!(format!("{t_addr}"), "t1bmMa1wJDFdbc2TiURQP5BbBz6jHjUBuHq");
375 }
376
377 #[test]
378 fn pubkey_testnet() {
379 let _init_guard = zebra_test::init();
380
381 let pub_key = PublicKey::from_slice(&[
382 3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41,
383 111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78,
384 ])
385 .expect("A PublicKey from slice");
386
387 let t_addr = pub_key.to_address(NetworkKind::Testnet);
388
389 assert_eq!(format!("{t_addr}"), "tmTc6trRhbv96kGfA99i7vrFwb5p7BVFwc3");
390 }
391
392 #[test]
393 fn empty_script_mainnet() {
394 let _init_guard = zebra_test::init();
395
396 let script = Script::new(&[0u8; 20]);
397
398 let t_addr = script.to_address(NetworkKind::Mainnet);
399
400 assert_eq!(format!("{t_addr}"), "t3Y5pHwfgHbS6pDjj1HLuMFxhFFip1fcJ6g");
401 }
402
403 #[test]
404 fn empty_script_testnet() {
405 let _init_guard = zebra_test::init();
406
407 let script = Script::new(&[0; 20]);
408
409 let t_addr = script.to_address(NetworkKind::Testnet);
410
411 assert_eq!(format!("{t_addr}"), "t2L51LcmpA43UMvKTw2Lwtt9LMjwyqU2V1P");
412 }
413
414 #[test]
415 fn from_string() {
416 let _init_guard = zebra_test::init();
417
418 let t_addr: Address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".parse().unwrap();
419
420 assert_eq!(format!("{t_addr}"), "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd");
421 }
422
423 #[test]
424 fn debug() {
425 let _init_guard = zebra_test::init();
426
427 let t_addr: Address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".parse().unwrap();
428
429 assert_eq!(
430 format!("{t_addr:?}"),
431 "TransparentAddress { network_kind: Mainnet, script_hash: \"7d46a730d31f97b1930d3368a967c309bd4d136a\" }"
432 );
433 }
434}
435
436#[cfg(test)]
437proptest! {
438
439 #[test]
440 fn transparent_address_roundtrip(taddr in any::<Address>()) {
441 let _init_guard = zebra_test::init();
442
443 let mut data = Vec::new();
444
445 taddr.zcash_serialize(&mut data).expect("t-addr should serialize");
446
447 let taddr2 = Address::zcash_deserialize(&data[..]).expect("randomized t-addr should deserialize");
448
449 prop_assert_eq![taddr, taddr2];
450 }
451}