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::*;
13
14/// Transparent Zcash Addresses
15///
16/// In Bitcoin a single byte is used for the version field identifying
17/// the address type. In Zcash two bytes are used. For addresses on
18/// the production network, this and the encoded length cause the first
19/// two characters of the Base58Check encoding to be fixed as "t3" for
20/// P2SH addresses, and as "t1" for P2PKH addresses. (This does not
21/// imply that a transparent Zcash address can be parsed identically
22/// to a Bitcoin address just by removing the "t".)
23///
24/// <https://zips.z.cash/protocol/protocol.pdf#transparentaddrencoding>
25#[derive(
26    Clone, Eq, PartialEq, Hash, serde_with::SerializeDisplay, serde_with::DeserializeFromStr,
27)]
28pub enum Address {
29    /// P2SH (Pay to Script Hash) addresses
30    PayToScriptHash {
31        /// Production, test, or other network
32        network_kind: NetworkKind,
33        /// 20 bytes specifying a script hash.
34        script_hash: [u8; 20],
35    },
36
37    /// P2PKH (Pay to Public Key Hash) addresses
38    PayToPublicKeyHash {
39        /// Production, test, or other network
40        network_kind: NetworkKind,
41        /// 20 bytes specifying a public key hash, which is a RIPEMD-160
42        /// hash of a SHA-256 hash of a compressed ECDSA key encoding.
43        pub_key_hash: [u8; 20],
44    },
45}
46
47impl fmt::Debug for Address {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        let mut debug_struct = f.debug_struct("TransparentAddress");
50
51        match self {
52            Address::PayToScriptHash {
53                network_kind,
54                script_hash,
55            } => debug_struct
56                .field("network_kind", network_kind)
57                .field("script_hash", &hex::encode(script_hash))
58                .finish(),
59            Address::PayToPublicKeyHash {
60                network_kind,
61                pub_key_hash,
62            } => debug_struct
63                .field("network_kind", network_kind)
64                .field("pub_key_hash", &hex::encode(pub_key_hash))
65                .finish(),
66        }
67    }
68}
69
70impl fmt::Display for Address {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        let mut bytes = io::Cursor::new(Vec::new());
73        let _ = self.zcash_serialize(&mut bytes);
74
75        f.write_str(&bs58::encode(bytes.get_ref()).with_check().into_string())
76    }
77}
78
79impl std::str::FromStr for Address {
80    type Err = SerializationError;
81
82    fn from_str(s: &str) -> Result<Self, Self::Err> {
83        let result = &bs58::decode(s).with_check(None).into_vec();
84
85        match result {
86            Ok(bytes) => Self::zcash_deserialize(&bytes[..]),
87            Err(_) => Err(SerializationError::Parse("t-addr decoding error")),
88        }
89    }
90}
91
92impl ZcashSerialize for Address {
93    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
94        match self {
95            Address::PayToScriptHash {
96                network_kind,
97                script_hash,
98            } => {
99                writer.write_all(&network_kind.b58_script_address_prefix())?;
100                writer.write_all(script_hash)?
101            }
102            Address::PayToPublicKeyHash {
103                network_kind,
104                pub_key_hash,
105            } => {
106                writer.write_all(&network_kind.b58_pubkey_address_prefix())?;
107                writer.write_all(pub_key_hash)?
108            }
109        }
110
111        Ok(())
112    }
113}
114
115impl ZcashDeserialize for Address {
116    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
117        let mut version_bytes = [0; 2];
118        reader.read_exact(&mut version_bytes)?;
119
120        let mut hash_bytes = [0; 20];
121        reader.read_exact(&mut hash_bytes)?;
122
123        match version_bytes {
124            zcash_primitives::constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX => {
125                Ok(Address::PayToScriptHash {
126                    network_kind: NetworkKind::Mainnet,
127                    script_hash: hash_bytes,
128                })
129            }
130            zcash_primitives::constants::testnet::B58_SCRIPT_ADDRESS_PREFIX => {
131                Ok(Address::PayToScriptHash {
132                    network_kind: NetworkKind::Testnet,
133                    script_hash: hash_bytes,
134                })
135            }
136            zcash_primitives::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX => {
137                Ok(Address::PayToPublicKeyHash {
138                    network_kind: NetworkKind::Mainnet,
139                    pub_key_hash: hash_bytes,
140                })
141            }
142            zcash_primitives::constants::testnet::B58_PUBKEY_ADDRESS_PREFIX => {
143                Ok(Address::PayToPublicKeyHash {
144                    network_kind: NetworkKind::Testnet,
145                    pub_key_hash: hash_bytes,
146                })
147            }
148            _ => Err(SerializationError::Parse("bad t-addr version/type")),
149        }
150    }
151}
152
153impl Address {
154    /// Create an address for the given public key hash and network.
155    pub fn from_pub_key_hash(network_kind: NetworkKind, pub_key_hash: [u8; 20]) -> Self {
156        Self::PayToPublicKeyHash {
157            network_kind,
158            pub_key_hash,
159        }
160    }
161
162    /// Create an address for the given script hash and network.
163    pub fn from_script_hash(network_kind: NetworkKind, script_hash: [u8; 20]) -> Self {
164        Self::PayToScriptHash {
165            network_kind,
166            script_hash,
167        }
168    }
169
170    /// Returns the network kind for this address.
171    pub fn network_kind(&self) -> NetworkKind {
172        match self {
173            Address::PayToScriptHash { network_kind, .. } => *network_kind,
174            Address::PayToPublicKeyHash { network_kind, .. } => *network_kind,
175        }
176    }
177
178    /// Returns `true` if the address is `PayToScriptHash`, and `false` if it is `PayToPublicKeyHash`.
179    pub fn is_script_hash(&self) -> bool {
180        matches!(self, Address::PayToScriptHash { .. })
181    }
182
183    /// Returns the hash bytes for this address, regardless of the address type.
184    ///
185    /// # Correctness
186    ///
187    /// Use [`ZcashSerialize`] and [`ZcashDeserialize`] for consensus-critical serialization.
188    pub fn hash_bytes(&self) -> [u8; 20] {
189        match *self {
190            Address::PayToScriptHash { script_hash, .. } => script_hash,
191            Address::PayToPublicKeyHash { pub_key_hash, .. } => pub_key_hash,
192        }
193    }
194
195    /// Given a transparent address (P2SH or a P2PKH), create a script that can be used in a coinbase
196    /// transaction output.
197    pub fn create_script_from_address(&self) -> Script {
198        let mut script_bytes = Vec::new();
199
200        match self {
201            // https://developer.bitcoin.org/devguide/transactions.html#pay-to-script-hash-p2sh
202            Address::PayToScriptHash { .. } => {
203                script_bytes.push(OpCode::Hash160 as u8);
204                script_bytes.push(OpCode::Push20Bytes as u8);
205                script_bytes.extend(self.hash_bytes());
206                script_bytes.push(OpCode::Equal as u8);
207            }
208            // https://developer.bitcoin.org/devguide/transactions.html#pay-to-public-key-hash-p2pkh
209            Address::PayToPublicKeyHash { .. } => {
210                script_bytes.push(OpCode::Dup as u8);
211                script_bytes.push(OpCode::Hash160 as u8);
212                script_bytes.push(OpCode::Push20Bytes as u8);
213                script_bytes.extend(self.hash_bytes());
214                script_bytes.push(OpCode::EqualVerify as u8);
215                script_bytes.push(OpCode::CheckSig as u8);
216            }
217        };
218
219        Script::new(&script_bytes)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use ripemd::{Digest, Ripemd160};
226    use secp256k1::PublicKey;
227    use sha2::Sha256;
228
229    use super::*;
230
231    trait ToAddressWithNetwork {
232        /// Convert `self` to an `Address`, given the current `network`.
233        fn to_address(&self, network: NetworkKind) -> Address;
234    }
235
236    impl ToAddressWithNetwork for Script {
237        fn to_address(&self, network_kind: NetworkKind) -> Address {
238            Address::PayToScriptHash {
239                network_kind,
240                script_hash: Address::hash_payload(self.as_raw_bytes()),
241            }
242        }
243    }
244
245    impl ToAddressWithNetwork for PublicKey {
246        fn to_address(&self, network_kind: NetworkKind) -> Address {
247            Address::PayToPublicKeyHash {
248                network_kind,
249                pub_key_hash: Address::hash_payload(&self.serialize()[..]),
250            }
251        }
252    }
253
254    impl Address {
255        /// A hash of a transparent address payload, as used in
256        /// transparent pay-to-script-hash and pay-to-publickey-hash
257        /// addresses.
258        ///
259        /// The resulting hash in both of these cases is always exactly 20
260        /// bytes.
261        /// <https://en.bitcoin.it/Base58Check_encoding#Encoding_a_Bitcoin_address>
262        #[allow(dead_code)]
263        fn hash_payload(bytes: &[u8]) -> [u8; 20] {
264            let sha_hash = Sha256::digest(bytes);
265            let ripe_hash = Ripemd160::digest(sha_hash);
266            let mut payload = [0u8; 20];
267            payload[..].copy_from_slice(&ripe_hash[..]);
268            payload
269        }
270    }
271
272    #[test]
273    fn pubkey_mainnet() {
274        let _init_guard = zebra_test::init();
275
276        let pub_key = PublicKey::from_slice(&[
277            3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41,
278            111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78,
279        ])
280        .expect("A PublicKey from slice");
281
282        let t_addr = pub_key.to_address(NetworkKind::Mainnet);
283
284        assert_eq!(format!("{t_addr}"), "t1bmMa1wJDFdbc2TiURQP5BbBz6jHjUBuHq");
285    }
286
287    #[test]
288    fn pubkey_testnet() {
289        let _init_guard = zebra_test::init();
290
291        let pub_key = PublicKey::from_slice(&[
292            3, 23, 183, 225, 206, 31, 159, 148, 195, 42, 67, 115, 146, 41, 248, 140, 11, 3, 51, 41,
293            111, 180, 110, 143, 114, 134, 88, 73, 198, 174, 52, 184, 78,
294        ])
295        .expect("A PublicKey from slice");
296
297        let t_addr = pub_key.to_address(NetworkKind::Testnet);
298
299        assert_eq!(format!("{t_addr}"), "tmTc6trRhbv96kGfA99i7vrFwb5p7BVFwc3");
300    }
301
302    #[test]
303    fn empty_script_mainnet() {
304        let _init_guard = zebra_test::init();
305
306        let script = Script::new(&[0u8; 20]);
307
308        let t_addr = script.to_address(NetworkKind::Mainnet);
309
310        assert_eq!(format!("{t_addr}"), "t3Y5pHwfgHbS6pDjj1HLuMFxhFFip1fcJ6g");
311    }
312
313    #[test]
314    fn empty_script_testnet() {
315        let _init_guard = zebra_test::init();
316
317        let script = Script::new(&[0; 20]);
318
319        let t_addr = script.to_address(NetworkKind::Testnet);
320
321        assert_eq!(format!("{t_addr}"), "t2L51LcmpA43UMvKTw2Lwtt9LMjwyqU2V1P");
322    }
323
324    #[test]
325    fn from_string() {
326        let _init_guard = zebra_test::init();
327
328        let t_addr: Address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".parse().unwrap();
329
330        assert_eq!(format!("{t_addr}"), "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd");
331    }
332
333    #[test]
334    fn debug() {
335        let _init_guard = zebra_test::init();
336
337        let t_addr: Address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".parse().unwrap();
338
339        assert_eq!(
340            format!("{t_addr:?}"),
341            "TransparentAddress { network_kind: Mainnet, script_hash: \"7d46a730d31f97b1930d3368a967c309bd4d136a\" }"
342        );
343    }
344}
345
346#[cfg(test)]
347proptest! {
348
349    #[test]
350    fn transparent_address_roundtrip(taddr in any::<Address>()) {
351        let _init_guard = zebra_test::init();
352
353        let mut data = Vec::new();
354
355        taddr.zcash_serialize(&mut data).expect("t-addr should serialize");
356
357        let taddr2 = Address::zcash_deserialize(&data[..]).expect("randomized t-addr should deserialize");
358
359        prop_assert_eq![taddr, taddr2];
360    }
361}