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