1use crate::address::UnifiedAddress;
7use alloc::borrow::ToOwned;
8use alloc::string::{String, ToString};
9use bs58::{self, decode::Error as Bs58Error};
10use core::fmt;
11
12use transparent::address::TransparentAddress;
13use zcash_address::unified::{self, Encoding};
14use zcash_protocol::consensus::{self, NetworkConstants};
15
16#[cfg(feature = "sapling")]
17use {
18 alloc::vec::Vec,
19 bech32::{
20 primitives::decode::{CheckedHrpstring, CheckedHrpstringError},
21 Bech32, Hrp,
22 },
23 core2::io::{self, Write},
24 sapling::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
25 zcash_protocol::consensus::NetworkType,
26};
27
28#[cfg(feature = "sapling")]
29fn bech32_encode<F>(hrp: &str, write: F) -> String
30where
31 F: Fn(&mut dyn Write) -> io::Result<()>,
32{
33 let mut data: Vec<u8> = vec![];
34 write(&mut data).expect("Should be able to write to a Vec");
35 bech32::encode::<Bech32>(Hrp::parse_unchecked(hrp), &data).expect("encoding is short enough")
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
39#[cfg(feature = "sapling")]
40pub enum Bech32DecodeError {
41 Bech32Error(bech32::DecodeError),
42 Hrp(CheckedHrpstringError),
43 ReadError,
44 HrpMismatch { expected: String, actual: String },
45}
46
47#[cfg(feature = "sapling")]
48impl From<bech32::DecodeError> for Bech32DecodeError {
49 fn from(err: bech32::DecodeError) -> Self {
50 Bech32DecodeError::Bech32Error(err)
51 }
52}
53
54#[cfg(feature = "sapling")]
55impl From<CheckedHrpstringError> for Bech32DecodeError {
56 fn from(err: CheckedHrpstringError) -> Self {
57 Bech32DecodeError::Hrp(err)
58 }
59}
60
61#[cfg(feature = "sapling")]
62impl fmt::Display for Bech32DecodeError {
63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 match &self {
65 Bech32DecodeError::Bech32Error(e) => write!(f, "{}", e),
66 Bech32DecodeError::Hrp(e) => write!(f, "Incorrect HRP encoding: {e}"),
67 Bech32DecodeError::ReadError => {
68 write!(f, "Failed to decode key from its binary representation.")
69 }
70 Bech32DecodeError::HrpMismatch { expected, actual } => write!(
71 f,
72 "Key was encoded for a different network: expected {}, got {}.",
73 expected, actual
74 ),
75 }
76 }
77}
78
79#[cfg(all(feature = "sapling", feature = "std"))]
80impl std::error::Error for Bech32DecodeError {}
81
82#[cfg(feature = "sapling")]
83fn bech32_decode<T, F>(hrp: &str, s: &str, read: F) -> Result<T, Bech32DecodeError>
84where
85 F: Fn(Vec<u8>) -> Option<T>,
86{
87 let parsed = CheckedHrpstring::new::<Bech32>(s)?;
88 if parsed.hrp().as_str() != hrp {
89 Err(Bech32DecodeError::HrpMismatch {
90 expected: hrp.to_string(),
91 actual: parsed.hrp().as_str().to_owned(),
92 })
93 } else {
94 read(parsed.byte_iter().collect::<Vec<_>>()).ok_or(Bech32DecodeError::ReadError)
95 }
96}
97
98pub trait AddressCodec<P>
100where
101 Self: core::marker::Sized,
102{
103 type Error;
104
105 fn encode(&self, params: &P) -> String;
110
111 fn decode(params: &P, address: &str) -> Result<Self, Self::Error>;
117}
118
119#[derive(Debug)]
120pub enum TransparentCodecError {
121 UnsupportedAddressType(String),
122 Base58(Bs58Error),
123}
124
125impl fmt::Display for TransparentCodecError {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 match &self {
128 TransparentCodecError::UnsupportedAddressType(s) => write!(
129 f,
130 "Could not recognize {} as a supported p2sh or p2pkh address.",
131 s
132 ),
133 TransparentCodecError::Base58(e) => write!(f, "{}", e),
134 }
135 }
136}
137
138#[cfg(feature = "std")]
139impl std::error::Error for TransparentCodecError {}
140
141impl<P: consensus::Parameters> AddressCodec<P> for TransparentAddress {
142 type Error = TransparentCodecError;
143
144 fn encode(&self, params: &P) -> String {
145 encode_transparent_address(
146 ¶ms.b58_pubkey_address_prefix(),
147 ¶ms.b58_script_address_prefix(),
148 self,
149 )
150 }
151
152 fn decode(params: &P, address: &str) -> Result<TransparentAddress, TransparentCodecError> {
153 decode_transparent_address(
154 ¶ms.b58_pubkey_address_prefix(),
155 ¶ms.b58_script_address_prefix(),
156 address,
157 )
158 .map_err(TransparentCodecError::Base58)
159 .and_then(|opt| {
160 opt.ok_or_else(|| TransparentCodecError::UnsupportedAddressType(address.to_string()))
161 })
162 }
163}
164
165#[cfg(feature = "sapling")]
166impl<P: consensus::Parameters> AddressCodec<P> for sapling::PaymentAddress {
167 type Error = Bech32DecodeError;
168
169 fn encode(&self, params: &P) -> String {
170 encode_payment_address(params.hrp_sapling_payment_address(), self)
171 }
172
173 fn decode(params: &P, address: &str) -> Result<Self, Bech32DecodeError> {
174 decode_payment_address(params.hrp_sapling_payment_address(), address)
175 }
176}
177
178impl<P: consensus::Parameters> AddressCodec<P> for UnifiedAddress {
179 type Error = String;
180
181 fn encode(&self, params: &P) -> String {
182 self.encode(params)
183 }
184
185 fn decode(params: &P, address: &str) -> Result<Self, String> {
186 unified::Address::decode(address)
187 .map_err(|e| format!("{}", e))
188 .and_then(|(network, addr)| {
189 if params.network_type() == network {
190 UnifiedAddress::try_from(addr).map_err(|e| e.to_owned())
191 } else {
192 Err(format!(
193 "Address {} is for a different network: {:?}",
194 address, network
195 ))
196 }
197 })
198 }
199}
200
201#[cfg(feature = "sapling")]
219pub fn encode_extended_spending_key(hrp: &str, extsk: &ExtendedSpendingKey) -> String {
220 bech32_encode(hrp, |w| extsk.write(w))
221}
222
223#[cfg(feature = "sapling")]
227pub fn decode_extended_spending_key(
228 hrp: &str,
229 s: &str,
230) -> Result<ExtendedSpendingKey, Bech32DecodeError> {
231 bech32_decode(hrp, s, |data| ExtendedSpendingKey::read(&data[..]).ok())
232}
233
234#[cfg(feature = "sapling")]
253pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingKey) -> String {
254 bech32_encode(hrp, |w| extfvk.write(w))
255}
256
257#[cfg(feature = "sapling")]
260pub fn decode_extended_full_viewing_key(
261 hrp: &str,
262 s: &str,
263) -> Result<ExtendedFullViewingKey, Bech32DecodeError> {
264 bech32_decode(hrp, s, |data| ExtendedFullViewingKey::read(&data[..]).ok())
265}
266
267#[cfg(feature = "sapling")]
270pub fn decode_extfvk_with_network(
271 s: &str,
272) -> Result<(NetworkType, ExtendedFullViewingKey), Bech32DecodeError> {
273 use zcash_protocol::constants::{mainnet, regtest, testnet};
274
275 let parsed = CheckedHrpstring::new::<Bech32>(s)?;
276 let network = match parsed.hrp().as_str() {
277 mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Main),
278 testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Test),
279 regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Regtest),
280 other => Err(Bech32DecodeError::HrpMismatch {
281 expected: format!(
282 "One of {}, {}, or {}",
283 mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
284 testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
285 regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
286 ),
287 actual: other.to_string(),
288 }),
289 }?;
290 let fvk = ExtendedFullViewingKey::read(&parsed.byte_iter().collect::<Vec<_>>()[..])
291 .map_err(|_| Bech32DecodeError::ReadError)?;
292
293 Ok((network, fvk))
294}
295
296#[cfg(feature = "sapling")]
323pub fn encode_payment_address(hrp: &str, addr: &sapling::PaymentAddress) -> String {
324 bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
325}
326
327#[cfg(feature = "sapling")]
333pub fn encode_payment_address_p<P: consensus::Parameters>(
334 params: &P,
335 addr: &sapling::PaymentAddress,
336) -> String {
337 encode_payment_address(params.hrp_sapling_payment_address(), addr)
338}
339
340#[cfg(feature = "sapling")]
370pub fn decode_payment_address(
371 hrp: &str,
372 s: &str,
373) -> Result<sapling::PaymentAddress, Bech32DecodeError> {
374 bech32_decode(hrp, s, |data| {
375 if data.len() != 43 {
376 return None;
377 }
378
379 let mut bytes = [0; 43];
380 bytes.copy_from_slice(&data);
381 sapling::PaymentAddress::from_bytes(&bytes)
382 })
383}
384
385pub fn encode_transparent_address(
412 pubkey_version: &[u8],
413 script_version: &[u8],
414 addr: &TransparentAddress,
415) -> String {
416 let decoded = match addr {
417 TransparentAddress::PublicKeyHash(key_id) => {
418 let mut decoded = vec![0; pubkey_version.len() + 20];
419 decoded[..pubkey_version.len()].copy_from_slice(pubkey_version);
420 decoded[pubkey_version.len()..].copy_from_slice(key_id);
421 decoded
422 }
423 TransparentAddress::ScriptHash(script_id) => {
424 let mut decoded = vec![0; script_version.len() + 20];
425 decoded[..script_version.len()].copy_from_slice(script_version);
426 decoded[script_version.len()..].copy_from_slice(script_id);
427 decoded
428 }
429 };
430 bs58::encode(decoded).with_check().into_string()
431}
432
433pub fn encode_transparent_address_p<P: consensus::Parameters>(
437 params: &P,
438 addr: &TransparentAddress,
439) -> String {
440 encode_transparent_address(
441 ¶ms.b58_pubkey_address_prefix(),
442 ¶ms.b58_script_address_prefix(),
443 addr,
444 )
445}
446
447pub fn decode_transparent_address(
476 pubkey_version: &[u8],
477 script_version: &[u8],
478 s: &str,
479) -> Result<Option<TransparentAddress>, Bs58Error> {
480 bs58::decode(s).with_check(None).into_vec().map(|decoded| {
481 if decoded.starts_with(pubkey_version) {
482 decoded[pubkey_version.len()..]
483 .try_into()
484 .ok()
485 .map(TransparentAddress::PublicKeyHash)
486 } else if decoded.starts_with(script_version) {
487 decoded[script_version.len()..]
488 .try_into()
489 .ok()
490 .map(TransparentAddress::ScriptHash)
491 } else {
492 None
493 }
494 })
495}
496
497#[cfg(test)]
498#[cfg(feature = "sapling")]
499mod tests_sapling {
500 use super::{
501 decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address,
502 encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
503 Bech32DecodeError,
504 };
505 use sapling::{zip32::ExtendedSpendingKey, PaymentAddress};
506 use zcash_protocol::constants;
507
508 #[test]
509 fn extended_spending_key() {
510 let extsk = ExtendedSpendingKey::master(&[0; 32][..]);
511
512 let encoded_main = "secret-extended-key-main1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qysqws3xh6qmha7gna72fs2n4clnc9zgyd22s658f65pex4exe56qjk5pqj9vfdq7dfdhjc2rs9jdwq0zl99uwycyrxzp86705rk687spn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjs87qvlj";
513 let encoded_test = "secret-extended-key-test1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qysqws3xh6qmha7gna72fs2n4clnc9zgyd22s658f65pex4exe56qjk5pqj9vfdq7dfdhjc2rs9jdwq0zl99uwycyrxzp86705rk687spn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjsvzyw8j";
514
515 assert_eq!(
516 encode_extended_spending_key(
517 constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
518 &extsk
519 ),
520 encoded_main
521 );
522 assert_eq!(
523 decode_extended_spending_key(
524 constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
525 encoded_main
526 )
527 .unwrap(),
528 extsk
529 );
530
531 assert_eq!(
532 encode_extended_spending_key(
533 constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
534 &extsk
535 ),
536 encoded_test
537 );
538 assert_eq!(
539 decode_extended_spending_key(
540 constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
541 encoded_test
542 )
543 .unwrap(),
544 extsk
545 );
546 }
547
548 #[test]
549 #[allow(deprecated)]
550 fn extended_full_viewing_key() {
551 let extfvk = ExtendedSpendingKey::master(&[0; 32][..]).to_extended_full_viewing_key();
552
553 let encoded_main = "zxviews1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjsxmansf";
554 let encoded_test = "zxviewtestsapling1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjs8evfkz";
555 let encoded_regtest = "zxviewregtestsapling1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjskjkzax";
556 assert_eq!(
557 encode_extended_full_viewing_key(
558 constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
559 &extfvk
560 ),
561 encoded_main
562 );
563 assert_eq!(
564 decode_extended_full_viewing_key(
565 constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
566 encoded_main
567 )
568 .unwrap(),
569 extfvk
570 );
571
572 assert_eq!(
573 encode_extended_full_viewing_key(
574 constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
575 &extfvk
576 ),
577 encoded_test
578 );
579
580 assert_eq!(
581 encode_extended_full_viewing_key(
582 constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
583 &extfvk
584 ),
585 encoded_regtest
586 );
587
588 assert_eq!(
589 decode_extended_full_viewing_key(
590 constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
591 encoded_regtest
592 )
593 .unwrap(),
594 extfvk
595 );
596 }
597
598 #[test]
599 fn payment_address() {
600 let addr = PaymentAddress::from_bytes(&[
601 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
602 0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
603 0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
604 0xea,
605 ])
606 .unwrap();
607
608 let encoded_main =
609 "zs1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75c8v35z";
610 let encoded_test =
611 "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk";
612 let encoded_regtest =
613 "zregtestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle7505hlz3";
614
615 assert_eq!(
616 encode_payment_address(constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS, &addr),
617 encoded_main
618 );
619
620 assert_eq!(
621 decode_payment_address(
622 constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS,
623 encoded_main
624 )
625 .unwrap(),
626 addr
627 );
628
629 assert_eq!(
630 encode_payment_address(constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, &addr),
631 encoded_test
632 );
633
634 assert_eq!(
635 encode_payment_address(constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS, &addr),
636 encoded_regtest
637 );
638
639 assert_eq!(
640 decode_payment_address(
641 constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
642 encoded_test
643 )
644 .unwrap(),
645 addr
646 );
647
648 assert_eq!(
649 decode_payment_address(
650 constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS,
651 encoded_regtest
652 )
653 .unwrap(),
654 addr
655 );
656 }
657
658 #[test]
659 fn invalid_diversifier() {
660 let encoded_main =
662 "zs1qyqszqgpqyqszqgpqycguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ugum9p";
663
664 assert_eq!(
665 decode_payment_address(
666 constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS,
667 encoded_main,
668 ),
669 Err(Bech32DecodeError::ReadError)
670 );
671 }
672}