Skip to main content

zcash_keys/
encoding.rs

1//! Encoding and decoding functions for Zcash key and address structs.
2//!
3//! The prefixes of these encodings are defined in [`crate::constants`] for keys, and in
4//! [`zcash_address::constants`] for addresses.
5
6use 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
96/// A trait for encoding and decoding Zcash addresses.
97pub trait AddressCodec<P>
98where
99    Self: core::marker::Sized,
100{
101    type Error;
102
103    /// Encode a Zcash address.
104    ///
105    /// # Arguments
106    /// * `params` - The network the address is to be used on.
107    fn encode(&self, params: &P) -> String;
108
109    /// Decodes a Zcash address from its string representation.
110    ///
111    /// # Arguments
112    /// * `params` - The network the address is to be used on.
113    /// * `address` - The string representation of the address.
114    fn decode(params: &P, address: &str) -> Result<Self, Self::Error>;
115}
116
117/// An error that prevents encoding a unified address or viewing key at a requested
118/// [ZIP 316] revision.
119///
120/// [ZIP 316]: https://zips.z.cash/zip-0316
121#[derive(Debug, PartialEq, Eq)]
122#[non_exhaustive]
123pub enum UnifiedEncodingError {
124    /// The value has no valid encoding at `revision`. For example, Revision 0 cannot
125    /// encode expiry metadata, a P2SH viewing key item, or a container whose only data
126    /// items are transparent.
127    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/// An error that prevents decoding a [ZIP 316] unified address or viewing key.
147///
148/// [ZIP 316]: https://zips.z.cash/zip-0316
149#[derive(Debug, PartialEq, Eq)]
150#[non_exhaustive]
151pub enum UnifiedDecodingError {
152    /// The string is not a valid unified container of the expected kind.
153    Parse(unified::ParseError),
154    /// The value is for a network other than the expected one.
155    NetworkMismatch {
156        expected: consensus::NetworkType,
157        actual: consensus::NetworkType,
158    },
159    /// The data of the item with this typecode is not a valid item of its type.
160    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/// Writes an [`ExtendedSpendingKey`] as a Bech32-encoded string.
255///
256/// # Examples
257///
258/// ```
259/// use zcash_keys::constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY;
260/// use zcash_protocol::constants::testnet::COIN_TYPE;
261/// use zip32::AccountId;
262///
263/// use zcash_keys::{
264///     encoding::encode_extended_spending_key,
265///     keys::sapling,
266/// };
267///
268/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO).expect("the derivation path yields a valid key");
269/// let encoded = encode_extended_spending_key(HRP_SAPLING_EXTENDED_SPENDING_KEY, &extsk);
270/// ```
271/// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
272#[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/// Decodes an [`ExtendedSpendingKey`] from a Bech32-encoded string.
278///
279/// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
280#[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/// Writes an [`ExtendedFullViewingKey`] as a Bech32-encoded string.
289///
290/// # Examples
291///
292/// ```
293/// use ::sapling::zip32::ExtendedFullViewingKey;
294/// use zcash_keys::constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY;
295/// use zcash_protocol::constants::testnet::COIN_TYPE;
296/// use zip32::AccountId;
297/// use zcash_keys::{
298///     encoding::encode_extended_full_viewing_key,
299///     keys::sapling,
300/// };
301///
302/// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO).expect("the derivation path yields a valid key");
303/// let extfvk = extsk.to_extended_full_viewing_key();
304/// let encoded = encode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &extfvk);
305/// ```
306/// [`ExtendedFullViewingKey`]: sapling::zip32::ExtendedFullViewingKey
307#[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/// Decodes an [`ExtendedFullViewingKey`] from a Bech32-encoded string, verifying that it matches
313/// the provided human-readable prefix.
314#[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/// Decodes an [`ExtendedFullViewingKey`] and the [`NetworkType`] that it is intended for use with
323/// from a Bech32-encoded string.
324#[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/// Writes a [`PaymentAddress`] as a Bech32-encoded string.
350///
351/// # Examples
352///
353/// ```
354/// use group::Group;
355/// use sapling::{Diversifier, PaymentAddress};
356/// use zcash_keys::{
357///     encoding::encode_payment_address,
358/// };
359/// use zcash_address::constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS;
360///
361/// let pa = PaymentAddress::from_bytes(&[
362///     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
363///     0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
364///     0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
365///     0xea,
366/// ])
367/// .unwrap();
368///
369/// assert_eq!(
370///     encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &pa),
371///     "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk",
372/// );
373/// ```
374/// [`PaymentAddress`]: sapling::PaymentAddress
375#[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/// Writes a [`PaymentAddress`] as a Bech32-encoded string
381/// using the human-readable prefix values defined in the specified
382/// network parameters.
383///
384/// [`PaymentAddress`]: sapling::PaymentAddress
385#[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/// Decodes a [`PaymentAddress`] from a Bech32-encoded string.
397///
398/// # Examples
399///
400/// ```
401/// use group::Group;
402/// use sapling::{Diversifier, PaymentAddress};
403/// use zcash_keys::{
404///     encoding::decode_payment_address,
405/// };
406/// use zcash_address::constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS;
407///
408/// let pa = PaymentAddress::from_bytes(&[
409///     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
410///     0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
411///     0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
412///     0xea,
413/// ])
414/// .unwrap();
415///
416/// assert_eq!(
417///     decode_payment_address(
418///         HRP_SAPLING_PAYMENT_ADDRESS,
419///         "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk",
420///     ),
421///     Ok(pa),
422/// );
423/// ```
424/// [`PaymentAddress`]: sapling::PaymentAddress
425#[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
441/// Writes a [`TransparentAddress`] as a Base58Check-encoded string.
442///
443/// # Examples
444///
445/// ```
446/// use zcash_keys::encoding::encode_transparent_address;
447/// use zcash_address::constants::testnet;
448/// use transparent::address::TransparentAddress;
449///
450/// assert_eq!(
451///     encode_transparent_address(
452///         &testnet::B58_PUBKEY_ADDRESS_PREFIX,
453///         &testnet::B58_SCRIPT_ADDRESS_PREFIX,
454///         &TransparentAddress::PublicKeyHash([0; 20]),
455///     ),
456///     "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
457/// );
458///
459/// assert_eq!(
460///     encode_transparent_address(
461///         &testnet::B58_PUBKEY_ADDRESS_PREFIX,
462///         &testnet::B58_SCRIPT_ADDRESS_PREFIX,
463///         &TransparentAddress::ScriptHash([0; 20]),
464///     ),
465///     "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
466/// );
467pub 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
489/// Writes a [`TransparentAddress`] as a Base58Check-encoded string.
490/// using the human-readable prefix values defined in the specified
491/// network parameters.
492pub 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
503/// Decodes a [`TransparentAddress`] from a Base58Check-encoded string.
504///
505/// # Examples
506///
507/// ```
508/// use zcash_address::constants::testnet;
509/// use transparent::address::TransparentAddress;
510/// use zcash_keys::{
511///     encoding::decode_transparent_address,
512/// };
513///
514/// assert_eq!(
515///     decode_transparent_address(
516///         &testnet::B58_PUBKEY_ADDRESS_PREFIX,
517///         &testnet::B58_SCRIPT_ADDRESS_PREFIX,
518///         "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
519///     ),
520///     Ok(Some(TransparentAddress::PublicKeyHash([0; 20]))),
521/// );
522///
523/// assert_eq!(
524///     decode_transparent_address(
525///         &testnet::B58_PUBKEY_ADDRESS_PREFIX,
526///         &testnet::B58_SCRIPT_ADDRESS_PREFIX,
527///         "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
528///     ),
529///     Ok(Some(TransparentAddress::ScriptHash([0; 20]))),
530/// );
531pub 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        // Has a diversifier of `[1u8; 11]`.
729        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}