Skip to main content

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};
14use zcash_transparent::address::TransparentAddress;
15
16/// Transparent Zcash Addresses
17///
18/// In Bitcoin a single byte is used for the version field identifying
19/// the address type. In Zcash two bytes are used. For addresses on
20/// the production network, this and the encoded length cause the first
21/// two characters of the Base58Check encoding to be fixed as "t3" for
22/// P2SH addresses, and as "t1" for P2PKH addresses. (This does not
23/// imply that a transparent Zcash address can be parsed identically
24/// to a Bitcoin address just by removing the "t".)
25///
26/// <https://zips.z.cash/protocol/protocol.pdf#transparentaddrencoding>
27// TODO Remove this type and move to `TransparentAddress` in `zcash-transparent`.
28#[derive(
29    Clone, Copy, Eq, PartialEq, Hash, serde_with::SerializeDisplay, serde_with::DeserializeFromStr,
30)]
31pub enum Address {
32    /// P2SH (Pay to Script Hash) addresses
33    PayToScriptHash {
34        /// Production, test, or other network
35        network_kind: NetworkKind,
36        /// 20 bytes specifying a script hash.
37        script_hash: [u8; 20],
38    },
39
40    /// P2PKH (Pay to Public Key Hash) addresses
41    PayToPublicKeyHash {
42        /// Production, test, or other network
43        network_kind: NetworkKind,
44        /// 20 bytes specifying a public key hash, which is a RIPEMD-160
45        /// hash of a SHA-256 hash of a compressed ECDSA key encoding.
46        pub_key_hash: [u8; 20],
47    },
48
49    /// Transparent-Source-Only Address.
50    ///
51    /// <https://zips.z.cash/zip-0320.html>
52    Tex {
53        /// Production, test, or other network
54        network_kind: NetworkKind,
55        /// 20 bytes specifying the validating key hash.
56        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        // Try Base58Check (prefixes: t1, t3, tm, t2)
137        if let Ok(data) = bs58::decode(s).with_check(None).into_vec() {
138            return Address::zcash_deserialize(&data[..]);
139        }
140
141        // Try Bech32 (prefixes: tex, textest)
142        let (hrp, payload) =
143            bech32::decode(s).map_err(|_| SerializationError::Parse("invalid Bech32 encoding"))?;
144
145        // We can’t meaningfully call `Address::zcash_deserialize` for Bech32 addresses, because
146        // that method is explicitly reading two binary prefix bytes (the Base58Check version) + 20 hash bytes.
147        // Bech32 textual addresses carry no such binary "version" on the wire, so there’s nothing in the
148        // reader for zcash_deserialize to match.
149
150        // Instead, we deserialize the Bech32 address here:
151
152        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    /// Create an address for the given public key hash and network.
245    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    /// Create an address for the given script hash and network.
253    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    /// Returns the network kind for this address.
261    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    /// Returns `true` if the address is `PayToScriptHash`, and `false` if it is `PayToPublicKeyHash`.
270    pub fn is_script_hash(&self) -> bool {
271        matches!(self, Address::PayToScriptHash { .. })
272    }
273
274    /// Returns the hash bytes for this address, regardless of the address type.
275    ///
276    /// # Correctness
277    ///
278    /// Use [`ZcashSerialize`] and [`ZcashDeserialize`] for consensus-critical serialization.
279    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    /// Turns the address into the `scriptPubKey` script that can be used in a coinbase output.
291    ///
292    /// TEX addresses are not supported and return an empty script.
293    pub fn script(&self) -> Script {
294        let mut script_bytes = Vec::new();
295
296        match self {
297            // https://developer.bitcoin.org/devguide/transactions.html#pay-to-script-hash-p2sh
298            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            // https://developer.bitcoin.org/devguide/transactions.html#pay-to-public-key-hash-p2pkh
305            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    /// Create a TEX address from the given network kind and validating key hash.
320    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        /// Convert `self` to an `Address`, given the current `network`.
338        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        /// A hash of a transparent address payload, as used in
361        /// transparent pay-to-script-hash and pay-to-publickey-hash
362        /// addresses.
363        ///
364        /// The resulting hash in both of these cases is always exactly 20
365        /// bytes.
366        /// <https://en.bitcoin.it/Base58Check_encoding#Encoding_a_Bitcoin_address>
367        #[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}