zcash_keys/
keys.rs

1//! Helper functions for managing light client key material.
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4use core::fmt::{self, Display};
5
6use zcash_address::unified::{self, Container, Encoding, Typecode, Ufvk, Uivk};
7use zcash_protocol::consensus;
8use zip32::{AccountId, DiversifierIndex};
9
10use crate::address::UnifiedAddress;
11
12#[cfg(any(feature = "sapling", feature = "orchard"))]
13use zcash_protocol::consensus::NetworkConstants;
14
15#[cfg(feature = "transparent-inputs")]
16use {
17    core::convert::TryInto,
18    transparent::keys::{IncomingViewingKey, NonHardenedChildIndex},
19};
20
21#[cfg(all(
22    feature = "transparent-inputs",
23    any(test, feature = "test-dependencies")
24))]
25use transparent::address::TransparentAddress;
26
27#[cfg(feature = "unstable")]
28use {
29    byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt},
30    core::convert::TryFrom,
31    core2::io::{Read, Write},
32    zcash_encoding::CompactSize,
33    zcash_protocol::consensus::BranchId,
34};
35
36#[cfg(feature = "orchard")]
37use orchard::{self, keys::Scope};
38
39#[cfg(all(feature = "sapling", feature = "unstable"))]
40use ::sapling::zip32::ExtendedFullViewingKey;
41
42#[cfg(feature = "sapling")]
43pub mod sapling {
44    pub use sapling::zip32::{
45        DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey,
46    };
47    use zip32::{AccountId, ChildIndex};
48
49    /// Derives the ZIP 32 [`ExtendedSpendingKey`] for a given coin type and account from the
50    /// given seed.
51    ///
52    /// # Panics
53    ///
54    /// Panics if `seed` is shorter than 32 bytes.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use zcash_protocol::constants::testnet::COIN_TYPE;
60    /// use zcash_keys::keys::sapling;
61    /// use zip32::AccountId;
62    ///
63    /// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO);
64    /// ```
65    /// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
66    pub fn spending_key(seed: &[u8], coin_type: u32, account: AccountId) -> ExtendedSpendingKey {
67        if seed.len() < 32 {
68            panic!("ZIP 32 seeds MUST be at least 32 bytes");
69        }
70
71        ExtendedSpendingKey::from_path(
72            &ExtendedSpendingKey::master(seed),
73            &[
74                ChildIndex::hardened(32),
75                ChildIndex::hardened(coin_type),
76                account.into(),
77            ],
78        )
79    }
80}
81
82#[cfg(feature = "transparent-inputs")]
83fn to_transparent_child_index(j: DiversifierIndex) -> Option<NonHardenedChildIndex> {
84    let (low_4_bytes, rest) = j.as_bytes().split_at(4);
85    let transparent_j = u32::from_le_bytes(low_4_bytes.try_into().unwrap());
86    if rest.iter().any(|b| b != &0) {
87        None
88    } else {
89        NonHardenedChildIndex::from_index(transparent_j)
90    }
91}
92
93#[derive(Debug)]
94pub enum DerivationError {
95    #[cfg(feature = "orchard")]
96    Orchard(orchard::zip32::Error),
97    #[cfg(feature = "transparent-inputs")]
98    Transparent(bip32::Error),
99}
100
101impl Display for DerivationError {
102    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            #[cfg(feature = "orchard")]
105            DerivationError::Orchard(e) => write!(_f, "Orchard error: {}", e),
106            #[cfg(feature = "transparent-inputs")]
107            DerivationError::Transparent(e) => write!(_f, "Transparent error: {}", e),
108            #[cfg(not(any(feature = "orchard", feature = "transparent-inputs")))]
109            other => {
110                unreachable!("Unhandled DerivationError variant {:?}", other)
111            }
112        }
113    }
114}
115
116#[cfg(feature = "std")]
117impl std::error::Error for DerivationError {}
118
119/// A version identifier for the encoding of unified spending keys.
120///
121/// Each era corresponds to a range of block heights. During an era, the unified spending key
122/// parsed from an encoded form tagged with that era's identifier is expected to provide
123/// sufficient spending authority to spend any non-Sprout shielded note created in a transaction
124/// within the era's block range.
125#[cfg(feature = "unstable")]
126#[derive(Debug, PartialEq, Eq)]
127pub enum Era {
128    /// The Orchard era begins at Orchard activation, and will end if a new pool that requires a
129    /// change to unified spending keys is introduced.
130    Orchard,
131}
132
133/// A type for errors that can occur when decoding keys from their serialized representations.
134#[derive(Debug, PartialEq, Eq)]
135pub enum DecodingError {
136    #[cfg(feature = "unstable")]
137    ReadError(&'static str),
138    #[cfg(feature = "unstable")]
139    EraInvalid,
140    #[cfg(feature = "unstable")]
141    EraMismatch(Era),
142    #[cfg(feature = "unstable")]
143    TypecodeInvalid,
144    #[cfg(feature = "unstable")]
145    LengthInvalid,
146    #[cfg(feature = "unstable")]
147    LengthMismatch(Typecode, u32),
148    #[cfg(feature = "unstable")]
149    InsufficientData(Typecode),
150    /// The key data could not be decoded from its string representation to a valid key.
151    KeyDataInvalid(Typecode),
152}
153
154impl core::fmt::Display for DecodingError {
155    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
156        match self {
157            #[cfg(feature = "unstable")]
158            DecodingError::ReadError(s) => write!(f, "Read error: {}", s),
159            #[cfg(feature = "unstable")]
160            DecodingError::EraInvalid => write!(f, "Invalid era"),
161            #[cfg(feature = "unstable")]
162            DecodingError::EraMismatch(e) => write!(f, "Era mismatch: actual {:?}", e),
163            #[cfg(feature = "unstable")]
164            DecodingError::TypecodeInvalid => write!(f, "Invalid typecode"),
165            #[cfg(feature = "unstable")]
166            DecodingError::LengthInvalid => write!(f, "Invalid length"),
167            #[cfg(feature = "unstable")]
168            DecodingError::LengthMismatch(t, l) => {
169                write!(
170                    f,
171                    "Length mismatch: received {} bytes for typecode {:?}",
172                    l, t
173                )
174            }
175            #[cfg(feature = "unstable")]
176            DecodingError::InsufficientData(t) => {
177                write!(f, "Insufficient data for typecode {:?}", t)
178            }
179            DecodingError::KeyDataInvalid(t) => write!(f, "Invalid key data for key type {:?}", t),
180        }
181    }
182}
183
184#[cfg(feature = "std")]
185impl std::error::Error for DecodingError {}
186
187#[cfg(feature = "unstable")]
188impl Era {
189    /// Returns the unique identifier for the era.
190    fn id(&self) -> u32 {
191        // We use the consensus branch id of the network upgrade that introduced a
192        // new USK format as the identifier for the era.
193        match self {
194            Era::Orchard => u32::from(BranchId::Nu5),
195        }
196    }
197
198    fn try_from_id(id: u32) -> Option<Self> {
199        BranchId::try_from(id).ok().and_then(|b| match b {
200            BranchId::Nu5 => Some(Era::Orchard),
201            _ => None,
202        })
203    }
204}
205
206/// A set of spending keys that are all associated with a single ZIP-0032 account identifier.
207#[derive(Clone, Debug)]
208pub struct UnifiedSpendingKey {
209    #[cfg(feature = "transparent-inputs")]
210    transparent: transparent::keys::AccountPrivKey,
211    #[cfg(feature = "sapling")]
212    sapling: sapling::ExtendedSpendingKey,
213    #[cfg(feature = "orchard")]
214    orchard: orchard::keys::SpendingKey,
215}
216
217impl UnifiedSpendingKey {
218    pub fn from_seed<P: consensus::Parameters>(
219        _params: &P,
220        seed: &[u8],
221        _account: AccountId,
222    ) -> Result<UnifiedSpendingKey, DerivationError> {
223        if seed.len() < 32 {
224            panic!("ZIP 32 seeds MUST be at least 32 bytes");
225        }
226
227        UnifiedSpendingKey::from_checked_parts(
228            #[cfg(feature = "transparent-inputs")]
229            transparent::keys::AccountPrivKey::from_seed(_params, seed, _account)
230                .map_err(DerivationError::Transparent)?,
231            #[cfg(feature = "sapling")]
232            sapling::spending_key(seed, _params.coin_type(), _account),
233            #[cfg(feature = "orchard")]
234            orchard::keys::SpendingKey::from_zip32_seed(seed, _params.coin_type(), _account)
235                .map_err(DerivationError::Orchard)?,
236        )
237    }
238
239    /// Construct a USK from its constituent parts, after verifying that UIVK derivation can
240    /// succeed.
241    fn from_checked_parts(
242        #[cfg(feature = "transparent-inputs")] transparent: transparent::keys::AccountPrivKey,
243        #[cfg(feature = "sapling")] sapling: sapling::ExtendedSpendingKey,
244        #[cfg(feature = "orchard")] orchard: orchard::keys::SpendingKey,
245    ) -> Result<UnifiedSpendingKey, DerivationError> {
246        // Verify that FVK and IVK derivation succeed; we don't want to construct a USK
247        // that can't derive transparent addresses.
248        #[cfg(feature = "transparent-inputs")]
249        let _ = transparent.to_account_pubkey().derive_external_ivk()?;
250
251        Ok(UnifiedSpendingKey {
252            #[cfg(feature = "transparent-inputs")]
253            transparent,
254            #[cfg(feature = "sapling")]
255            sapling,
256            #[cfg(feature = "orchard")]
257            orchard,
258        })
259    }
260
261    pub fn to_unified_full_viewing_key(&self) -> UnifiedFullViewingKey {
262        UnifiedFullViewingKey {
263            #[cfg(feature = "transparent-inputs")]
264            transparent: Some(self.transparent.to_account_pubkey()),
265            #[cfg(feature = "sapling")]
266            sapling: Some(self.sapling.to_diversifiable_full_viewing_key()),
267            #[cfg(feature = "orchard")]
268            orchard: Some((&self.orchard).into()),
269            unknown: vec![],
270        }
271    }
272
273    /// Returns the transparent component of the unified key at the
274    /// BIP44 path `m/44'/<coin_type>'/<account>'`.
275    #[cfg(feature = "transparent-inputs")]
276    pub fn transparent(&self) -> &transparent::keys::AccountPrivKey {
277        &self.transparent
278    }
279
280    /// Returns the Sapling extended spending key component of this unified spending key.
281    #[cfg(feature = "sapling")]
282    pub fn sapling(&self) -> &sapling::ExtendedSpendingKey {
283        &self.sapling
284    }
285
286    /// Returns the Orchard spending key component of this unified spending key.
287    #[cfg(feature = "orchard")]
288    pub fn orchard(&self) -> &orchard::keys::SpendingKey {
289        &self.orchard
290    }
291
292    /// Returns a binary encoding of this key suitable for decoding with [`Self::from_bytes`].
293    ///
294    /// The encoded form of a unified spending key is only intended for use
295    /// within wallets when required for storage and/or crossing FFI boundaries;
296    /// unified spending keys should not be exposed to users, and consequently
297    /// no string-based encoding is defined. This encoding does not include any
298    /// internal validation metadata (such as checksums) as keys decoded from
299    /// this form will necessarily be validated when the attempt is made to
300    /// spend a note that they have authority for.
301    #[cfg(feature = "unstable")]
302    pub fn to_bytes(&self, era: Era) -> Vec<u8> {
303        let mut result = vec![];
304        result.write_u32::<LittleEndian>(era.id()).unwrap();
305
306        #[cfg(feature = "orchard")]
307        {
308            let orchard_key = self.orchard();
309            CompactSize::write(&mut result, usize::try_from(Typecode::Orchard).unwrap()).unwrap();
310
311            let orchard_key_bytes = orchard_key.to_bytes();
312            CompactSize::write(&mut result, orchard_key_bytes.len()).unwrap();
313            result.write_all(orchard_key_bytes).unwrap();
314        }
315
316        #[cfg(feature = "sapling")]
317        {
318            let sapling_key = self.sapling();
319            CompactSize::write(&mut result, usize::try_from(Typecode::Sapling).unwrap()).unwrap();
320
321            let sapling_key_bytes = sapling_key.to_bytes();
322            CompactSize::write(&mut result, sapling_key_bytes.len()).unwrap();
323            result.write_all(&sapling_key_bytes).unwrap();
324        }
325
326        #[cfg(feature = "transparent-inputs")]
327        {
328            let account_tkey = self.transparent();
329            CompactSize::write(&mut result, usize::try_from(Typecode::P2pkh).unwrap()).unwrap();
330
331            let account_tkey_bytes = account_tkey.to_bytes();
332            CompactSize::write(&mut result, account_tkey_bytes.len()).unwrap();
333            result.write_all(&account_tkey_bytes).unwrap();
334        }
335
336        result
337    }
338
339    /// Decodes a [`UnifiedSpendingKey`] value from its serialized representation.
340    ///
341    /// See [`Self::to_bytes`] for additional detail about the encoded form.
342    #[allow(clippy::unnecessary_unwrap)]
343    #[cfg(feature = "unstable")]
344    pub fn from_bytes(era: Era, encoded: &[u8]) -> Result<Self, DecodingError> {
345        let mut source = core2::io::Cursor::new(encoded);
346        let decoded_era = source
347            .read_u32::<LittleEndian>()
348            .map_err(|_| DecodingError::ReadError("era"))
349            .and_then(|id| Era::try_from_id(id).ok_or(DecodingError::EraInvalid))?;
350
351        if decoded_era != era {
352            return Err(DecodingError::EraMismatch(decoded_era));
353        }
354
355        #[cfg(feature = "orchard")]
356        let mut orchard = None;
357        #[cfg(feature = "sapling")]
358        let mut sapling = None;
359        #[cfg(feature = "transparent-inputs")]
360        let mut transparent = None;
361        loop {
362            let tc = CompactSize::read_t::<_, u32>(&mut source)
363                .map_err(|_| DecodingError::ReadError("typecode"))
364                .and_then(|v| Typecode::try_from(v).map_err(|_| DecodingError::TypecodeInvalid))?;
365
366            let len = CompactSize::read_t::<_, u32>(&mut source)
367                .map_err(|_| DecodingError::ReadError("key length"))?;
368
369            match tc {
370                Typecode::Orchard => {
371                    if len != 32 {
372                        return Err(DecodingError::LengthMismatch(Typecode::Orchard, len));
373                    }
374
375                    let mut key = [0u8; 32];
376                    source
377                        .read_exact(&mut key)
378                        .map_err(|_| DecodingError::InsufficientData(Typecode::Orchard))?;
379
380                    #[cfg(feature = "orchard")]
381                    {
382                        orchard = Some(
383                            Option::<orchard::keys::SpendingKey>::from(
384                                orchard::keys::SpendingKey::from_bytes(key),
385                            )
386                            .ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?,
387                        );
388                    }
389                }
390                Typecode::Sapling => {
391                    if len != 169 {
392                        return Err(DecodingError::LengthMismatch(Typecode::Sapling, len));
393                    }
394
395                    let mut key = [0u8; 169];
396                    source
397                        .read_exact(&mut key)
398                        .map_err(|_| DecodingError::InsufficientData(Typecode::Sapling))?;
399
400                    #[cfg(feature = "sapling")]
401                    {
402                        sapling = Some(
403                            sapling::ExtendedSpendingKey::from_bytes(&key)
404                                .map_err(|_| DecodingError::KeyDataInvalid(Typecode::Sapling))?,
405                        );
406                    }
407                }
408                Typecode::P2pkh => {
409                    if len != 74 {
410                        return Err(DecodingError::LengthMismatch(Typecode::P2pkh, len));
411                    }
412
413                    let mut key = [0u8; 74];
414                    source
415                        .read_exact(&mut key)
416                        .map_err(|_| DecodingError::InsufficientData(Typecode::P2pkh))?;
417
418                    #[cfg(feature = "transparent-inputs")]
419                    {
420                        transparent = Some(
421                            transparent::keys::AccountPrivKey::from_bytes(&key)
422                                .ok_or(DecodingError::KeyDataInvalid(Typecode::P2pkh))?,
423                        );
424                    }
425                }
426                _ => {
427                    return Err(DecodingError::TypecodeInvalid);
428                }
429            }
430
431            #[cfg(feature = "orchard")]
432            let has_orchard = orchard.is_some();
433            #[cfg(not(feature = "orchard"))]
434            let has_orchard = true;
435
436            #[cfg(feature = "sapling")]
437            let has_sapling = sapling.is_some();
438            #[cfg(not(feature = "sapling"))]
439            let has_sapling = true;
440
441            #[cfg(feature = "transparent-inputs")]
442            let has_transparent = transparent.is_some();
443            #[cfg(not(feature = "transparent-inputs"))]
444            let has_transparent = true;
445
446            if has_orchard && has_sapling && has_transparent {
447                return UnifiedSpendingKey::from_checked_parts(
448                    #[cfg(feature = "transparent-inputs")]
449                    transparent.unwrap(),
450                    #[cfg(feature = "sapling")]
451                    sapling.unwrap(),
452                    #[cfg(feature = "orchard")]
453                    orchard.unwrap(),
454                )
455                .map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh));
456            }
457        }
458    }
459
460    #[cfg(any(test, feature = "test-dependencies"))]
461    pub fn default_address(
462        &self,
463        request: UnifiedAddressRequest,
464    ) -> (UnifiedAddress, DiversifierIndex) {
465        self.to_unified_full_viewing_key()
466            .default_address(request)
467            .unwrap()
468    }
469
470    #[cfg(all(
471        feature = "transparent-inputs",
472        any(test, feature = "test-dependencies")
473    ))]
474    pub fn default_transparent_address(&self) -> (TransparentAddress, NonHardenedChildIndex) {
475        self.transparent()
476            .to_account_pubkey()
477            .derive_external_ivk()
478            .unwrap()
479            .default_address()
480    }
481}
482
483/// Errors that can occur in the generation of unified addresses.
484#[derive(Clone, Debug)]
485pub enum AddressGenerationError {
486    /// The requested diversifier index was outside the range of valid transparent
487    /// child address indices.
488    #[cfg(feature = "transparent-inputs")]
489    InvalidTransparentChildIndex(DiversifierIndex),
490    /// The diversifier index could not be mapped to a valid Sapling diversifier.
491    #[cfg(feature = "sapling")]
492    InvalidSaplingDiversifierIndex(DiversifierIndex),
493    /// The space of available diversifier indices has been exhausted.
494    DiversifierSpaceExhausted,
495    /// A requested address typecode was not recognized, so we are unable to generate the address
496    /// as requested.
497    ReceiverTypeNotSupported(Typecode),
498    /// A requested address typecode was recognized, but the unified key being used to generate the
499    /// address lacks an item of the requested type.
500    KeyNotAvailable(Typecode),
501    /// A Unified address cannot be generated without at least one shielded receiver being
502    /// included.
503    ShieldedReceiverRequired,
504}
505
506impl fmt::Display for AddressGenerationError {
507    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
508        match &self {
509            #[cfg(feature = "transparent-inputs")]
510            AddressGenerationError::InvalidTransparentChildIndex(i) => {
511                write!(
512                    f,
513                    "Child index {:?} does not generate a valid transparent receiver",
514                    i
515                )
516            }
517            #[cfg(feature = "sapling")]
518            AddressGenerationError::InvalidSaplingDiversifierIndex(i) => {
519                write!(
520                    f,
521                    "Child index {:?} does not generate a valid Sapling receiver",
522                    i
523                )
524            }
525            AddressGenerationError::DiversifierSpaceExhausted => {
526                write!(
527                    f,
528                    "Exhausted the space of diversifier indices without finding an address."
529                )
530            }
531            AddressGenerationError::ReceiverTypeNotSupported(t) => {
532                write!(
533                    f,
534                    "Unified Address generation does not yet support receivers of type {:?}.",
535                    t
536                )
537            }
538            AddressGenerationError::KeyNotAvailable(t) => {
539                write!(
540                    f,
541                    "The Unified Viewing Key does not contain a key for typecode {:?}.",
542                    t
543                )
544            }
545            AddressGenerationError::ShieldedReceiverRequired => {
546                write!(f, "A Unified Address requires at least one shielded (Sapling or Orchard) receiver.")
547            }
548        }
549    }
550}
551
552#[cfg(feature = "std")]
553impl std::error::Error for AddressGenerationError {}
554
555/// An enumeration of the ways in which a receiver may be requested to be present in a generated
556/// [`UnifiedAddress`].
557#[derive(Clone, Copy, Debug, PartialEq, Eq)]
558pub enum ReceiverRequirement {
559    /// A receiver of the associated type is required to be present in the generated
560    /// `[UnifiedAddress`], and if it is not possible to generate a receiver of this type, the
561    /// address generation method should return an error. When calling [`Self::intersect`], this
562    /// variant will be preferred over [`ReceiverRequirement::Allow`].
563    Require,
564    /// The associated receiver should be included, if a corresponding item exists in the IVK from
565    /// which the address is being derived and derivation of the receiver succeeds at the given
566    /// diversifier index.
567    Allow,
568    /// No receiver of the associated type may be included in the generated [`UnifiedAddress`]
569    /// under any circumstances. When calling [`Self::intersect`], this variant will be preferred
570    /// over [`ReceiverRequirement::Allow`].
571    Omit,
572}
573
574impl ReceiverRequirement {
575    /// Return the intersection of two requirements that chooses the stronger requirement, if one
576    /// exists. [`ReceiverRequirement::Require`] and [`ReceiverRequirement::Omit`] are
577    /// incompatible; attempting an intersection between these will return an error.
578    pub fn intersect(self, other: Self) -> Result<Self, ()> {
579        use ReceiverRequirement::*;
580        match (self, other) {
581            (Require, Omit) => Err(()),
582            (Require, Require) => Ok(Require),
583            (Require, Allow) => Ok(Require),
584            (Allow, Require) => Ok(Require),
585            (Allow, Allow) => Ok(Allow),
586            (Allow, Omit) => Ok(Omit),
587            (Omit, Require) => Err(()),
588            (Omit, Allow) => Ok(Omit),
589            (Omit, Omit) => Ok(Omit),
590        }
591    }
592}
593
594/// Specification for how a unified address should be generated from a unified viewing key.
595#[derive(Clone, Copy, Debug)]
596pub enum UnifiedAddressRequest {
597    AllAvailableKeys,
598    Custom(ReceiverRequirements),
599}
600
601impl UnifiedAddressRequest {
602    /// Constructs a new unified address request that allows a receiver of each type.
603    pub const ALLOW_ALL: Self = Self::Custom(ReceiverRequirements::ALLOW_ALL);
604
605    /// Constructs a new unified address request that allows only shielded receivers.
606    pub const SHIELDED: Self = Self::Custom(ReceiverRequirements::SHIELDED);
607
608    /// Constructs a new unified address request that requires an Orchard receiver and no others.
609    pub const ORCHARD: Self = Self::Custom(ReceiverRequirements::ORCHARD);
610
611    pub fn custom(
612        orchard: ReceiverRequirement,
613        sapling: ReceiverRequirement,
614        p2pkh: ReceiverRequirement,
615    ) -> Result<Self, ()> {
616        ReceiverRequirements::new(orchard, sapling, p2pkh).map(UnifiedAddressRequest::Custom)
617    }
618
619    pub const fn unsafe_custom(
620        orchard: ReceiverRequirement,
621        sapling: ReceiverRequirement,
622        p2pkh: ReceiverRequirement,
623    ) -> Self {
624        UnifiedAddressRequest::Custom(ReceiverRequirements::unsafe_new(orchard, sapling, p2pkh))
625    }
626}
627
628/// Specification for how a unified address should be generated from a unified viewing key.
629#[derive(Clone, Copy, Debug)]
630pub struct ReceiverRequirements {
631    orchard: ReceiverRequirement,
632    sapling: ReceiverRequirement,
633    p2pkh: ReceiverRequirement,
634}
635
636impl ReceiverRequirements {
637    /// Construct a new unified address request from its constituent parts.
638    ///
639    /// Returns `Err(())` if the resulting unified address would not include at least one shielded receiver.
640    pub fn new(
641        orchard: ReceiverRequirement,
642        sapling: ReceiverRequirement,
643        p2pkh: ReceiverRequirement,
644    ) -> Result<Self, ()> {
645        use ReceiverRequirement::*;
646        if orchard == Omit && sapling == Omit {
647            Err(())
648        } else {
649            Ok(Self {
650                orchard,
651                sapling,
652                p2pkh,
653            })
654        }
655    }
656
657    /// Constructs a new unified address request that allows a receiver of each type.
658    pub const ALLOW_ALL: ReceiverRequirements = {
659        use ReceiverRequirement::*;
660        Self::unsafe_new(Allow, Allow, Allow)
661    };
662
663    /// Constructs a new unified address request that allows only shielded receivers.
664    pub const SHIELDED: ReceiverRequirements = {
665        use ReceiverRequirement::*;
666        Self::unsafe_new(Allow, Allow, Omit)
667    };
668
669    /// Constructs a new unified address request that requires an Orchard receiver, and no others.
670    pub const ORCHARD: ReceiverRequirements = {
671        use ReceiverRequirement::*;
672        Self::unsafe_new(Require, Omit, Omit)
673    };
674
675    /// Constructs a new unified address request that includes only the receivers that are allowed
676    /// both in itself and a given other request. Returns [`None`] if requirements are incompatible
677    /// or if no shielded receiver type is allowed.
678    pub fn intersect(&self, other: &ReceiverRequirements) -> Result<ReceiverRequirements, ()> {
679        let orchard = self.orchard.intersect(other.orchard)?;
680        let sapling = self.sapling.intersect(other.sapling)?;
681        let p2pkh = self.p2pkh.intersect(other.p2pkh)?;
682        Self::new(orchard, sapling, p2pkh)
683    }
684
685    /// Construct a new unified address request from its constituent parts.
686    ///
687    /// Panics: at least one of `orchard` or `sapling` must be allowed.
688    pub const fn unsafe_new(
689        orchard: ReceiverRequirement,
690        sapling: ReceiverRequirement,
691        p2pkh: ReceiverRequirement,
692    ) -> Self {
693        use ReceiverRequirement::*;
694        if matches!(orchard, Omit) && matches!(sapling, Omit) {
695            panic!("At least one shielded receiver must be allowed.")
696        }
697
698        Self {
699            orchard,
700            sapling,
701            p2pkh,
702        }
703    }
704
705    /// Returns the [`ReceiverRequirement`] for inclusion of an Orchard receiver.
706    pub fn orchard(&self) -> ReceiverRequirement {
707        self.orchard
708    }
709
710    /// Returns the [`ReceiverRequirement`] for inclusion of a Sapling receiver.
711    pub fn sapling(&self) -> ReceiverRequirement {
712        self.sapling
713    }
714
715    /// Returns the [`ReceiverRequirement`] for inclusion of a P2PKH receiver.
716    pub fn p2pkh(&self) -> ReceiverRequirement {
717        self.p2pkh
718    }
719}
720
721#[cfg(feature = "transparent-inputs")]
722impl From<bip32::Error> for DerivationError {
723    fn from(e: bip32::Error) -> Self {
724        DerivationError::Transparent(e)
725    }
726}
727
728/// A [ZIP 316](https://zips.z.cash/zip-0316) unified full viewing key.
729#[derive(Clone, Debug)]
730pub struct UnifiedFullViewingKey {
731    #[cfg(feature = "transparent-inputs")]
732    transparent: Option<transparent::keys::AccountPubKey>,
733    #[cfg(feature = "sapling")]
734    sapling: Option<sapling::DiversifiableFullViewingKey>,
735    #[cfg(feature = "orchard")]
736    orchard: Option<orchard::keys::FullViewingKey>,
737    unknown: Vec<(u32, Vec<u8>)>,
738}
739
740impl UnifiedFullViewingKey {
741    /// Construct a new unified full viewing key.
742    ///
743    /// This method is only available when the `test-dependencies` feature is enabled,
744    /// as derivation from the USK or deserialization from the serialized form should
745    /// be used instead.
746    #[cfg(any(test, feature = "test-dependencies"))]
747    pub fn new(
748        #[cfg(feature = "transparent-inputs")] transparent: Option<
749            transparent::keys::AccountPubKey,
750        >,
751        #[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
752        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
753        // TODO: Implement construction of UFVKs with metadata items.
754    ) -> Result<UnifiedFullViewingKey, DerivationError> {
755        Self::from_checked_parts(
756            #[cfg(feature = "transparent-inputs")]
757            transparent,
758            #[cfg(feature = "sapling")]
759            sapling,
760            #[cfg(feature = "orchard")]
761            orchard,
762            // We don't currently allow constructing new UFVKs with unknown items, but we store
763            // this to allow parsing such UFVKs.
764            vec![],
765        )
766    }
767
768    #[cfg(feature = "unstable-frost")]
769    pub fn from_orchard_fvk(
770        orchard: orchard::keys::FullViewingKey,
771    ) -> Result<UnifiedFullViewingKey, DerivationError> {
772        Self::from_checked_parts(
773            #[cfg(feature = "transparent-inputs")]
774            None,
775            #[cfg(feature = "sapling")]
776            None,
777            #[cfg(feature = "orchard")]
778            Some(orchard),
779            // We don't currently allow constructing new UFVKs with unknown items, but we store
780            // this to allow parsing such UFVKs.
781            vec![],
782        )
783    }
784
785    #[cfg(all(feature = "sapling", feature = "unstable"))]
786    pub fn from_sapling_extended_full_viewing_key(
787        sapling: ExtendedFullViewingKey,
788    ) -> Result<UnifiedFullViewingKey, DerivationError> {
789        Self::from_checked_parts(
790            #[cfg(feature = "transparent-inputs")]
791            None,
792            #[cfg(feature = "sapling")]
793            Some(sapling.to_diversifiable_full_viewing_key()),
794            #[cfg(feature = "orchard")]
795            None,
796            // We don't currently allow constructing new UFVKs with unknown items, but we store
797            // this to allow parsing such UFVKs.
798            vec![],
799        )
800    }
801
802    /// Construct a UFVK from its constituent parts, after verifying that UIVK derivation can
803    /// succeed.
804    fn from_checked_parts(
805        #[cfg(feature = "transparent-inputs")] transparent: Option<
806            transparent::keys::AccountPubKey,
807        >,
808        #[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
809        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
810        unknown: Vec<(u32, Vec<u8>)>,
811    ) -> Result<UnifiedFullViewingKey, DerivationError> {
812        // Verify that IVK derivation succeeds; we don't want to construct a UFVK
813        // that can't derive transparent addresses.
814        #[cfg(feature = "transparent-inputs")]
815        let _ = transparent
816            .as_ref()
817            .map(|t| t.derive_external_ivk())
818            .transpose()?;
819
820        Ok(UnifiedFullViewingKey {
821            #[cfg(feature = "transparent-inputs")]
822            transparent,
823            #[cfg(feature = "sapling")]
824            sapling,
825            #[cfg(feature = "orchard")]
826            orchard,
827            unknown,
828        })
829    }
830
831    /// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
832    ///
833    /// [ZIP 316]: https://zips.z.cash/zip-0316
834    pub fn decode<P: consensus::Parameters>(params: &P, encoding: &str) -> Result<Self, String> {
835        let (net, ufvk) = unified::Ufvk::decode(encoding).map_err(|e| e.to_string())?;
836        let expected_net = params.network_type();
837        if net != expected_net {
838            return Err(format!(
839                "UFVK is for network {:?} but we expected {:?}",
840                net, expected_net,
841            ));
842        }
843
844        Self::parse(&ufvk).map_err(|e| e.to_string())
845    }
846
847    /// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
848    ///
849    /// [ZIP 316]: https://zips.z.cash/zip-0316
850    pub fn parse(ufvk: &Ufvk) -> Result<Self, DecodingError> {
851        #[cfg(feature = "orchard")]
852        let mut orchard = None;
853        #[cfg(feature = "sapling")]
854        let mut sapling = None;
855        #[cfg(feature = "transparent-inputs")]
856        let mut transparent = None;
857
858        // We can use as-parsed order here for efficiency, because we're breaking out the
859        // receivers we support from the unknown receivers.
860        let unknown = ufvk
861            .items_as_parsed()
862            .iter()
863            .filter_map(|receiver| match receiver {
864                #[cfg(feature = "orchard")]
865                unified::Fvk::Orchard(data) => orchard::keys::FullViewingKey::from_bytes(data)
866                    .ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))
867                    .map(|addr| {
868                        orchard = Some(addr);
869                        None
870                    })
871                    .transpose(),
872                #[cfg(not(feature = "orchard"))]
873                unified::Fvk::Orchard(data) => Some(Ok::<_, DecodingError>((
874                    u32::from(unified::Typecode::Orchard),
875                    data.to_vec(),
876                ))),
877                #[cfg(feature = "sapling")]
878                unified::Fvk::Sapling(data) => {
879                    sapling::DiversifiableFullViewingKey::from_bytes(data)
880                        .ok_or(DecodingError::KeyDataInvalid(Typecode::Sapling))
881                        .map(|pa| {
882                            sapling = Some(pa);
883                            None
884                        })
885                        .transpose()
886                }
887                #[cfg(not(feature = "sapling"))]
888                unified::Fvk::Sapling(data) => Some(Ok::<_, DecodingError>((
889                    u32::from(unified::Typecode::Sapling),
890                    data.to_vec(),
891                ))),
892                #[cfg(feature = "transparent-inputs")]
893                unified::Fvk::P2pkh(data) => transparent::keys::AccountPubKey::deserialize(data)
894                    .map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))
895                    .map(|tfvk| {
896                        transparent = Some(tfvk);
897                        None
898                    })
899                    .transpose(),
900                #[cfg(not(feature = "transparent-inputs"))]
901                unified::Fvk::P2pkh(data) => Some(Ok::<_, DecodingError>((
902                    u32::from(unified::Typecode::P2pkh),
903                    data.to_vec(),
904                ))),
905                unified::Fvk::Unknown { typecode, data } => Some(Ok((*typecode, data.clone()))),
906            })
907            .collect::<Result<_, _>>()?;
908
909        Self::from_checked_parts(
910            #[cfg(feature = "transparent-inputs")]
911            transparent,
912            #[cfg(feature = "sapling")]
913            sapling,
914            #[cfg(feature = "orchard")]
915            orchard,
916            unknown,
917        )
918        .map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))
919    }
920
921    /// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
922    pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
923        self.to_ufvk().encode(&params.network_type())
924    }
925
926    /// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
927    fn to_ufvk(&self) -> Ufvk {
928        let items = core::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
929            unified::Fvk::Unknown {
930                typecode: *typecode,
931                data: data.clone(),
932            }
933        }));
934        #[cfg(feature = "orchard")]
935        let items = items.chain(
936            self.orchard
937                .as_ref()
938                .map(|fvk| fvk.to_bytes())
939                .map(unified::Fvk::Orchard),
940        );
941        #[cfg(feature = "sapling")]
942        let items = items.chain(
943            self.sapling
944                .as_ref()
945                .map(|dfvk| dfvk.to_bytes())
946                .map(unified::Fvk::Sapling),
947        );
948        #[cfg(feature = "transparent-inputs")]
949        let items = items.chain(
950            self.transparent
951                .as_ref()
952                .map(|tfvk| tfvk.serialize().try_into().unwrap())
953                .map(unified::Fvk::P2pkh),
954        );
955
956        unified::Ufvk::try_from_items(items.collect())
957            .expect("UnifiedFullViewingKey should only be constructed safely")
958    }
959
960    /// Derives a Unified Incoming Viewing Key from this Unified Full Viewing Key.
961    pub fn to_unified_incoming_viewing_key(&self) -> UnifiedIncomingViewingKey {
962        UnifiedIncomingViewingKey {
963            #[cfg(feature = "transparent-inputs")]
964            transparent: self.transparent.as_ref().map(|t| {
965                t.derive_external_ivk()
966                    .expect("Transparent IVK derivation was checked at construction.")
967            }),
968            #[cfg(feature = "sapling")]
969            sapling: self.sapling.as_ref().map(|s| s.to_external_ivk()),
970            #[cfg(feature = "orchard")]
971            orchard: self.orchard.as_ref().map(|o| o.to_ivk(Scope::External)),
972            unknown: Vec::new(),
973        }
974    }
975
976    /// Returns the transparent component of the unified key at the
977    /// BIP44 path `m/44'/<coin_type>'/<account>'`.
978    #[cfg(feature = "transparent-inputs")]
979    pub fn transparent(&self) -> Option<&transparent::keys::AccountPubKey> {
980        self.transparent.as_ref()
981    }
982
983    /// Returns the Sapling diversifiable full viewing key component of this unified key.
984    #[cfg(feature = "sapling")]
985    pub fn sapling(&self) -> Option<&sapling::DiversifiableFullViewingKey> {
986        self.sapling.as_ref()
987    }
988
989    /// Returns the Orchard full viewing key component of this unified key.
990    #[cfg(feature = "orchard")]
991    pub fn orchard(&self) -> Option<&orchard::keys::FullViewingKey> {
992        self.orchard.as_ref()
993    }
994
995    /// Attempts to derive the Unified Address for the given diversifier index and receiver types.
996    /// If `request` is None, the address should be derived to contain a receiver for each item in
997    /// this UFVK.
998    ///
999    /// Returns `None` if the specified index does not produce a valid diversifier.
1000    pub fn address(
1001        &self,
1002        j: DiversifierIndex,
1003        request: UnifiedAddressRequest,
1004    ) -> Result<UnifiedAddress, AddressGenerationError> {
1005        self.to_unified_incoming_viewing_key().address(j, request)
1006    }
1007
1008    /// Searches the diversifier space starting at diversifier index `j` for one which will produce
1009    /// a valid diversifier, and return the Unified Address constructed using that diversifier
1010    /// along with the index at which the valid diversifier was found. If `request` is None, the
1011    /// address should be derived to contain a receiver for each item in this UFVK.
1012    ///
1013    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
1014    /// required to satisfy the unified address request are not properly enabled.
1015    pub fn find_address(
1016        &self,
1017        j: DiversifierIndex,
1018        request: UnifiedAddressRequest,
1019    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1020        self.to_unified_incoming_viewing_key()
1021            .find_address(j, request)
1022    }
1023
1024    /// Find the Unified Address corresponding to the smallest valid diversifier index, along with
1025    /// that index. If `request` is None, the address should be derived to contain a receiver for
1026    /// each item in this UFVK.
1027    ///
1028    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
1029    /// required to satisfy the unified address request are not properly enabled.
1030    pub fn default_address(
1031        &self,
1032        request: UnifiedAddressRequest,
1033    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1034        self.find_address(DiversifierIndex::new(), request)
1035    }
1036}
1037
1038/// A [ZIP 316](https://zips.z.cash/zip-0316) unified incoming viewing key.
1039#[derive(Clone, Debug)]
1040pub struct UnifiedIncomingViewingKey {
1041    #[cfg(feature = "transparent-inputs")]
1042    transparent: Option<transparent::keys::ExternalIvk>,
1043    #[cfg(feature = "sapling")]
1044    sapling: Option<::sapling::zip32::IncomingViewingKey>,
1045    #[cfg(feature = "orchard")]
1046    orchard: Option<orchard::keys::IncomingViewingKey>,
1047    /// Stores the unrecognized elements of the unified encoding.
1048    unknown: Vec<(u32, Vec<u8>)>,
1049}
1050
1051impl UnifiedIncomingViewingKey {
1052    /// Construct a new unified incoming viewing key.
1053    ///
1054    /// This method is only available when the `test-dependencies` feature is enabled,
1055    /// as derivation from the UFVK or deserialization from the serialized form should
1056    /// be used instead.
1057    #[cfg(any(test, feature = "test-dependencies"))]
1058    pub fn new(
1059        #[cfg(feature = "transparent-inputs")] transparent: Option<transparent::keys::ExternalIvk>,
1060        #[cfg(feature = "sapling")] sapling: Option<::sapling::zip32::IncomingViewingKey>,
1061        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::IncomingViewingKey>,
1062        // TODO: Implement construction of UIVKs with metadata items.
1063    ) -> UnifiedIncomingViewingKey {
1064        UnifiedIncomingViewingKey {
1065            #[cfg(feature = "transparent-inputs")]
1066            transparent,
1067            #[cfg(feature = "sapling")]
1068            sapling,
1069            #[cfg(feature = "orchard")]
1070            orchard,
1071            // We don't allow constructing new UFVKs with unknown items, but we store
1072            // this to allow parsing such UFVKs.
1073            unknown: vec![],
1074        }
1075    }
1076
1077    /// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
1078    ///
1079    /// [ZIP 316]: https://zips.z.cash/zip-0316
1080    pub fn decode<P: consensus::Parameters>(params: &P, encoding: &str) -> Result<Self, String> {
1081        let (net, ufvk) = unified::Uivk::decode(encoding).map_err(|e| e.to_string())?;
1082        let expected_net = params.network_type();
1083        if net != expected_net {
1084            return Err(format!(
1085                "UIVK is for network {:?} but we expected {:?}",
1086                net, expected_net,
1087            ));
1088        }
1089
1090        Self::parse(&ufvk).map_err(|e| e.to_string())
1091    }
1092
1093    /// Constructs a unified incoming viewing key from a parsed unified encoding.
1094    fn parse(uivk: &Uivk) -> Result<Self, DecodingError> {
1095        #[cfg(feature = "orchard")]
1096        let mut orchard = None;
1097        #[cfg(feature = "sapling")]
1098        let mut sapling = None;
1099        #[cfg(feature = "transparent-inputs")]
1100        let mut transparent = None;
1101
1102        let mut unknown = vec![];
1103
1104        // We can use as-parsed order here for efficiency, because we're breaking out the
1105        // receivers we support from the unknown receivers.
1106        for receiver in uivk.items_as_parsed() {
1107            match receiver {
1108                unified::Ivk::Orchard(data) => {
1109                    #[cfg(feature = "orchard")]
1110                    {
1111                        orchard = Some(
1112                            Option::from(orchard::keys::IncomingViewingKey::from_bytes(data))
1113                                .ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?,
1114                        );
1115                    }
1116
1117                    #[cfg(not(feature = "orchard"))]
1118                    unknown.push((u32::from(unified::Typecode::Orchard), data.to_vec()));
1119                }
1120                unified::Ivk::Sapling(data) => {
1121                    #[cfg(feature = "sapling")]
1122                    {
1123                        sapling = Some(
1124                            Option::from(::sapling::zip32::IncomingViewingKey::from_bytes(data))
1125                                .ok_or(DecodingError::KeyDataInvalid(Typecode::Sapling))?,
1126                        );
1127                    }
1128
1129                    #[cfg(not(feature = "sapling"))]
1130                    unknown.push((u32::from(unified::Typecode::Sapling), data.to_vec()));
1131                }
1132                unified::Ivk::P2pkh(data) => {
1133                    #[cfg(feature = "transparent-inputs")]
1134                    {
1135                        transparent = Some(
1136                            transparent::keys::ExternalIvk::deserialize(data)
1137                                .map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))?,
1138                        );
1139                    }
1140
1141                    #[cfg(not(feature = "transparent-inputs"))]
1142                    unknown.push((u32::from(unified::Typecode::P2pkh), data.to_vec()));
1143                }
1144                unified::Ivk::Unknown { typecode, data } => {
1145                    unknown.push((*typecode, data.clone()));
1146                }
1147            }
1148        }
1149
1150        Ok(Self {
1151            #[cfg(feature = "transparent-inputs")]
1152            transparent,
1153            #[cfg(feature = "sapling")]
1154            sapling,
1155            #[cfg(feature = "orchard")]
1156            orchard,
1157            unknown,
1158        })
1159    }
1160
1161    /// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
1162    pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
1163        self.render().encode(&params.network_type())
1164    }
1165
1166    /// Converts this unified incoming viewing key to a unified encoding.
1167    fn render(&self) -> Uivk {
1168        let items = core::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
1169            unified::Ivk::Unknown {
1170                typecode: *typecode,
1171                data: data.clone(),
1172            }
1173        }));
1174        #[cfg(feature = "orchard")]
1175        let items = items.chain(
1176            self.orchard
1177                .as_ref()
1178                .map(|ivk| ivk.to_bytes())
1179                .map(unified::Ivk::Orchard),
1180        );
1181        #[cfg(feature = "sapling")]
1182        let items = items.chain(
1183            self.sapling
1184                .as_ref()
1185                .map(|divk| divk.to_bytes())
1186                .map(unified::Ivk::Sapling),
1187        );
1188        #[cfg(feature = "transparent-inputs")]
1189        let items = items.chain(
1190            self.transparent
1191                .as_ref()
1192                .map(|tivk| tivk.serialize().try_into().unwrap())
1193                .map(unified::Ivk::P2pkh),
1194        );
1195
1196        unified::Uivk::try_from_items(items.collect())
1197            .expect("UnifiedIncomingViewingKey should only be constructed safely.")
1198    }
1199
1200    /// Returns whether this uivk has a transparent key item.
1201    ///
1202    /// This method is available irrespective of whether the `transparent-inputs` feature flag is enabled.
1203    pub fn has_transparent(&self) -> bool {
1204        #[cfg(not(feature = "transparent-inputs"))]
1205        return false;
1206        #[cfg(feature = "transparent-inputs")]
1207        return self.transparent.is_some();
1208    }
1209
1210    /// Returns the Transparent external IVK, if present.
1211    #[cfg(feature = "transparent-inputs")]
1212    pub fn transparent(&self) -> &Option<transparent::keys::ExternalIvk> {
1213        &self.transparent
1214    }
1215
1216    /// Returns whether this uivk has a Sapling key item.
1217    ///
1218    /// This method is available irrespective of whether the `sapling` feature flag is enabled.
1219    pub fn has_sapling(&self) -> bool {
1220        #[cfg(not(feature = "sapling"))]
1221        return false;
1222        #[cfg(feature = "sapling")]
1223        return self.sapling.is_some();
1224    }
1225
1226    /// Returns the Sapling IVK, if present.
1227    #[cfg(feature = "sapling")]
1228    pub fn sapling(&self) -> &Option<::sapling::zip32::IncomingViewingKey> {
1229        &self.sapling
1230    }
1231
1232    /// Returns whether this uivk has an Orchard key item.
1233    ///
1234    /// This method is available irrespective of whether the `orchard` feature flag is enabled.
1235    pub fn has_orchard(&self) -> bool {
1236        #[cfg(not(feature = "orchard"))]
1237        return false;
1238        #[cfg(feature = "orchard")]
1239        return self.orchard.is_some();
1240    }
1241
1242    /// Returns the Orchard IVK, if present.
1243    #[cfg(feature = "orchard")]
1244    pub fn orchard(&self) -> &Option<orchard::keys::IncomingViewingKey> {
1245        &self.orchard
1246    }
1247
1248    /// Attempts to derive the Unified Address for the given diversifier index and receiver types.
1249    /// If `request` is None, the address will be derived to contain a receiver for each item in
1250    /// this UFVK.
1251    ///
1252    /// Returns an error if the this key does not produce a valid receiver for a required receiver
1253    /// type at the given diversifier index.
1254    pub fn address(
1255        &self,
1256        _j: DiversifierIndex,
1257        request: UnifiedAddressRequest,
1258    ) -> Result<UnifiedAddress, AddressGenerationError> {
1259        use ReceiverRequirement::*;
1260
1261        let request = self
1262            .receiver_requirements(request)
1263            .map_err(|_| AddressGenerationError::ShieldedReceiverRequired)?;
1264
1265        // If we need to generate a transparent receiver, check that the user has not
1266        // specified an invalid transparent child index, from which we can never search to
1267        // find a valid index.
1268        #[cfg(feature = "transparent-inputs")]
1269        if request.p2pkh == ReceiverRequirement::Require
1270            && self.transparent.is_some()
1271            && to_transparent_child_index(_j).is_none()
1272        {
1273            return Err(AddressGenerationError::InvalidTransparentChildIndex(_j));
1274        }
1275
1276        #[cfg(feature = "orchard")]
1277        let mut orchard = None;
1278        if request.orchard != Omit {
1279            #[cfg(not(feature = "orchard"))]
1280            if request.orchard == Require {
1281                return Err(AddressGenerationError::ReceiverTypeNotSupported(
1282                    Typecode::Orchard,
1283                ));
1284            }
1285
1286            #[cfg(feature = "orchard")]
1287            if let Some(oivk) = &self.orchard {
1288                let orchard_j = orchard::keys::DiversifierIndex::from(*_j.as_bytes());
1289                orchard = Some(oivk.address_at(orchard_j))
1290            } else if request.orchard == Require {
1291                return Err(AddressGenerationError::KeyNotAvailable(Typecode::Orchard));
1292            }
1293        }
1294
1295        #[cfg(feature = "sapling")]
1296        let mut sapling = None;
1297        if request.sapling != Omit {
1298            #[cfg(not(feature = "sapling"))]
1299            if request.sapling == Require {
1300                return Err(AddressGenerationError::ReceiverTypeNotSupported(
1301                    Typecode::Sapling,
1302                ));
1303            }
1304
1305            #[cfg(feature = "sapling")]
1306            if let Some(divk) = &self.sapling {
1307                // If a Sapling receiver type is requested, we must be able to construct an
1308                // address; if we're unable to do so, then no Unified Address exists at this
1309                // diversifier and we use `?` to early-return from this method.
1310                sapling = match (request.sapling, divk.address_at(_j)) {
1311                    (Require | Allow, Some(addr)) => Ok(Some(addr)),
1312                    (Require, None) => {
1313                        Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_j))
1314                    }
1315                    _ => Ok(None),
1316                }?;
1317            } else if request.sapling == Require {
1318                return Err(AddressGenerationError::KeyNotAvailable(Typecode::Sapling));
1319            }
1320        }
1321
1322        #[cfg(feature = "transparent-inputs")]
1323        let mut transparent = None;
1324        if request.p2pkh != Omit {
1325            #[cfg(not(feature = "transparent-inputs"))]
1326            if request.p2pkh == Require {
1327                return Err(AddressGenerationError::ReceiverTypeNotSupported(
1328                    Typecode::P2pkh,
1329                ));
1330            }
1331
1332            #[cfg(feature = "transparent-inputs")]
1333            if let Some(tivk) = self.transparent.as_ref() {
1334                // If a transparent receiver type is requested, we must be able to construct an
1335                // address; if we're unable to do so, then no Unified Address exists at this
1336                // diversifier.
1337                let j = to_transparent_child_index(_j);
1338
1339                transparent = match (request.p2pkh, j.and_then(|j| tivk.derive_address(j).ok())) {
1340                    (Require | Allow, Some(addr)) => Ok(Some(addr)),
1341                    (Require, None) => {
1342                        Err(AddressGenerationError::InvalidTransparentChildIndex(_j))
1343                    }
1344                    _ => Ok(None),
1345                }?;
1346            } else if request.p2pkh == Require {
1347                return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2pkh));
1348            }
1349        }
1350        #[cfg(not(feature = "transparent-inputs"))]
1351        let transparent = None;
1352
1353        UnifiedAddress::from_receivers(
1354            #[cfg(feature = "orchard")]
1355            orchard,
1356            #[cfg(feature = "sapling")]
1357            sapling,
1358            transparent,
1359        )
1360        .ok_or(AddressGenerationError::ShieldedReceiverRequired)
1361    }
1362
1363    /// Searches the diversifier space starting at diversifier index `j` for one which will produce
1364    /// a valid address that conforms to the provided request, and returns that Unified Address
1365    /// along with the index at which the valid diversifier was found.
1366    ///
1367    /// If [`None`] is specified for the `request` parameter, a default request that [`Require`]s a
1368    /// receiver be present for each key item enabled by the feature flags in use will be used to
1369    /// search the diversifier space.
1370    ///
1371    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
1372    /// required to satisfy the unified address request are not enabled.
1373    ///
1374    /// [`Require`]: ReceiverRequirement::Require
1375    #[allow(unused_mut)]
1376    pub fn find_address(
1377        &self,
1378        mut j: DiversifierIndex,
1379        request: UnifiedAddressRequest,
1380    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1381        // Find a working diversifier and construct the associated address.
1382        loop {
1383            let res = self.address(j, request);
1384            match res {
1385                Ok(ua) => {
1386                    return Ok((ua, j));
1387                }
1388                #[cfg(feature = "sapling")]
1389                Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => {
1390                    if j.increment().is_err() {
1391                        return Err(AddressGenerationError::DiversifierSpaceExhausted);
1392                    }
1393                }
1394                Err(other) => {
1395                    return Err(other);
1396                }
1397            }
1398        }
1399    }
1400
1401    /// Find the Unified Address corresponding to the smallest valid diversifier index, along with
1402    /// that index. If `request` is None, the address will be derived to contain a receiver for
1403    /// each data item in this UFVK.
1404    ///
1405    /// Returns an error if the this key does not produce a valid receiver for a required receiver
1406    /// type at any diversifier index.
1407    pub fn default_address(
1408        &self,
1409        request: UnifiedAddressRequest,
1410    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1411        self.find_address(DiversifierIndex::new(), request)
1412    }
1413
1414    /// Convenience method for choosing a set of receiver requirements based upon the given unified
1415    /// address request and the available items of this key.
1416    ///
1417    /// Returns an error if the provided request cannot be satisfied in address generation using
1418    /// this key.
1419    pub fn receiver_requirements(
1420        &self,
1421        request: UnifiedAddressRequest,
1422    ) -> Result<ReceiverRequirements, AddressGenerationError> {
1423        use ReceiverRequirement::*;
1424        match request {
1425            UnifiedAddressRequest::AllAvailableKeys => self
1426                .to_receiver_requirements()
1427                .map_err(|_| AddressGenerationError::ShieldedReceiverRequired),
1428            UnifiedAddressRequest::Custom(req) => {
1429                if req.orchard() == Require && !self.has_orchard() {
1430                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
1431                        Typecode::Orchard,
1432                    ));
1433                }
1434
1435                if req.sapling() == Require && !self.has_sapling() {
1436                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
1437                        Typecode::Sapling,
1438                    ));
1439                }
1440
1441                if req.p2pkh() == Require && !self.has_transparent() {
1442                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
1443                        Typecode::P2pkh,
1444                    ));
1445                }
1446
1447                Ok(req)
1448            }
1449        }
1450    }
1451
1452    /// Constructs the [`ReceiverRequirements`] that requires a receiver for each data item of this UIVK.
1453    ///
1454    /// Returns [`Err`] if the resulting request would not include a shielded receiver.
1455    #[allow(unused_mut)]
1456    pub fn to_receiver_requirements(&self) -> Result<ReceiverRequirements, ()> {
1457        use ReceiverRequirement::*;
1458
1459        let mut orchard = Omit;
1460        #[cfg(feature = "orchard")]
1461        if self.orchard.is_some() {
1462            orchard = Require;
1463        }
1464
1465        let mut sapling = Omit;
1466        #[cfg(feature = "sapling")]
1467        if self.sapling.is_some() {
1468            sapling = Require;
1469        }
1470
1471        let mut p2pkh = Omit;
1472        #[cfg(feature = "transparent-inputs")]
1473        if self.transparent.is_some() {
1474            p2pkh = Require;
1475        }
1476
1477        ReceiverRequirements::new(orchard, sapling, p2pkh)
1478    }
1479}
1480
1481#[cfg(any(test, feature = "test-dependencies"))]
1482pub mod testing {
1483    use proptest::prelude::*;
1484
1485    use super::UnifiedSpendingKey;
1486    use zcash_protocol::consensus::Network;
1487    use zip32::AccountId;
1488
1489    pub fn arb_unified_spending_key(params: Network) -> impl Strategy<Value = UnifiedSpendingKey> {
1490        prop::array::uniform32(prop::num::u8::ANY).prop_flat_map(move |seed| {
1491            prop::num::u32::ANY
1492                .prop_map(move |account| {
1493                    UnifiedSpendingKey::from_seed(
1494                        &params,
1495                        &seed,
1496                        AccountId::try_from(account & ((1 << 31) - 1)).unwrap(),
1497                    )
1498                })
1499                .prop_filter("seeds must generate valid USKs", |v| v.is_ok())
1500                .prop_map(|v| v.unwrap())
1501        })
1502    }
1503}
1504
1505#[cfg(test)]
1506mod tests {
1507    use proptest::prelude::proptest;
1508
1509    use zcash_protocol::consensus::MAIN_NETWORK;
1510    use zip32::AccountId;
1511
1512    #[cfg(any(feature = "sapling", feature = "orchard"))]
1513    use {
1514        super::{UnifiedFullViewingKey, UnifiedIncomingViewingKey},
1515        zcash_address::unified::{Encoding, Uivk},
1516    };
1517
1518    #[cfg(feature = "orchard")]
1519    use zip32::Scope;
1520
1521    #[cfg(feature = "sapling")]
1522    use super::sapling;
1523
1524    #[cfg(feature = "transparent-inputs")]
1525    use {
1526        crate::{address::Address, encoding::AddressCodec},
1527        alloc::string::ToString,
1528        alloc::vec::Vec,
1529        transparent::keys::{AccountPrivKey, IncomingViewingKey},
1530        zcash_address::test_vectors,
1531        zip32::DiversifierIndex,
1532    };
1533
1534    #[cfg(feature = "unstable")]
1535    use super::{testing::arb_unified_spending_key, Era, UnifiedSpendingKey};
1536
1537    #[cfg(all(feature = "orchard", feature = "unstable"))]
1538    use subtle::ConstantTimeEq;
1539
1540    #[cfg(feature = "transparent-inputs")]
1541    fn seed() -> Vec<u8> {
1542        let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f";
1543        hex::decode(seed_hex).unwrap()
1544    }
1545
1546    #[test]
1547    #[should_panic]
1548    #[cfg(feature = "sapling")]
1549    fn spending_key_panics_on_short_seed() {
1550        let _ = sapling::spending_key(&[0; 31][..], 0, AccountId::ZERO);
1551    }
1552
1553    #[cfg(feature = "transparent-inputs")]
1554    #[test]
1555    fn pk_to_taddr() {
1556        use transparent::keys::NonHardenedChildIndex;
1557
1558        let taddr = AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO)
1559            .unwrap()
1560            .to_account_pubkey()
1561            .derive_external_ivk()
1562            .unwrap()
1563            .derive_address(NonHardenedChildIndex::ZERO)
1564            .unwrap()
1565            .encode(&MAIN_NETWORK);
1566        assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string());
1567    }
1568
1569    #[test]
1570    #[cfg(any(feature = "orchard", feature = "sapling"))]
1571    fn ufvk_round_trip() {
1572        #[cfg(feature = "orchard")]
1573        let orchard = {
1574            let sk =
1575                orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
1576            Some(orchard::keys::FullViewingKey::from(&sk))
1577        };
1578
1579        #[cfg(feature = "sapling")]
1580        let sapling = {
1581            let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
1582            Some(extsk.to_diversifiable_full_viewing_key())
1583        };
1584
1585        #[cfg(feature = "transparent-inputs")]
1586        let transparent = {
1587            let privkey =
1588                AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::ZERO).unwrap();
1589            Some(privkey.to_account_pubkey())
1590        };
1591
1592        let ufvk = UnifiedFullViewingKey::new(
1593            #[cfg(feature = "transparent-inputs")]
1594            transparent,
1595            #[cfg(feature = "sapling")]
1596            sapling,
1597            #[cfg(feature = "orchard")]
1598            orchard,
1599        );
1600
1601        let ufvk = ufvk.expect("Orchard or Sapling fvk is present.");
1602        let encoded = ufvk.encode(&MAIN_NETWORK);
1603
1604        // Test encoded form against known values; these test vectors contain Orchard receivers
1605        // that will be treated as unknown if the `orchard` feature is not enabled.
1606        let encoded_with_t = "uview1tg6rpjgju2s2j37gkgjq79qrh5lvzr6e0ed3n4sf4hu5qd35vmsh7avl80xa6mx7ryqce9hztwaqwrdthetpy4pc0kce25x453hwcmax02p80pg5savlg865sft9reat07c5vlactr6l2pxtlqtqunt2j9gmvr8spcuzf07af80h5qmut38h0gvcfa9k4rwujacwwca9vu8jev7wq6c725huv8qjmhss3hdj2vh8cfxhpqcm2qzc34msyrfxk5u6dqttt4vv2mr0aajreww5yufpk0gn4xkfm888467k7v6fmw7syqq6cceu078yw8xja502jxr0jgum43lhvpzmf7eu5dmnn6cr6f7p43yw8znzgxg598mllewnx076hljlvynhzwn5es94yrv65tdg3utuz2u3sras0wfcq4adxwdvlk387d22g3q98t5z74quw2fa4wed32escx8dwh4mw35t4jwf35xyfxnu83mk5s4kw2glkgsshmxk";
1607        let _encoded_no_t = "uview12z384wdq76ceewlsu0esk7d97qnd23v2qnvhujxtcf2lsq8g4hwzpx44fwxssnm5tg8skyh4tnc8gydwxefnnm0hd0a6c6etmj0pp9jqkdsllkr70u8gpf7ndsfqcjlqn6dec3faumzqlqcmtjf8vp92h7kj38ph2786zx30hq2wru8ae3excdwc8w0z3t9fuw7mt7xy5sn6s4e45kwm0cjp70wytnensgdnev286t3vew3yuwt2hcz865y037k30e428dvgne37xvyeal2vu8yjnznphf9t2rw3gdp0hk5zwq00ws8f3l3j5n3qkqgsyzrwx4qzmgq0xwwk4vz2r6vtsykgz089jncvycmem3535zjwvvtvjw8v98y0d5ydwte575gjm7a7k";
1608
1609        // We test the full roundtrip only with the `sapling` and `orchard` features enabled,
1610        // because we will not generate these parts of the encoding if the UFVK does not have an
1611        // these parts.
1612        #[cfg(all(feature = "sapling", feature = "orchard"))]
1613        {
1614            #[cfg(feature = "transparent-inputs")]
1615            assert_eq!(encoded, encoded_with_t);
1616            #[cfg(not(feature = "transparent-inputs"))]
1617            assert_eq!(encoded, _encoded_no_t);
1618        }
1619
1620        let decoded = UnifiedFullViewingKey::decode(&MAIN_NETWORK, &encoded).unwrap();
1621        let reencoded = decoded.encode(&MAIN_NETWORK);
1622        assert_eq!(encoded, reencoded);
1623
1624        #[cfg(feature = "transparent-inputs")]
1625        assert_eq!(
1626            decoded.transparent.map(|t| t.serialize()),
1627            ufvk.transparent.as_ref().map(|t| t.serialize()),
1628        );
1629        #[cfg(feature = "sapling")]
1630        assert_eq!(
1631            decoded.sapling.map(|s| s.to_bytes()),
1632            ufvk.sapling.map(|s| s.to_bytes()),
1633        );
1634        #[cfg(feature = "orchard")]
1635        assert_eq!(
1636            decoded.orchard.map(|o| o.to_bytes()),
1637            ufvk.orchard.map(|o| o.to_bytes()),
1638        );
1639
1640        let decoded_with_t = UnifiedFullViewingKey::decode(&MAIN_NETWORK, encoded_with_t).unwrap();
1641        #[cfg(feature = "transparent-inputs")]
1642        assert_eq!(
1643            decoded_with_t.transparent.map(|t| t.serialize()),
1644            ufvk.transparent.as_ref().map(|t| t.serialize()),
1645        );
1646
1647        // Both Orchard and Sapling enabled
1648        #[cfg(all(
1649            feature = "orchard",
1650            feature = "sapling",
1651            feature = "transparent-inputs"
1652        ))]
1653        assert_eq!(decoded_with_t.unknown.len(), 0);
1654        #[cfg(all(
1655            feature = "orchard",
1656            feature = "sapling",
1657            not(feature = "transparent-inputs")
1658        ))]
1659        assert_eq!(decoded_with_t.unknown.len(), 1);
1660
1661        // Orchard enabled
1662        #[cfg(all(
1663            feature = "orchard",
1664            not(feature = "sapling"),
1665            feature = "transparent-inputs"
1666        ))]
1667        assert_eq!(decoded_with_t.unknown.len(), 1);
1668        #[cfg(all(
1669            feature = "orchard",
1670            not(feature = "sapling"),
1671            not(feature = "transparent-inputs")
1672        ))]
1673        assert_eq!(decoded_with_t.unknown.len(), 2);
1674
1675        // Sapling enabled
1676        #[cfg(all(
1677            not(feature = "orchard"),
1678            feature = "sapling",
1679            feature = "transparent-inputs"
1680        ))]
1681        assert_eq!(decoded_with_t.unknown.len(), 1);
1682        #[cfg(all(
1683            not(feature = "orchard"),
1684            feature = "sapling",
1685            not(feature = "transparent-inputs")
1686        ))]
1687        assert_eq!(decoded_with_t.unknown.len(), 2);
1688    }
1689
1690    #[test]
1691    #[cfg(feature = "transparent-inputs")]
1692    fn ufvk_derivation() {
1693        use crate::keys::UnifiedAddressRequest;
1694
1695        use super::{ReceiverRequirement::*, UnifiedSpendingKey};
1696
1697        for tv in test_vectors::UNIFIED {
1698            let usk = UnifiedSpendingKey::from_seed(
1699                &MAIN_NETWORK,
1700                &tv.root_seed,
1701                AccountId::try_from(tv.account).unwrap(),
1702            )
1703            .expect("seed produced a valid unified spending key");
1704
1705            let d_idx = DiversifierIndex::from(tv.diversifier_index);
1706            let ufvk = usk.to_unified_full_viewing_key();
1707
1708            // The test vectors contain some diversifier indices that do not generate
1709            // valid Sapling addresses, so skip those.
1710            #[cfg(feature = "sapling")]
1711            if ufvk.sapling().unwrap().address(d_idx).is_none() {
1712                continue;
1713            }
1714
1715            let ua = ufvk
1716                .address(
1717                    d_idx,
1718                    UnifiedAddressRequest::unsafe_custom(Omit, Require, Require),
1719                )
1720                .unwrap_or_else(|err| {
1721                    panic!(
1722                        "unified address generation failed for account {}: {:?}",
1723                        tv.account, err
1724                    )
1725                });
1726
1727            match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
1728                Some(Address::Unified(tvua)) => {
1729                    // We always derive transparent and Sapling receivers, but not
1730                    // every value in the test vectors has these present.
1731                    if tvua.has_transparent() {
1732                        assert_eq!(tvua.transparent(), ua.transparent());
1733                    }
1734                    #[cfg(feature = "sapling")]
1735                    if tvua.has_sapling() {
1736                        assert_eq!(tvua.sapling(), ua.sapling());
1737                    }
1738                }
1739                _other => {
1740                    panic!(
1741                        "{} did not decode to a valid unified address",
1742                        tv.unified_addr
1743                    );
1744                }
1745            }
1746        }
1747    }
1748
1749    #[test]
1750    #[cfg(any(feature = "orchard", feature = "sapling"))]
1751    fn uivk_round_trip() {
1752        use zcash_protocol::consensus::NetworkType;
1753
1754        #[cfg(feature = "orchard")]
1755        let orchard = {
1756            let sk =
1757                orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
1758            Some(orchard::keys::FullViewingKey::from(&sk).to_ivk(Scope::External))
1759        };
1760
1761        #[cfg(feature = "sapling")]
1762        let sapling = {
1763            let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
1764            Some(extsk.to_diversifiable_full_viewing_key().to_external_ivk())
1765        };
1766
1767        #[cfg(feature = "transparent-inputs")]
1768        let transparent = {
1769            let privkey =
1770                AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::ZERO).unwrap();
1771            Some(privkey.to_account_pubkey().derive_external_ivk().unwrap())
1772        };
1773
1774        let uivk = UnifiedIncomingViewingKey::new(
1775            #[cfg(feature = "transparent-inputs")]
1776            transparent,
1777            #[cfg(feature = "sapling")]
1778            sapling,
1779            #[cfg(feature = "orchard")]
1780            orchard,
1781        );
1782
1783        let encoded = uivk.render().encode(&NetworkType::Main);
1784
1785        // Test encoded form against known values; these test vectors contain Orchard receivers
1786        // that will be treated as unknown if the `orchard` feature is not enabled.
1787        let encoded_with_t = "uivk1z28yg638vjwusmf0zc9ad2j0mpv6s42wc5kqt004aaqfu5xxxgu7mdcydn9qf723fnryt34s6jyxyw0jt7spq04c3v9ze6qe9gjjc5aglz8zv5pqtw58czd0actynww5n85z3052kzgy6cu0fyjafyp4sr4kppyrrwhwev2rr0awq6m8d66esvk6fgacggqnswg5g9gkv6t6fj9ajhyd0gmel4yzscprpzduncc0e2lywufup6fvzf6y8cefez2r99pgge5yyfuus0r60khgu895pln5e7nn77q6s9kh2uwf6lrfu06ma2kd7r05jjvl4hn6nupge8fajh0cazd7mkmz23t79w";
1788        let _encoded_no_t = "uivk1020vq9j5zeqxh303sxa0zv2hn9wm9fev8x0p8yqxdwyzde9r4c90fcglc63usj0ycl2scy8zxuhtser0qrq356xfy8x3vyuxu7f6gas75svl9v9m3ctuazsu0ar8e8crtx7x6zgh4kw8xm3q4rlkpm9er2wefxhhf9pn547gpuz9vw27gsdp6c03nwlrxgzhr2g6xek0x8l5avrx9ue9lf032tr7kmhqf3nfdxg7ldfgx6yf09g";
1789
1790        // We test the full roundtrip only with the `sapling` and `orchard` features enabled,
1791        // because we will not generate these parts of the encoding if the UIVK does not have an
1792        // these parts.
1793        #[cfg(all(feature = "sapling", feature = "orchard"))]
1794        {
1795            #[cfg(feature = "transparent-inputs")]
1796            assert_eq!(encoded, encoded_with_t);
1797            #[cfg(not(feature = "transparent-inputs"))]
1798            assert_eq!(encoded, _encoded_no_t);
1799        }
1800
1801        let decoded = UnifiedIncomingViewingKey::parse(&Uivk::decode(&encoded).unwrap().1).unwrap();
1802        let reencoded = decoded.render().encode(&NetworkType::Main);
1803        assert_eq!(encoded, reencoded);
1804
1805        #[cfg(feature = "transparent-inputs")]
1806        assert_eq!(
1807            decoded.transparent.map(|t| t.serialize()),
1808            uivk.transparent.as_ref().map(|t| t.serialize()),
1809        );
1810        #[cfg(feature = "sapling")]
1811        assert_eq!(
1812            decoded.sapling.map(|s| s.to_bytes()),
1813            uivk.sapling.map(|s| s.to_bytes()),
1814        );
1815        #[cfg(feature = "orchard")]
1816        assert_eq!(
1817            decoded.orchard.map(|o| o.to_bytes()),
1818            uivk.orchard.map(|o| o.to_bytes()),
1819        );
1820
1821        let decoded_with_t =
1822            UnifiedIncomingViewingKey::parse(&Uivk::decode(encoded_with_t).unwrap().1).unwrap();
1823        #[cfg(feature = "transparent-inputs")]
1824        assert_eq!(
1825            decoded_with_t.transparent.map(|t| t.serialize()),
1826            uivk.transparent.as_ref().map(|t| t.serialize()),
1827        );
1828
1829        // Both Orchard and Sapling enabled
1830        #[cfg(all(
1831            feature = "orchard",
1832            feature = "sapling",
1833            feature = "transparent-inputs"
1834        ))]
1835        assert_eq!(decoded_with_t.unknown.len(), 0);
1836        #[cfg(all(
1837            feature = "orchard",
1838            feature = "sapling",
1839            not(feature = "transparent-inputs")
1840        ))]
1841        assert_eq!(decoded_with_t.unknown.len(), 1);
1842
1843        // Orchard enabled
1844        #[cfg(all(
1845            feature = "orchard",
1846            not(feature = "sapling"),
1847            feature = "transparent-inputs"
1848        ))]
1849        assert_eq!(decoded_with_t.unknown.len(), 1);
1850        #[cfg(all(
1851            feature = "orchard",
1852            not(feature = "sapling"),
1853            not(feature = "transparent-inputs")
1854        ))]
1855        assert_eq!(decoded_with_t.unknown.len(), 2);
1856
1857        // Sapling enabled
1858        #[cfg(all(
1859            not(feature = "orchard"),
1860            feature = "sapling",
1861            feature = "transparent-inputs"
1862        ))]
1863        assert_eq!(decoded_with_t.unknown.len(), 1);
1864        #[cfg(all(
1865            not(feature = "orchard"),
1866            feature = "sapling",
1867            not(feature = "transparent-inputs")
1868        ))]
1869        assert_eq!(decoded_with_t.unknown.len(), 2);
1870    }
1871
1872    #[test]
1873    #[cfg(feature = "transparent-inputs")]
1874    fn uivk_derivation() {
1875        use crate::keys::UnifiedAddressRequest;
1876
1877        use super::{ReceiverRequirement::*, UnifiedSpendingKey};
1878
1879        for tv in test_vectors::UNIFIED {
1880            let usk = UnifiedSpendingKey::from_seed(
1881                &MAIN_NETWORK,
1882                &tv.root_seed,
1883                AccountId::try_from(tv.account).unwrap(),
1884            )
1885            .expect("seed produced a valid unified spending key");
1886
1887            let d_idx = DiversifierIndex::from(tv.diversifier_index);
1888            let uivk = usk
1889                .to_unified_full_viewing_key()
1890                .to_unified_incoming_viewing_key();
1891
1892            // The test vectors contain some diversifier indices that do not generate
1893            // valid Sapling addresses, so skip those.
1894            #[cfg(feature = "sapling")]
1895            if uivk.sapling().as_ref().unwrap().address_at(d_idx).is_none() {
1896                continue;
1897            }
1898
1899            let ua = uivk
1900                .address(
1901                    d_idx,
1902                    UnifiedAddressRequest::unsafe_custom(Omit, Require, Require),
1903                )
1904                .unwrap_or_else(|err| {
1905                    panic!(
1906                        "unified address generation failed for account {}: {:?}",
1907                        tv.account, err
1908                    )
1909                });
1910
1911            match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
1912                Some(Address::Unified(tvua)) => {
1913                    // We always derive transparent and Sapling receivers, but not
1914                    // every value in the test vectors has these present.
1915                    if tvua.has_transparent() {
1916                        assert_eq!(tvua.transparent(), ua.transparent());
1917                    }
1918                    #[cfg(feature = "sapling")]
1919                    if tvua.has_sapling() {
1920                        assert_eq!(tvua.sapling(), ua.sapling());
1921                    }
1922                }
1923                _other => {
1924                    panic!(
1925                        "{} did not decode to a valid unified address",
1926                        tv.unified_addr
1927                    );
1928                }
1929            }
1930        }
1931    }
1932
1933    proptest! {
1934        #[test]
1935        #[cfg(feature = "unstable")]
1936        fn prop_usk_roundtrip(usk in arb_unified_spending_key(zcash_protocol::consensus::Network::MainNetwork)) {
1937            let encoded = usk.to_bytes(Era::Orchard);
1938
1939            #[allow(clippy::let_and_return)]
1940            let encoded_len = {
1941                let len = 4;
1942
1943                #[cfg(feature = "orchard")]
1944                let len = len + 2 + 32;
1945
1946                let len = len + 2 + 169;
1947
1948                // Transparent part is an `xprv` transparent extended key deserialized
1949                // into bytes from Base58, minus the 4 prefix bytes.
1950                #[cfg(feature = "transparent-inputs")]
1951                let len = len + 2 + 74;
1952
1953                #[allow(clippy::let_and_return)]
1954                len
1955            };
1956            assert_eq!(encoded.len(), encoded_len);
1957
1958            let decoded = UnifiedSpendingKey::from_bytes(Era::Orchard, &encoded);
1959            let decoded = decoded.unwrap_or_else(|e| panic!("Error decoding USK: {:?}", e));
1960
1961            #[cfg(feature = "orchard")]
1962            assert!(bool::from(decoded.orchard().ct_eq(usk.orchard())));
1963
1964            assert_eq!(decoded.sapling(), usk.sapling());
1965
1966            #[cfg(feature = "transparent-inputs")]
1967            assert_eq!(decoded.transparent().to_bytes(), usk.transparent().to_bytes());
1968        }
1969    }
1970}