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 {expected}, got {actual}."
73 ),
74 }
75 }
76}
77
78#[cfg(all(feature = "sapling", feature = "std"))]
79impl std::error::Error for Bech32DecodeError {}
80
81#[cfg(feature = "sapling")]
82fn bech32_decode<T, F>(hrp: &str, s: &str, read: F) -> Result<T, Bech32DecodeError>
83where
84 F: Fn(Vec<u8>) -> Option<T>,
85{
86 let parsed = CheckedHrpstring::new::<Bech32>(s)?;
87 if parsed.hrp().as_str() != hrp {
88 Err(Bech32DecodeError::HrpMismatch {
89 expected: hrp.to_string(),
90 actual: parsed.hrp().as_str().to_owned(),
91 })
92 } else {
93 read(parsed.byte_iter().collect::<Vec<_>>()).ok_or(Bech32DecodeError::ReadError)
94 }
95}
96
97pub trait AddressCodec<P>
99where
100 Self: core::marker::Sized,
101{
102 type Error;
103
104 fn encode(&self, params: &P) -> String;
109
110 fn decode(params: &P, address: &str) -> Result<Self, Self::Error>;
116}
117
118#[derive(Debug)]
119pub enum TransparentCodecError {
120 UnsupportedAddressType(String),
121 Base58(Bs58Error),
122}
123
124impl fmt::Display for TransparentCodecError {
125 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126 match &self {
127 TransparentCodecError::UnsupportedAddressType(s) => write!(
128 f,
129 "Could not recognize {s} as a supported p2sh or p2pkh address."
130 ),
131 TransparentCodecError::Base58(e) => write!(f, "{e}"),
132 }
133 }
134}
135
136#[cfg(feature = "std")]
137impl std::error::Error for TransparentCodecError {}
138
139impl<P: consensus::Parameters> AddressCodec<P> for TransparentAddress {
140 type Error = TransparentCodecError;
141
142 fn encode(&self, params: &P) -> String {
143 encode_transparent_address(
144 ¶ms.b58_pubkey_address_prefix(),
145 ¶ms.b58_script_address_prefix(),
146 self,
147 )
148 }
149
150 fn decode(params: &P, address: &str) -> Result<TransparentAddress, TransparentCodecError> {
151 decode_transparent_address(
152 ¶ms.b58_pubkey_address_prefix(),
153 ¶ms.b58_script_address_prefix(),
154 address,
155 )
156 .map_err(TransparentCodecError::Base58)
157 .and_then(|opt| {
158 opt.ok_or_else(|| TransparentCodecError::UnsupportedAddressType(address.to_string()))
159 })
160 }
161}
162
163#[cfg(feature = "sapling")]
164impl<P: consensus::Parameters> AddressCodec<P> for sapling::PaymentAddress {
165 type Error = Bech32DecodeError;
166
167 fn encode(&self, params: &P) -> String {
168 encode_payment_address(params.hrp_sapling_payment_address(), self)
169 }
170
171 fn decode(params: &P, address: &str) -> Result<Self, Bech32DecodeError> {
172 decode_payment_address(params.hrp_sapling_payment_address(), address)
173 }
174}
175
176impl<P: consensus::Parameters> AddressCodec<P> for UnifiedAddress {
177 type Error = String;
178
179 fn encode(&self, params: &P) -> String {
180 self.encode(params)
181 }
182
183 fn decode(params: &P, address: &str) -> Result<Self, String> {
184 unified::Address::decode(address)
185 .map_err(|e| format!("{e}"))
186 .and_then(|(network, addr)| {
187 if params.network_type() == network {
188 UnifiedAddress::try_from(addr).map_err(|e| e.to_owned())
189 } else {
190 Err(format!(
191 "Address {address} is for a different network: {network:?}"
192 ))
193 }
194 })
195 }
196}
197
198#[cfg(feature = "sapling")]
216pub fn encode_extended_spending_key(hrp: &str, extsk: &ExtendedSpendingKey) -> String {
217 bech32_encode(hrp, |w| extsk.write(w))
218}
219
220#[cfg(feature = "sapling")]
224pub fn decode_extended_spending_key(
225 hrp: &str,
226 s: &str,
227) -> Result<ExtendedSpendingKey, Bech32DecodeError> {
228 bech32_decode(hrp, s, |data| ExtendedSpendingKey::read(&data[..]).ok())
229}
230
231#[cfg(feature = "sapling")]
250pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingKey) -> String {
251 bech32_encode(hrp, |w| extfvk.write(w))
252}
253
254#[cfg(feature = "sapling")]
257pub fn decode_extended_full_viewing_key(
258 hrp: &str,
259 s: &str,
260) -> Result<ExtendedFullViewingKey, Bech32DecodeError> {
261 bech32_decode(hrp, s, |data| ExtendedFullViewingKey::read(&data[..]).ok())
262}
263
264#[cfg(feature = "sapling")]
267pub fn decode_extfvk_with_network(
268 s: &str,
269) -> Result<(NetworkType, ExtendedFullViewingKey), Bech32DecodeError> {
270 use zcash_protocol::constants::{mainnet, regtest, testnet};
271
272 let parsed = CheckedHrpstring::new::<Bech32>(s)?;
273 let network = match parsed.hrp().as_str() {
274 mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Main),
275 testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Test),
276 regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Regtest),
277 other => Err(Bech32DecodeError::HrpMismatch {
278 expected: format!(
279 "One of {}, {}, or {}",
280 mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
281 testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
282 regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
283 ),
284 actual: other.to_string(),
285 }),
286 }?;
287 let fvk = ExtendedFullViewingKey::read(&parsed.byte_iter().collect::<Vec<_>>()[..])
288 .map_err(|_| Bech32DecodeError::ReadError)?;
289
290 Ok((network, fvk))
291}
292
293#[cfg(feature = "sapling")]
320pub fn encode_payment_address(hrp: &str, addr: &sapling::PaymentAddress) -> String {
321 bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
322}
323
324#[cfg(feature = "sapling")]
330pub fn encode_payment_address_p<P: consensus::Parameters>(
331 params: &P,
332 addr: &sapling::PaymentAddress,
333) -> String {
334 encode_payment_address(params.hrp_sapling_payment_address(), addr)
335}
336
337#[cfg(feature = "sapling")]
367pub fn decode_payment_address(
368 hrp: &str,
369 s: &str,
370) -> Result<sapling::PaymentAddress, Bech32DecodeError> {
371 bech32_decode(hrp, s, |data| {
372 if data.len() != 43 {
373 return None;
374 }
375
376 let mut bytes = [0; 43];
377 bytes.copy_from_slice(&data);
378 sapling::PaymentAddress::from_bytes(&bytes)
379 })
380}
381
382pub fn encode_transparent_address(
409 pubkey_version: &[u8],
410 script_version: &[u8],
411 addr: &TransparentAddress,
412) -> String {
413 let decoded = match addr {
414 TransparentAddress::PublicKeyHash(key_id) => {
415 let mut decoded = vec![0; pubkey_version.len() + 20];
416 decoded[..pubkey_version.len()].copy_from_slice(pubkey_version);
417 decoded[pubkey_version.len()..].copy_from_slice(key_id);
418 decoded
419 }
420 TransparentAddress::ScriptHash(script_id) => {
421 let mut decoded = vec![0; script_version.len() + 20];
422 decoded[..script_version.len()].copy_from_slice(script_version);
423 decoded[script_version.len()..].copy_from_slice(script_id);
424 decoded
425 }
426 };
427 bs58::encode(decoded).with_check().into_string()
428}
429
430pub fn encode_transparent_address_p<P: consensus::Parameters>(
434 params: &P,
435 addr: &TransparentAddress,
436) -> String {
437 encode_transparent_address(
438 ¶ms.b58_pubkey_address_prefix(),
439 ¶ms.b58_script_address_prefix(),
440 addr,
441 )
442}
443
444pub fn decode_transparent_address(
473 pubkey_version: &[u8],
474 script_version: &[u8],
475 s: &str,
476) -> Result<Option<TransparentAddress>, Bs58Error> {
477 bs58::decode(s).with_check(None).into_vec().map(|decoded| {
478 if decoded.starts_with(pubkey_version) {
479 decoded[pubkey_version.len()..]
480 .try_into()
481 .ok()
482 .map(TransparentAddress::PublicKeyHash)
483 } else if decoded.starts_with(script_version) {
484 decoded[script_version.len()..]
485 .try_into()
486 .ok()
487 .map(TransparentAddress::ScriptHash)
488 } else {
489 None
490 }
491 })
492}
493
494#[cfg(test)]
495#[cfg(feature = "sapling")]
496mod tests_sapling {
497 use super::{
498 decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address,
499 encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
500 Bech32DecodeError,
501 };
502 use sapling::{zip32::ExtendedSpendingKey, PaymentAddress};
503 use zcash_protocol::constants;
504
505 #[test]
506 fn extended_spending_key() {
507 let extsk = ExtendedSpendingKey::master(&[0; 32][..]);
508
509 let encoded_main = "secret-extended-key-main1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qysqws3xh6qmha7gna72fs2n4clnc9zgyd22s658f65pex4exe56qjk5pqj9vfdq7dfdhjc2rs9jdwq0zl99uwycyrxzp86705rk687spn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjs87qvlj";
510 let encoded_test = "secret-extended-key-test1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qysqws3xh6qmha7gna72fs2n4clnc9zgyd22s658f65pex4exe56qjk5pqj9vfdq7dfdhjc2rs9jdwq0zl99uwycyrxzp86705rk687spn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjsvzyw8j";
511
512 assert_eq!(
513 encode_extended_spending_key(
514 constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
515 &extsk
516 ),
517 encoded_main
518 );
519 assert_eq!(
520 decode_extended_spending_key(
521 constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
522 encoded_main
523 )
524 .unwrap(),
525 extsk
526 );
527
528 assert_eq!(
529 encode_extended_spending_key(
530 constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
531 &extsk
532 ),
533 encoded_test
534 );
535 assert_eq!(
536 decode_extended_spending_key(
537 constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
538 encoded_test
539 )
540 .unwrap(),
541 extsk
542 );
543 }
544
545 #[test]
546 #[allow(deprecated)]
547 fn extended_full_viewing_key() {
548 let extfvk = ExtendedSpendingKey::master(&[0; 32][..]).to_extended_full_viewing_key();
549
550 let encoded_main = "zxviews1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjsxmansf";
551 let encoded_test = "zxviewtestsapling1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjs8evfkz";
552 let encoded_regtest = "zxviewregtestsapling1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjskjkzax";
553 assert_eq!(
554 encode_extended_full_viewing_key(
555 constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
556 &extfvk
557 ),
558 encoded_main
559 );
560 assert_eq!(
561 decode_extended_full_viewing_key(
562 constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
563 encoded_main
564 )
565 .unwrap(),
566 extfvk
567 );
568
569 assert_eq!(
570 encode_extended_full_viewing_key(
571 constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
572 &extfvk
573 ),
574 encoded_test
575 );
576
577 assert_eq!(
578 encode_extended_full_viewing_key(
579 constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
580 &extfvk
581 ),
582 encoded_regtest
583 );
584
585 assert_eq!(
586 decode_extended_full_viewing_key(
587 constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
588 encoded_regtest
589 )
590 .unwrap(),
591 extfvk
592 );
593 }
594
595 #[test]
596 fn payment_address() {
597 let addr = PaymentAddress::from_bytes(&[
598 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
599 0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
600 0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
601 0xea,
602 ])
603 .unwrap();
604
605 let encoded_main =
606 "zs1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75c8v35z";
607 let encoded_test =
608 "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk";
609 let encoded_regtest =
610 "zregtestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle7505hlz3";
611
612 assert_eq!(
613 encode_payment_address(constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS, &addr),
614 encoded_main
615 );
616
617 assert_eq!(
618 decode_payment_address(
619 constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS,
620 encoded_main
621 )
622 .unwrap(),
623 addr
624 );
625
626 assert_eq!(
627 encode_payment_address(constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS, &addr),
628 encoded_test
629 );
630
631 assert_eq!(
632 encode_payment_address(constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS, &addr),
633 encoded_regtest
634 );
635
636 assert_eq!(
637 decode_payment_address(
638 constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
639 encoded_test
640 )
641 .unwrap(),
642 addr
643 );
644
645 assert_eq!(
646 decode_payment_address(
647 constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS,
648 encoded_regtest
649 )
650 .unwrap(),
651 addr
652 );
653 }
654
655 #[test]
656 fn invalid_diversifier() {
657 let encoded_main =
659 "zs1qyqszqgpqyqszqgpqycguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ugum9p";
660
661 assert_eq!(
662 decode_payment_address(
663 constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS,
664 encoded_main,
665 ),
666 Err(Bech32DecodeError::ReadError)
667 );
668 }
669}