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