zcash_keys/
encoding.rs

1//! Encoding and decoding functions for Zcash key and address structs.
2//!
3//! Human-Readable Prefixes (HRPs) for Bech32 encodings are located in the
4//! [zcash_protocol::constants] module.
5
6use 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
97/// A trait for encoding and decoding Zcash addresses.
98pub trait AddressCodec<P>
99where
100    Self: core::marker::Sized,
101{
102    type Error;
103
104    /// Encode a Zcash address.
105    ///
106    /// # Arguments
107    /// * `params` - The network the address is to be used on.
108    fn encode(&self, params: &P) -> String;
109
110    /// Decodes a Zcash address from its string representation.
111    ///
112    /// # Arguments
113    /// * `params` - The network the address is to be used on.
114    /// * `address` - The string representation of the address.
115    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            &params.b58_pubkey_address_prefix(),
145            &params.b58_script_address_prefix(),
146            self,
147        )
148    }
149
150    fn decode(params: &P, address: &str) -> Result<TransparentAddress, TransparentCodecError> {
151        decode_transparent_address(
152            &params.b58_pubkey_address_prefix(),
153            &params.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/// Writes an [`ExtendedSpendingKey`] as a Bech32-encoded string.
199///
200/// # Examples
201///
202/// ```
203/// use zcash_protocol::constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_SPENDING_KEY};
204/// use zip32::AccountId;
205///
206/// use zcash_keys::{
207///     encoding::encode_extended_spending_key,
208///     keys::sapling,
209/// };
210///
211/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO);
212/// let encoded = encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, &extsk);
213/// ```
214/// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
215#[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/// Decodes an [`ExtendedSpendingKey`] from a Bech32-encoded string.
221///
222/// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
223#[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/// Writes an [`ExtendedFullViewingKey`] as a Bech32-encoded string.
232///
233/// # Examples
234///
235/// ```
236/// use ::sapling::zip32::ExtendedFullViewingKey;
237/// use zcash_protocol::constants::testnet::{COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY};
238/// use zip32::AccountId;
239/// use zcash_keys::{
240///     encoding::encode_extended_full_viewing_key,
241///     keys::sapling,
242/// };
243///
244/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO);
245/// let extfvk = extsk.to_extended_full_viewing_key();
246/// let encoded = encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk);
247/// ```
248/// [`ExtendedFullViewingKey`]: sapling::zip32::ExtendedFullViewingKey
249#[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/// Decodes an [`ExtendedFullViewingKey`] from a Bech32-encoded string, verifying that it matches
255/// the provided human-readable prefix.
256#[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/// Decodes an [`ExtendedFullViewingKey`] and the [`NetworkType`] that it is intended for use with
265/// from a Bech32-encoded string.
266#[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/// Writes a [`PaymentAddress`] as a Bech32-encoded string.
294///
295/// # Examples
296///
297/// ```
298/// use group::Group;
299/// use sapling::{Diversifier, PaymentAddress};
300/// use zcash_keys::{
301///     encoding::encode_payment_address,
302/// };
303/// use zcash_protocol::constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS;
304///
305/// let pa = PaymentAddress::from_bytes(&[
306///     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
307///     0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
308///     0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
309///     0xea,
310/// ])
311/// .unwrap();
312///
313/// assert_eq!(
314///     encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &pa),
315///     "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk",
316/// );
317/// ```
318/// [`PaymentAddress`]: sapling::PaymentAddress
319#[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/// Writes a [`PaymentAddress`] as a Bech32-encoded string
325/// using the human-readable prefix values defined in the specified
326/// network parameters.
327///
328/// [`PaymentAddress`]: sapling::PaymentAddress
329#[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/// Decodes a [`PaymentAddress`] from a Bech32-encoded string.
338///
339/// # Examples
340///
341/// ```
342/// use group::Group;
343/// use sapling::{Diversifier, PaymentAddress};
344/// use zcash_keys::{
345///     encoding::decode_payment_address,
346/// };
347/// use zcash_protocol::consensus::{TEST_NETWORK, NetworkConstants, Parameters};
348///
349/// let pa = PaymentAddress::from_bytes(&[
350///     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
351///     0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
352///     0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
353///     0xea,
354/// ])
355/// .unwrap();
356///
357/// assert_eq!(
358///     decode_payment_address(
359///         TEST_NETWORK.hrp_sapling_payment_address(),
360///         "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk",
361///     ),
362///     Ok(pa),
363/// );
364/// ```
365/// [`PaymentAddress`]: sapling::PaymentAddress
366#[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
382/// Writes a [`TransparentAddress`] as a Base58Check-encoded string.
383///
384/// # Examples
385///
386/// ```
387/// use zcash_keys::encoding::encode_transparent_address;
388/// use zcash_protocol::consensus::{TEST_NETWORK, NetworkConstants, Parameters};
389/// use transparent::address::TransparentAddress;
390///
391/// assert_eq!(
392///     encode_transparent_address(
393///         &TEST_NETWORK.b58_pubkey_address_prefix(),
394///         &TEST_NETWORK.b58_script_address_prefix(),
395///         &TransparentAddress::PublicKeyHash([0; 20]),
396///     ),
397///     "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
398/// );
399///
400/// assert_eq!(
401///     encode_transparent_address(
402///         &TEST_NETWORK.b58_pubkey_address_prefix(),
403///         &TEST_NETWORK.b58_script_address_prefix(),
404///         &TransparentAddress::ScriptHash([0; 20]),
405///     ),
406///     "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
407/// );
408pub 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
430/// Writes a [`TransparentAddress`] as a Base58Check-encoded string.
431/// using the human-readable prefix values defined in the specified
432/// network parameters.
433pub fn encode_transparent_address_p<P: consensus::Parameters>(
434    params: &P,
435    addr: &TransparentAddress,
436) -> String {
437    encode_transparent_address(
438        &params.b58_pubkey_address_prefix(),
439        &params.b58_script_address_prefix(),
440        addr,
441    )
442}
443
444/// Decodes a [`TransparentAddress`] from a Base58Check-encoded string.
445///
446/// # Examples
447///
448/// ```
449/// use zcash_protocol::consensus::{TEST_NETWORK, NetworkConstants, Parameters};
450/// use transparent::address::TransparentAddress;
451/// use zcash_keys::{
452///     encoding::decode_transparent_address,
453/// };
454///
455/// assert_eq!(
456///     decode_transparent_address(
457///         &TEST_NETWORK.b58_pubkey_address_prefix(),
458///         &TEST_NETWORK.b58_script_address_prefix(),
459///         "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
460///     ),
461///     Ok(Some(TransparentAddress::PublicKeyHash([0; 20]))),
462/// );
463///
464/// assert_eq!(
465///     decode_transparent_address(
466///         &TEST_NETWORK.b58_pubkey_address_prefix(),
467///         &TEST_NETWORK.b58_script_address_prefix(),
468///         "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
469///     ),
470///     Ok(Some(TransparentAddress::ScriptHash([0; 20]))),
471/// );
472pub 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        // Has a diversifier of `[1u8; 11]`.
658        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}