zebra_chain/transparent/
address.rs

1//! Transparent Address types.
2
3use 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/// Transparent Zcash Addresses
16///
17/// In Bitcoin a single byte is used for the version field identifying
18/// the address type. In Zcash two bytes are used. For addresses on
19/// the production network, this and the encoded length cause the first
20/// two characters of the Base58Check encoding to be fixed as "t3" for
21/// P2SH addresses, and as "t1" for P2PKH addresses. (This does not
22/// imply that a transparent Zcash address can be parsed identically
23/// to a Bitcoin address just by removing the "t".)
24///
25/// <https://zips.z.cash/protocol/protocol.pdf#transparentaddrencoding>
26// TODO Remove this type and move to `TransparentAddress` in `zcash-transparent`.
27#[derive(
28    Clone, Eq, PartialEq, Hash, serde_with::SerializeDisplay, serde_with::DeserializeFromStr,
29)]
30pub enum Address {
31    /// P2SH (Pay to Script Hash) addresses
32    PayToScriptHash {
33        /// Production, test, or other network
34        network_kind: NetworkKind,
35        /// 20 bytes specifying a script hash.
36        script_hash: [u8; 20],
37    },
38
39    /// P2PKH (Pay to Public Key Hash) addresses
40    PayToPublicKeyHash {
41        /// Production, test, or other network
42        network_kind: NetworkKind,
43        /// 20 bytes specifying a public key hash, which is a RIPEMD-160
44        /// hash of a SHA-256 hash of a compressed ECDSA key encoding.
45        pub_key_hash: [u8; 20],
46    },
47
48    /// Transparent-Source-Only Address.
49    ///
50    /// <https://zips.z.cash/zip-0320.html>
51    Tex {
52        /// Production, test, or other network
53        network_kind: NetworkKind,
54        /// 20 bytes specifying the validating key hash.
55        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        // Try Base58Check (prefixes: t1, t3, tm, t2)
122        if let Ok(data) = bs58::decode(s).with_check(None).into_vec() {
123            return Address::zcash_deserialize(&data[..]);
124        }
125
126        // Try Bech32 (prefixes: tex, textest)
127        let (hrp, payload) =
128            bech32::decode(s).map_err(|_| SerializationError::Parse("invalid Bech32 encoding"))?;
129
130        // We can’t meaningfully call `Address::zcash_deserialize` for Bech32 addresses, because
131        // that method is explicitly reading two binary prefix bytes (the Base58Check version) + 20 hash bytes.
132        // Bech32 textual addresses carry no such binary "version" on the wire, so there’s nothing in the
133        // reader for zcash_deserialize to match.
134
135        // Instead, we deserialize the Bech32 address here:
136
137        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    /// Create an address for the given public key hash and network.
230    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    /// Create an address for the given script hash and network.
238    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    /// Returns the network kind for this address.
246    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    /// Returns `true` if the address is `PayToScriptHash`, and `false` if it is `PayToPublicKeyHash`.
255    pub fn is_script_hash(&self) -> bool {
256        matches!(self, Address::PayToScriptHash { .. })
257    }
258
259    /// Returns the hash bytes for this address, regardless of the address type.
260    ///
261    /// # Correctness
262    ///
263    /// Use [`ZcashSerialize`] and [`ZcashDeserialize`] for consensus-critical serialization.
264    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    /// Turns the address into the `scriptPubKey` script that can be used in a coinbase output.
276    ///
277    /// TEX addresses are not supported and return an empty script.
278    pub fn script(&self) -> Script {
279        let mut script_bytes = Vec::new();
280
281        match self {
282            // https://developer.bitcoin.org/devguide/transactions.html#pay-to-script-hash-p2sh
283            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            // https://developer.bitcoin.org/devguide/transactions.html#pay-to-public-key-hash-p2pkh
290            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    /// Create a TEX address from the given network kind and validating key hash.
305    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        /// Convert `self` to an `Address`, given the current `network`.
323        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        /// A hash of a transparent address payload, as used in
346        /// transparent pay-to-script-hash and pay-to-publickey-hash
347        /// addresses.
348        ///
349        /// The resulting hash in both of these cases is always exactly 20
350        /// bytes.
351        /// <https://en.bitcoin.it/Base58Check_encoding#Encoding_a_Bitcoin_address>
352        #[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}