1use alloc::string::{String, ToString};
7use bs58::{self, decode::Error as Bs58Error};
8use core::fmt;
9
10use transparent::address::TransparentAddress;
11use zcash_address::constants as address_constants;
12use zcash_address::unified::{self, Revision};
13use zcash_protocol::consensus;
14#[cfg(feature = "sapling")]
15use {
16 crate::constants::{mainnet, regtest, testnet},
17 alloc::{borrow::ToOwned, vec::Vec},
18 bech32::{
19 Bech32, Hrp,
20 primitives::decode::{CheckedHrpstring, CheckedHrpstringError},
21 },
22 corez::io::{self, Write},
23 sapling::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
24 zcash_protocol::consensus::NetworkType,
25};
26
27#[cfg(feature = "sapling")]
28fn bech32_encode<F>(hrp: &str, write: F) -> String
29where
30 F: Fn(&mut dyn Write) -> io::Result<()>,
31{
32 let mut data: Vec<u8> = vec![];
33 write(&mut data).expect("Should be able to write to a Vec");
34 bech32::encode::<Bech32>(Hrp::parse_unchecked(hrp), &data).expect("encoding is short enough")
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
38#[cfg(feature = "sapling")]
39pub enum Bech32DecodeError {
40 Bech32Error(bech32::DecodeError),
41 Hrp(CheckedHrpstringError),
42 ReadError,
43 HrpMismatch { expected: String, actual: String },
44}
45
46#[cfg(feature = "sapling")]
47impl From<bech32::DecodeError> for Bech32DecodeError {
48 fn from(err: bech32::DecodeError) -> Self {
49 Bech32DecodeError::Bech32Error(err)
50 }
51}
52
53#[cfg(feature = "sapling")]
54impl From<CheckedHrpstringError> for Bech32DecodeError {
55 fn from(err: CheckedHrpstringError) -> Self {
56 Bech32DecodeError::Hrp(err)
57 }
58}
59
60#[cfg(feature = "sapling")]
61impl fmt::Display for Bech32DecodeError {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 match &self {
64 Bech32DecodeError::Bech32Error(e) => write!(f, "{e}"),
65 Bech32DecodeError::Hrp(e) => write!(f, "Incorrect HRP encoding: {e}"),
66 Bech32DecodeError::ReadError => {
67 write!(f, "Failed to decode key from its binary representation.")
68 }
69 Bech32DecodeError::HrpMismatch { expected, actual } => write!(
70 f,
71 "Key was encoded for a different network: expected {expected}, got {actual}."
72 ),
73 }
74 }
75}
76
77#[cfg(all(feature = "sapling", feature = "std"))]
78impl std::error::Error for Bech32DecodeError {}
79
80#[cfg(feature = "sapling")]
81fn bech32_decode<T, F>(hrp: &str, s: &str, read: F) -> Result<T, Bech32DecodeError>
82where
83 F: Fn(Vec<u8>) -> Option<T>,
84{
85 let parsed = CheckedHrpstring::new::<Bech32>(s)?;
86 if parsed.hrp().as_str() != hrp {
87 Err(Bech32DecodeError::HrpMismatch {
88 expected: hrp.to_string(),
89 actual: parsed.hrp().as_str().to_owned(),
90 })
91 } else {
92 read(parsed.byte_iter().collect::<Vec<_>>()).ok_or(Bech32DecodeError::ReadError)
93 }
94}
95
96pub trait AddressCodec<P>
98where
99 Self: core::marker::Sized,
100{
101 type Error;
102
103 fn encode(&self, params: &P) -> String;
108
109 fn decode(params: &P, address: &str) -> Result<Self, Self::Error>;
115}
116
117#[derive(Debug, PartialEq, Eq)]
122#[non_exhaustive]
123pub enum UnifiedEncodingError {
124 NotRepresentable {
128 revision: Revision,
129 cause: unified::ParseError,
130 },
131}
132
133impl fmt::Display for UnifiedEncodingError {
134 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135 match self {
136 UnifiedEncodingError::NotRepresentable { revision, cause } => {
137 write!(f, "Cannot encode at unified revision {revision:?}: {cause}")
138 }
139 }
140 }
141}
142
143#[cfg(feature = "std")]
144impl std::error::Error for UnifiedEncodingError {}
145
146#[derive(Debug, PartialEq, Eq)]
150#[non_exhaustive]
151pub enum UnifiedDecodingError {
152 Parse(unified::ParseError),
154 NetworkMismatch {
156 expected: consensus::NetworkType,
157 actual: consensus::NetworkType,
158 },
159 InvalidItem(unified::Typecode),
161}
162
163impl fmt::Display for UnifiedDecodingError {
164 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165 match self {
166 UnifiedDecodingError::Parse(e) => write!(f, "{e}"),
167 UnifiedDecodingError::NetworkMismatch { expected, actual } => write!(
168 f,
169 "Unified encoding is for network {actual:?} but {expected:?} was expected"
170 ),
171 UnifiedDecodingError::InvalidItem(typecode) => write!(
172 f,
173 "Unified encoding contains invalid data for typecode {}",
174 typecode.typecode_value()
175 ),
176 }
177 }
178}
179
180#[cfg(feature = "std")]
181impl std::error::Error for UnifiedDecodingError {
182 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
183 match self {
184 UnifiedDecodingError::Parse(e) => Some(e),
185 _ => None,
186 }
187 }
188}
189
190#[derive(Debug)]
191pub enum TransparentCodecError {
192 UnsupportedAddressType(String),
193 Base58(Bs58Error),
194}
195
196impl fmt::Display for TransparentCodecError {
197 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198 match &self {
199 TransparentCodecError::UnsupportedAddressType(s) => write!(
200 f,
201 "Could not recognize {s} as a supported p2sh or p2pkh address."
202 ),
203 TransparentCodecError::Base58(e) => write!(f, "{e}"),
204 }
205 }
206}
207
208#[cfg(feature = "std")]
209impl std::error::Error for TransparentCodecError {}
210
211impl<P: consensus::Parameters> AddressCodec<P> for TransparentAddress {
212 type Error = TransparentCodecError;
213
214 fn encode(&self, params: &P) -> String {
215 encode_transparent_address(
216 &address_constants::b58_pubkey_address_prefix(params.network_type()),
217 &address_constants::b58_script_address_prefix(params.network_type()),
218 self,
219 )
220 }
221
222 fn decode(params: &P, address: &str) -> Result<TransparentAddress, TransparentCodecError> {
223 decode_transparent_address(
224 &address_constants::b58_pubkey_address_prefix(params.network_type()),
225 &address_constants::b58_script_address_prefix(params.network_type()),
226 address,
227 )
228 .map_err(TransparentCodecError::Base58)
229 .and_then(|opt| {
230 opt.ok_or_else(|| TransparentCodecError::UnsupportedAddressType(address.to_string()))
231 })
232 }
233}
234
235#[cfg(feature = "sapling")]
236impl<P: consensus::Parameters> AddressCodec<P> for sapling::PaymentAddress {
237 type Error = Bech32DecodeError;
238
239 fn encode(&self, params: &P) -> String {
240 encode_payment_address(
241 address_constants::hrp_sapling_payment_address(params.network_type()),
242 self,
243 )
244 }
245
246 fn decode(params: &P, address: &str) -> Result<Self, Bech32DecodeError> {
247 decode_payment_address(
248 address_constants::hrp_sapling_payment_address(params.network_type()),
249 address,
250 )
251 }
252}
253
254#[cfg(feature = "sapling")]
273pub fn encode_extended_spending_key(hrp: &str, extsk: &ExtendedSpendingKey) -> String {
274 bech32_encode(hrp, |w| extsk.write(w))
275}
276
277#[cfg(feature = "sapling")]
281pub fn decode_extended_spending_key(
282 hrp: &str,
283 s: &str,
284) -> Result<ExtendedSpendingKey, Bech32DecodeError> {
285 bech32_decode(hrp, s, |data| ExtendedSpendingKey::read(&data[..]).ok())
286}
287
288#[cfg(feature = "sapling")]
308pub fn encode_extended_full_viewing_key(hrp: &str, extfvk: &ExtendedFullViewingKey) -> String {
309 bech32_encode(hrp, |w| extfvk.write(w))
310}
311
312#[cfg(feature = "sapling")]
315pub fn decode_extended_full_viewing_key(
316 hrp: &str,
317 s: &str,
318) -> Result<ExtendedFullViewingKey, Bech32DecodeError> {
319 bech32_decode(hrp, s, |data| ExtendedFullViewingKey::read(&data[..]).ok())
320}
321
322#[cfg(feature = "sapling")]
325pub fn decode_extfvk_with_network(
326 s: &str,
327) -> Result<(NetworkType, ExtendedFullViewingKey), Bech32DecodeError> {
328 let parsed = CheckedHrpstring::new::<Bech32>(s)?;
329 let network = match parsed.hrp().as_str() {
330 mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Main),
331 testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Test),
332 regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => Ok(NetworkType::Regtest),
333 other => Err(Bech32DecodeError::HrpMismatch {
334 expected: format!(
335 "One of {}, {}, or {}",
336 mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
337 testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
338 regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
339 ),
340 actual: other.to_string(),
341 }),
342 }?;
343 let fvk = ExtendedFullViewingKey::read(&parsed.byte_iter().collect::<Vec<_>>()[..])
344 .map_err(|_| Bech32DecodeError::ReadError)?;
345
346 Ok((network, fvk))
347}
348
349#[cfg(feature = "sapling")]
376pub fn encode_payment_address(hrp: &str, addr: &sapling::PaymentAddress) -> String {
377 bech32_encode(hrp, |w| w.write_all(&addr.to_bytes()))
378}
379
380#[cfg(feature = "sapling")]
386pub fn encode_payment_address_p<P: consensus::Parameters>(
387 params: &P,
388 addr: &sapling::PaymentAddress,
389) -> String {
390 encode_payment_address(
391 address_constants::hrp_sapling_payment_address(params.network_type()),
392 addr,
393 )
394}
395
396#[cfg(feature = "sapling")]
426pub fn decode_payment_address(
427 hrp: &str,
428 s: &str,
429) -> Result<sapling::PaymentAddress, Bech32DecodeError> {
430 bech32_decode(hrp, s, |data| {
431 if data.len() != 43 {
432 return None;
433 }
434
435 let mut bytes = [0; 43];
436 bytes.copy_from_slice(&data);
437 sapling::PaymentAddress::from_bytes(&bytes)
438 })
439}
440
441pub fn encode_transparent_address(
468 pubkey_version: &[u8],
469 script_version: &[u8],
470 addr: &TransparentAddress,
471) -> String {
472 let decoded = match addr {
473 TransparentAddress::PublicKeyHash(key_id) => {
474 let mut decoded = vec![0; pubkey_version.len() + 20];
475 decoded[..pubkey_version.len()].copy_from_slice(pubkey_version);
476 decoded[pubkey_version.len()..].copy_from_slice(key_id);
477 decoded
478 }
479 TransparentAddress::ScriptHash(script_id) => {
480 let mut decoded = vec![0; script_version.len() + 20];
481 decoded[..script_version.len()].copy_from_slice(script_version);
482 decoded[script_version.len()..].copy_from_slice(script_id);
483 decoded
484 }
485 };
486 bs58::encode(decoded).with_check().into_string()
487}
488
489pub fn encode_transparent_address_p<P: consensus::Parameters>(
493 params: &P,
494 addr: &TransparentAddress,
495) -> String {
496 encode_transparent_address(
497 &address_constants::b58_pubkey_address_prefix(params.network_type()),
498 &address_constants::b58_script_address_prefix(params.network_type()),
499 addr,
500 )
501}
502
503pub fn decode_transparent_address(
532 pubkey_version: &[u8],
533 script_version: &[u8],
534 s: &str,
535) -> Result<Option<TransparentAddress>, Bs58Error> {
536 bs58::decode(s).with_check(None).into_vec().map(|decoded| {
537 if decoded.starts_with(pubkey_version) {
538 decoded[pubkey_version.len()..]
539 .try_into()
540 .ok()
541 .map(TransparentAddress::PublicKeyHash)
542 } else if decoded.starts_with(script_version) {
543 decoded[script_version.len()..]
544 .try_into()
545 .ok()
546 .map(TransparentAddress::ScriptHash)
547 } else {
548 None
549 }
550 })
551}
552
553#[cfg(test)]
554#[cfg(feature = "sapling")]
555mod tests_sapling {
556 use super::{
557 Bech32DecodeError, decode_extended_full_viewing_key, decode_extended_spending_key,
558 decode_payment_address, encode_extended_full_viewing_key, encode_extended_spending_key,
559 encode_payment_address,
560 };
561 use sapling::{PaymentAddress, zip32::ExtendedSpendingKey};
562 use zcash_address::constants as address_constants;
563
564 use crate::constants;
565
566 #[test]
567 fn extended_spending_key() {
568 let extsk = ExtendedSpendingKey::master(&[0; 32][..])
569 .expect("the derivation path yields a valid key");
570
571 let encoded_main = "secret-extended-key-main1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qysqws3xh6qmha7gna72fs2n4clnc9zgyd22s658f65pex4exe56qjk5pqj9vfdq7dfdhjc2rs9jdwq0zl99uwycyrxzp86705rk687spn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjs87qvlj";
572 let encoded_test = "secret-extended-key-test1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qysqws3xh6qmha7gna72fs2n4clnc9zgyd22s658f65pex4exe56qjk5pqj9vfdq7dfdhjc2rs9jdwq0zl99uwycyrxzp86705rk687spn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjsvzyw8j";
573
574 assert_eq!(
575 encode_extended_spending_key(
576 constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
577 &extsk
578 ),
579 encoded_main
580 );
581 assert_eq!(
582 decode_extended_spending_key(
583 constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
584 encoded_main
585 )
586 .unwrap(),
587 extsk
588 );
589
590 assert_eq!(
591 encode_extended_spending_key(
592 constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
593 &extsk
594 ),
595 encoded_test
596 );
597 assert_eq!(
598 decode_extended_spending_key(
599 constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY,
600 encoded_test
601 )
602 .unwrap(),
603 extsk
604 );
605 }
606
607 #[test]
608 #[allow(deprecated)]
609 fn extended_full_viewing_key() {
610 let extfvk = ExtendedSpendingKey::master(&[0; 32][..])
611 .expect("the derivation path yields a valid key")
612 .to_extended_full_viewing_key();
613
614 let encoded_main = "zxviews1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjsxmansf";
615 let encoded_test = "zxviewtestsapling1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjs8evfkz";
616 let encoded_regtest = "zxviewregtestsapling1qqqqqqqqqqqqqq8n3zjjmvhhr854uy3qhpda3ml34haf0x388z5r7h4st4kpsf6qy3zw4wc246aw9rlfyg5ndlwvne7mwdq0qe6vxl42pqmcf8pvmmd5slmjxduqa9evgej6wa3th2505xq4nggrxdm93rxk4rpdjt5nmq2vn44e2uhm7h0hsagfvkk4n7n6nfer6u57v9cac84t7nl2zth0xpyfeg0w2p2wv2yn6jn923aaz0vdaml07l60ahapk6efchyxwysrvjskjkzax";
617 assert_eq!(
618 encode_extended_full_viewing_key(
619 constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
620 &extfvk
621 ),
622 encoded_main
623 );
624 assert_eq!(
625 decode_extended_full_viewing_key(
626 constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
627 encoded_main
628 )
629 .unwrap(),
630 extfvk
631 );
632
633 assert_eq!(
634 encode_extended_full_viewing_key(
635 constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
636 &extfvk
637 ),
638 encoded_test
639 );
640
641 assert_eq!(
642 encode_extended_full_viewing_key(
643 constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
644 &extfvk
645 ),
646 encoded_regtest
647 );
648
649 assert_eq!(
650 decode_extended_full_viewing_key(
651 constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
652 encoded_regtest
653 )
654 .unwrap(),
655 extfvk
656 );
657 }
658
659 #[test]
660 fn payment_address() {
661 let addr = PaymentAddress::from_bytes(&[
662 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
663 0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
664 0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
665 0xea,
666 ])
667 .unwrap();
668
669 let encoded_main =
670 "zs1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75c8v35z";
671 let encoded_test = "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk";
672 let encoded_regtest = "zregtestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle7505hlz3";
673
674 assert_eq!(
675 encode_payment_address(
676 address_constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS,
677 &addr
678 ),
679 encoded_main
680 );
681
682 assert_eq!(
683 decode_payment_address(
684 address_constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS,
685 encoded_main
686 )
687 .unwrap(),
688 addr
689 );
690
691 assert_eq!(
692 encode_payment_address(
693 address_constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
694 &addr
695 ),
696 encoded_test
697 );
698
699 assert_eq!(
700 encode_payment_address(
701 address_constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS,
702 &addr
703 ),
704 encoded_regtest
705 );
706
707 assert_eq!(
708 decode_payment_address(
709 address_constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS,
710 encoded_test
711 )
712 .unwrap(),
713 addr
714 );
715
716 assert_eq!(
717 decode_payment_address(
718 address_constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS,
719 encoded_regtest
720 )
721 .unwrap(),
722 addr
723 );
724 }
725
726 #[test]
727 fn invalid_diversifier() {
728 let encoded_main =
730 "zs1qyqszqgpqyqszqgpqycguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ugum9p";
731
732 assert_eq!(
733 decode_payment_address(
734 address_constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS,
735 encoded_main,
736 ),
737 Err(Bech32DecodeError::ReadError)
738 );
739 }
740}