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    pub fn custom(
606        orchard: ReceiverRequirement,
607        sapling: ReceiverRequirement,
608        p2pkh: ReceiverRequirement,
609    ) -> Result<Self, ()> {
610        ReceiverRequirements::new(orchard, sapling, p2pkh).map(UnifiedAddressRequest::Custom)
611    }
612
613    pub const fn unsafe_custom(
614        orchard: ReceiverRequirement,
615        sapling: ReceiverRequirement,
616        p2pkh: ReceiverRequirement,
617    ) -> Self {
618        UnifiedAddressRequest::Custom(ReceiverRequirements::unsafe_new(orchard, sapling, p2pkh))
619    }
620}
621
622/// Specification for how a unified address should be generated from a unified viewing key.
623#[derive(Clone, Copy, Debug)]
624pub struct ReceiverRequirements {
625    orchard: ReceiverRequirement,
626    sapling: ReceiverRequirement,
627    p2pkh: ReceiverRequirement,
628}
629
630impl ReceiverRequirements {
631    /// Construct a new unified address request from its constituent parts.
632    ///
633    /// Returns `Err(())` if the resulting unified address would not include at least one shielded receiver.
634    pub fn new(
635        orchard: ReceiverRequirement,
636        sapling: ReceiverRequirement,
637        p2pkh: ReceiverRequirement,
638    ) -> Result<Self, ()> {
639        use ReceiverRequirement::*;
640        if orchard == Omit && sapling == Omit {
641            Err(())
642        } else {
643            Ok(Self {
644                orchard,
645                sapling,
646                p2pkh,
647            })
648        }
649    }
650
651    /// Constructs a new unified address request that allows a receiver of each type.
652    pub const ALLOW_ALL: ReceiverRequirements = {
653        use ReceiverRequirement::*;
654        Self::unsafe_new(Allow, Allow, Allow)
655    };
656
657    /// Constructs a new unified address request that includes only the receivers that are allowed
658    /// both in itself and a given other request. Returns [`None`] if requirements are incompatible
659    /// or if no shielded receiver type is allowed.
660    pub fn intersect(&self, other: &ReceiverRequirements) -> Result<ReceiverRequirements, ()> {
661        let orchard = self.orchard.intersect(other.orchard)?;
662        let sapling = self.sapling.intersect(other.sapling)?;
663        let p2pkh = self.p2pkh.intersect(other.p2pkh)?;
664        Self::new(orchard, sapling, p2pkh)
665    }
666
667    /// Construct a new unified address request from its constituent parts.
668    ///
669    /// Panics: at least one of `orchard` or `sapling` must be allowed.
670    pub const fn unsafe_new(
671        orchard: ReceiverRequirement,
672        sapling: ReceiverRequirement,
673        p2pkh: ReceiverRequirement,
674    ) -> Self {
675        use ReceiverRequirement::*;
676        if matches!(orchard, Omit) && matches!(sapling, Omit) {
677            panic!("At least one shielded receiver must be allowed.")
678        }
679
680        Self {
681            orchard,
682            sapling,
683            p2pkh,
684        }
685    }
686
687    /// Returns the [`ReceiverRequirement`] for inclusion of an Orchard receiver.
688    pub fn orchard(&self) -> ReceiverRequirement {
689        self.orchard
690    }
691
692    /// Returns the [`ReceiverRequirement`] for inclusion of a Sapling receiver.
693    pub fn sapling(&self) -> ReceiverRequirement {
694        self.sapling
695    }
696
697    /// Returns the [`ReceiverRequirement`] for inclusion of a P2PKH receiver.
698    pub fn p2pkh(&self) -> ReceiverRequirement {
699        self.p2pkh
700    }
701}
702
703#[cfg(feature = "transparent-inputs")]
704impl From<bip32::Error> for DerivationError {
705    fn from(e: bip32::Error) -> Self {
706        DerivationError::Transparent(e)
707    }
708}
709
710/// A [ZIP 316](https://zips.z.cash/zip-0316) unified full viewing key.
711#[derive(Clone, Debug)]
712pub struct UnifiedFullViewingKey {
713    #[cfg(feature = "transparent-inputs")]
714    transparent: Option<transparent::keys::AccountPubKey>,
715    #[cfg(feature = "sapling")]
716    sapling: Option<sapling::DiversifiableFullViewingKey>,
717    #[cfg(feature = "orchard")]
718    orchard: Option<orchard::keys::FullViewingKey>,
719    unknown: Vec<(u32, Vec<u8>)>,
720}
721
722impl UnifiedFullViewingKey {
723    /// Construct a new unified full viewing key.
724    ///
725    /// This method is only available when the `test-dependencies` feature is enabled,
726    /// as derivation from the USK or deserialization from the serialized form should
727    /// be used instead.
728    #[cfg(any(test, feature = "test-dependencies"))]
729    pub fn new(
730        #[cfg(feature = "transparent-inputs")] transparent: Option<
731            transparent::keys::AccountPubKey,
732        >,
733        #[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
734        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
735        // TODO: Implement construction of UFVKs with metadata items.
736    ) -> Result<UnifiedFullViewingKey, DerivationError> {
737        Self::from_checked_parts(
738            #[cfg(feature = "transparent-inputs")]
739            transparent,
740            #[cfg(feature = "sapling")]
741            sapling,
742            #[cfg(feature = "orchard")]
743            orchard,
744            // We don't currently allow constructing new UFVKs with unknown items, but we store
745            // this to allow parsing such UFVKs.
746            vec![],
747        )
748    }
749
750    #[cfg(feature = "unstable-frost")]
751    pub fn from_orchard_fvk(
752        orchard: orchard::keys::FullViewingKey,
753    ) -> Result<UnifiedFullViewingKey, DerivationError> {
754        Self::from_checked_parts(
755            #[cfg(feature = "transparent-inputs")]
756            None,
757            #[cfg(feature = "sapling")]
758            None,
759            #[cfg(feature = "orchard")]
760            Some(orchard),
761            // We don't currently allow constructing new UFVKs with unknown items, but we store
762            // this to allow parsing such UFVKs.
763            vec![],
764        )
765    }
766
767    #[cfg(all(feature = "sapling", feature = "unstable"))]
768    pub fn from_sapling_extended_full_viewing_key(
769        sapling: ExtendedFullViewingKey,
770    ) -> Result<UnifiedFullViewingKey, DerivationError> {
771        Self::from_checked_parts(
772            #[cfg(feature = "transparent-inputs")]
773            None,
774            #[cfg(feature = "sapling")]
775            Some(sapling.to_diversifiable_full_viewing_key()),
776            #[cfg(feature = "orchard")]
777            None,
778            // We don't currently allow constructing new UFVKs with unknown items, but we store
779            // this to allow parsing such UFVKs.
780            vec![],
781        )
782    }
783
784    /// Construct a UFVK from its constituent parts, after verifying that UIVK derivation can
785    /// succeed.
786    fn from_checked_parts(
787        #[cfg(feature = "transparent-inputs")] transparent: Option<
788            transparent::keys::AccountPubKey,
789        >,
790        #[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
791        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
792        unknown: Vec<(u32, Vec<u8>)>,
793    ) -> Result<UnifiedFullViewingKey, DerivationError> {
794        // Verify that IVK derivation succeeds; we don't want to construct a UFVK
795        // that can't derive transparent addresses.
796        #[cfg(feature = "transparent-inputs")]
797        let _ = transparent
798            .as_ref()
799            .map(|t| t.derive_external_ivk())
800            .transpose()?;
801
802        Ok(UnifiedFullViewingKey {
803            #[cfg(feature = "transparent-inputs")]
804            transparent,
805            #[cfg(feature = "sapling")]
806            sapling,
807            #[cfg(feature = "orchard")]
808            orchard,
809            unknown,
810        })
811    }
812
813    /// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
814    ///
815    /// [ZIP 316]: https://zips.z.cash/zip-0316
816    pub fn decode<P: consensus::Parameters>(params: &P, encoding: &str) -> Result<Self, String> {
817        let (net, ufvk) = unified::Ufvk::decode(encoding).map_err(|e| e.to_string())?;
818        let expected_net = params.network_type();
819        if net != expected_net {
820            return Err(format!(
821                "UFVK is for network {:?} but we expected {:?}",
822                net, expected_net,
823            ));
824        }
825
826        Self::parse(&ufvk).map_err(|e| e.to_string())
827    }
828
829    /// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
830    ///
831    /// [ZIP 316]: https://zips.z.cash/zip-0316
832    pub fn parse(ufvk: &Ufvk) -> Result<Self, DecodingError> {
833        #[cfg(feature = "orchard")]
834        let mut orchard = None;
835        #[cfg(feature = "sapling")]
836        let mut sapling = None;
837        #[cfg(feature = "transparent-inputs")]
838        let mut transparent = None;
839
840        // We can use as-parsed order here for efficiency, because we're breaking out the
841        // receivers we support from the unknown receivers.
842        let unknown = ufvk
843            .items_as_parsed()
844            .iter()
845            .filter_map(|receiver| match receiver {
846                #[cfg(feature = "orchard")]
847                unified::Fvk::Orchard(data) => orchard::keys::FullViewingKey::from_bytes(data)
848                    .ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))
849                    .map(|addr| {
850                        orchard = Some(addr);
851                        None
852                    })
853                    .transpose(),
854                #[cfg(not(feature = "orchard"))]
855                unified::Fvk::Orchard(data) => Some(Ok::<_, DecodingError>((
856                    u32::from(unified::Typecode::Orchard),
857                    data.to_vec(),
858                ))),
859                #[cfg(feature = "sapling")]
860                unified::Fvk::Sapling(data) => {
861                    sapling::DiversifiableFullViewingKey::from_bytes(data)
862                        .ok_or(DecodingError::KeyDataInvalid(Typecode::Sapling))
863                        .map(|pa| {
864                            sapling = Some(pa);
865                            None
866                        })
867                        .transpose()
868                }
869                #[cfg(not(feature = "sapling"))]
870                unified::Fvk::Sapling(data) => Some(Ok::<_, DecodingError>((
871                    u32::from(unified::Typecode::Sapling),
872                    data.to_vec(),
873                ))),
874                #[cfg(feature = "transparent-inputs")]
875                unified::Fvk::P2pkh(data) => transparent::keys::AccountPubKey::deserialize(data)
876                    .map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))
877                    .map(|tfvk| {
878                        transparent = Some(tfvk);
879                        None
880                    })
881                    .transpose(),
882                #[cfg(not(feature = "transparent-inputs"))]
883                unified::Fvk::P2pkh(data) => Some(Ok::<_, DecodingError>((
884                    u32::from(unified::Typecode::P2pkh),
885                    data.to_vec(),
886                ))),
887                unified::Fvk::Unknown { typecode, data } => Some(Ok((*typecode, data.clone()))),
888            })
889            .collect::<Result<_, _>>()?;
890
891        Self::from_checked_parts(
892            #[cfg(feature = "transparent-inputs")]
893            transparent,
894            #[cfg(feature = "sapling")]
895            sapling,
896            #[cfg(feature = "orchard")]
897            orchard,
898            unknown,
899        )
900        .map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))
901    }
902
903    /// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
904    pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
905        self.to_ufvk().encode(&params.network_type())
906    }
907
908    /// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
909    fn to_ufvk(&self) -> Ufvk {
910        let items = core::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
911            unified::Fvk::Unknown {
912                typecode: *typecode,
913                data: data.clone(),
914            }
915        }));
916        #[cfg(feature = "orchard")]
917        let items = items.chain(
918            self.orchard
919                .as_ref()
920                .map(|fvk| fvk.to_bytes())
921                .map(unified::Fvk::Orchard),
922        );
923        #[cfg(feature = "sapling")]
924        let items = items.chain(
925            self.sapling
926                .as_ref()
927                .map(|dfvk| dfvk.to_bytes())
928                .map(unified::Fvk::Sapling),
929        );
930        #[cfg(feature = "transparent-inputs")]
931        let items = items.chain(
932            self.transparent
933                .as_ref()
934                .map(|tfvk| tfvk.serialize().try_into().unwrap())
935                .map(unified::Fvk::P2pkh),
936        );
937
938        unified::Ufvk::try_from_items(items.collect())
939            .expect("UnifiedFullViewingKey should only be constructed safely")
940    }
941
942    /// Derives a Unified Incoming Viewing Key from this Unified Full Viewing Key.
943    pub fn to_unified_incoming_viewing_key(&self) -> UnifiedIncomingViewingKey {
944        UnifiedIncomingViewingKey {
945            #[cfg(feature = "transparent-inputs")]
946            transparent: self.transparent.as_ref().map(|t| {
947                t.derive_external_ivk()
948                    .expect("Transparent IVK derivation was checked at construction.")
949            }),
950            #[cfg(feature = "sapling")]
951            sapling: self.sapling.as_ref().map(|s| s.to_external_ivk()),
952            #[cfg(feature = "orchard")]
953            orchard: self.orchard.as_ref().map(|o| o.to_ivk(Scope::External)),
954            unknown: Vec::new(),
955        }
956    }
957
958    /// Returns the transparent component of the unified key at the
959    /// BIP44 path `m/44'/<coin_type>'/<account>'`.
960    #[cfg(feature = "transparent-inputs")]
961    pub fn transparent(&self) -> Option<&transparent::keys::AccountPubKey> {
962        self.transparent.as_ref()
963    }
964
965    /// Returns the Sapling diversifiable full viewing key component of this unified key.
966    #[cfg(feature = "sapling")]
967    pub fn sapling(&self) -> Option<&sapling::DiversifiableFullViewingKey> {
968        self.sapling.as_ref()
969    }
970
971    /// Returns the Orchard full viewing key component of this unified key.
972    #[cfg(feature = "orchard")]
973    pub fn orchard(&self) -> Option<&orchard::keys::FullViewingKey> {
974        self.orchard.as_ref()
975    }
976
977    /// Attempts to derive the Unified Address for the given diversifier index and receiver types.
978    /// If `request` is None, the address should be derived to contain a receiver for each item in
979    /// this UFVK.
980    ///
981    /// Returns `None` if the specified index does not produce a valid diversifier.
982    pub fn address(
983        &self,
984        j: DiversifierIndex,
985        request: UnifiedAddressRequest,
986    ) -> Result<UnifiedAddress, AddressGenerationError> {
987        self.to_unified_incoming_viewing_key().address(j, request)
988    }
989
990    /// Searches the diversifier space starting at diversifier index `j` for one which will produce
991    /// a valid diversifier, and return the Unified Address constructed using that diversifier
992    /// along with the index at which the valid diversifier was found. If `request` is None, the
993    /// address should be derived to contain a receiver for each item in this UFVK.
994    ///
995    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
996    /// required to satisfy the unified address request are not properly enabled.
997    pub fn find_address(
998        &self,
999        j: DiversifierIndex,
1000        request: UnifiedAddressRequest,
1001    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1002        self.to_unified_incoming_viewing_key()
1003            .find_address(j, request)
1004    }
1005
1006    /// Find the Unified Address corresponding to the smallest valid diversifier index, along with
1007    /// that index. If `request` is None, the address should be derived to contain a receiver for
1008    /// each item in this UFVK.
1009    ///
1010    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
1011    /// required to satisfy the unified address request are not properly enabled.
1012    pub fn default_address(
1013        &self,
1014        request: UnifiedAddressRequest,
1015    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1016        self.find_address(DiversifierIndex::new(), request)
1017    }
1018}
1019
1020/// A [ZIP 316](https://zips.z.cash/zip-0316) unified incoming viewing key.
1021#[derive(Clone, Debug)]
1022pub struct UnifiedIncomingViewingKey {
1023    #[cfg(feature = "transparent-inputs")]
1024    transparent: Option<transparent::keys::ExternalIvk>,
1025    #[cfg(feature = "sapling")]
1026    sapling: Option<::sapling::zip32::IncomingViewingKey>,
1027    #[cfg(feature = "orchard")]
1028    orchard: Option<orchard::keys::IncomingViewingKey>,
1029    /// Stores the unrecognized elements of the unified encoding.
1030    unknown: Vec<(u32, Vec<u8>)>,
1031}
1032
1033impl UnifiedIncomingViewingKey {
1034    /// Construct a new unified incoming viewing key.
1035    ///
1036    /// This method is only available when the `test-dependencies` feature is enabled,
1037    /// as derivation from the UFVK or deserialization from the serialized form should
1038    /// be used instead.
1039    #[cfg(any(test, feature = "test-dependencies"))]
1040    pub fn new(
1041        #[cfg(feature = "transparent-inputs")] transparent: Option<transparent::keys::ExternalIvk>,
1042        #[cfg(feature = "sapling")] sapling: Option<::sapling::zip32::IncomingViewingKey>,
1043        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::IncomingViewingKey>,
1044        // TODO: Implement construction of UIVKs with metadata items.
1045    ) -> UnifiedIncomingViewingKey {
1046        UnifiedIncomingViewingKey {
1047            #[cfg(feature = "transparent-inputs")]
1048            transparent,
1049            #[cfg(feature = "sapling")]
1050            sapling,
1051            #[cfg(feature = "orchard")]
1052            orchard,
1053            // We don't allow constructing new UFVKs with unknown items, but we store
1054            // this to allow parsing such UFVKs.
1055            unknown: vec![],
1056        }
1057    }
1058
1059    /// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding.
1060    ///
1061    /// [ZIP 316]: https://zips.z.cash/zip-0316
1062    pub fn decode<P: consensus::Parameters>(params: &P, encoding: &str) -> Result<Self, String> {
1063        let (net, ufvk) = unified::Uivk::decode(encoding).map_err(|e| e.to_string())?;
1064        let expected_net = params.network_type();
1065        if net != expected_net {
1066            return Err(format!(
1067                "UIVK is for network {:?} but we expected {:?}",
1068                net, expected_net,
1069            ));
1070        }
1071
1072        Self::parse(&ufvk).map_err(|e| e.to_string())
1073    }
1074
1075    /// Constructs a unified incoming viewing key from a parsed unified encoding.
1076    fn parse(uivk: &Uivk) -> Result<Self, DecodingError> {
1077        #[cfg(feature = "orchard")]
1078        let mut orchard = None;
1079        #[cfg(feature = "sapling")]
1080        let mut sapling = None;
1081        #[cfg(feature = "transparent-inputs")]
1082        let mut transparent = None;
1083
1084        let mut unknown = vec![];
1085
1086        // We can use as-parsed order here for efficiency, because we're breaking out the
1087        // receivers we support from the unknown receivers.
1088        for receiver in uivk.items_as_parsed() {
1089            match receiver {
1090                unified::Ivk::Orchard(data) => {
1091                    #[cfg(feature = "orchard")]
1092                    {
1093                        orchard = Some(
1094                            Option::from(orchard::keys::IncomingViewingKey::from_bytes(data))
1095                                .ok_or(DecodingError::KeyDataInvalid(Typecode::Orchard))?,
1096                        );
1097                    }
1098
1099                    #[cfg(not(feature = "orchard"))]
1100                    unknown.push((u32::from(unified::Typecode::Orchard), data.to_vec()));
1101                }
1102                unified::Ivk::Sapling(data) => {
1103                    #[cfg(feature = "sapling")]
1104                    {
1105                        sapling = Some(
1106                            Option::from(::sapling::zip32::IncomingViewingKey::from_bytes(data))
1107                                .ok_or(DecodingError::KeyDataInvalid(Typecode::Sapling))?,
1108                        );
1109                    }
1110
1111                    #[cfg(not(feature = "sapling"))]
1112                    unknown.push((u32::from(unified::Typecode::Sapling), data.to_vec()));
1113                }
1114                unified::Ivk::P2pkh(data) => {
1115                    #[cfg(feature = "transparent-inputs")]
1116                    {
1117                        transparent = Some(
1118                            transparent::keys::ExternalIvk::deserialize(data)
1119                                .map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2pkh))?,
1120                        );
1121                    }
1122
1123                    #[cfg(not(feature = "transparent-inputs"))]
1124                    unknown.push((u32::from(unified::Typecode::P2pkh), data.to_vec()));
1125                }
1126                unified::Ivk::Unknown { typecode, data } => {
1127                    unknown.push((*typecode, data.clone()));
1128                }
1129            }
1130        }
1131
1132        Ok(Self {
1133            #[cfg(feature = "transparent-inputs")]
1134            transparent,
1135            #[cfg(feature = "sapling")]
1136            sapling,
1137            #[cfg(feature = "orchard")]
1138            orchard,
1139            unknown,
1140        })
1141    }
1142
1143    /// Returns the string encoding of this `UnifiedFullViewingKey` for the given network.
1144    pub fn encode<P: consensus::Parameters>(&self, params: &P) -> String {
1145        self.render().encode(&params.network_type())
1146    }
1147
1148    /// Converts this unified incoming viewing key to a unified encoding.
1149    fn render(&self) -> Uivk {
1150        let items = core::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
1151            unified::Ivk::Unknown {
1152                typecode: *typecode,
1153                data: data.clone(),
1154            }
1155        }));
1156        #[cfg(feature = "orchard")]
1157        let items = items.chain(
1158            self.orchard
1159                .as_ref()
1160                .map(|ivk| ivk.to_bytes())
1161                .map(unified::Ivk::Orchard),
1162        );
1163        #[cfg(feature = "sapling")]
1164        let items = items.chain(
1165            self.sapling
1166                .as_ref()
1167                .map(|divk| divk.to_bytes())
1168                .map(unified::Ivk::Sapling),
1169        );
1170        #[cfg(feature = "transparent-inputs")]
1171        let items = items.chain(
1172            self.transparent
1173                .as_ref()
1174                .map(|tivk| tivk.serialize().try_into().unwrap())
1175                .map(unified::Ivk::P2pkh),
1176        );
1177
1178        unified::Uivk::try_from_items(items.collect())
1179            .expect("UnifiedIncomingViewingKey should only be constructed safely.")
1180    }
1181
1182    /// Returns whether this uivk has a transparent key item.
1183    ///
1184    /// This method is available irrespective of whether the `transparent-inputs` feature flag is enabled.
1185    pub fn has_transparent(&self) -> bool {
1186        #[cfg(not(feature = "transparent-inputs"))]
1187        return false;
1188        #[cfg(feature = "transparent-inputs")]
1189        return self.transparent.is_some();
1190    }
1191
1192    /// Returns the Transparent external IVK, if present.
1193    #[cfg(feature = "transparent-inputs")]
1194    pub fn transparent(&self) -> &Option<transparent::keys::ExternalIvk> {
1195        &self.transparent
1196    }
1197
1198    /// Returns whether this uivk has a Sapling key item.
1199    ///
1200    /// This method is available irrespective of whether the `sapling` feature flag is enabled.
1201    pub fn has_sapling(&self) -> bool {
1202        #[cfg(not(feature = "sapling"))]
1203        return false;
1204        #[cfg(feature = "sapling")]
1205        return self.sapling.is_some();
1206    }
1207
1208    /// Returns the Sapling IVK, if present.
1209    #[cfg(feature = "sapling")]
1210    pub fn sapling(&self) -> &Option<::sapling::zip32::IncomingViewingKey> {
1211        &self.sapling
1212    }
1213
1214    /// Returns whether this uivk has an Orchard key item.
1215    ///
1216    /// This method is available irrespective of whether the `orchard` feature flag is enabled.
1217    pub fn has_orchard(&self) -> bool {
1218        #[cfg(not(feature = "orchard"))]
1219        return false;
1220        #[cfg(feature = "orchard")]
1221        return self.orchard.is_some();
1222    }
1223
1224    /// Returns the Orchard IVK, if present.
1225    #[cfg(feature = "orchard")]
1226    pub fn orchard(&self) -> &Option<orchard::keys::IncomingViewingKey> {
1227        &self.orchard
1228    }
1229
1230    /// Attempts to derive the Unified Address for the given diversifier index and receiver types.
1231    /// If `request` is None, the address will be derived to contain a receiver for each item in
1232    /// this UFVK.
1233    ///
1234    /// Returns an error if the this key does not produce a valid receiver for a required receiver
1235    /// type at the given diversifier index.
1236    pub fn address(
1237        &self,
1238        _j: DiversifierIndex,
1239        request: UnifiedAddressRequest,
1240    ) -> Result<UnifiedAddress, AddressGenerationError> {
1241        use ReceiverRequirement::*;
1242
1243        let request = self
1244            .receiver_requirements(request)
1245            .map_err(|_| AddressGenerationError::ShieldedReceiverRequired)?;
1246
1247        // If we need to generate a transparent receiver, check that the user has not
1248        // specified an invalid transparent child index, from which we can never search to
1249        // find a valid index.
1250        #[cfg(feature = "transparent-inputs")]
1251        if request.p2pkh == ReceiverRequirement::Require
1252            && self.transparent.is_some()
1253            && to_transparent_child_index(_j).is_none()
1254        {
1255            return Err(AddressGenerationError::InvalidTransparentChildIndex(_j));
1256        }
1257
1258        #[cfg(feature = "orchard")]
1259        let mut orchard = None;
1260        if request.orchard != Omit {
1261            #[cfg(not(feature = "orchard"))]
1262            if request.orchard == Require {
1263                return Err(AddressGenerationError::ReceiverTypeNotSupported(
1264                    Typecode::Orchard,
1265                ));
1266            }
1267
1268            #[cfg(feature = "orchard")]
1269            if let Some(oivk) = &self.orchard {
1270                let orchard_j = orchard::keys::DiversifierIndex::from(*_j.as_bytes());
1271                orchard = Some(oivk.address_at(orchard_j))
1272            } else if request.orchard == Require {
1273                return Err(AddressGenerationError::KeyNotAvailable(Typecode::Orchard));
1274            }
1275        }
1276
1277        #[cfg(feature = "sapling")]
1278        let mut sapling = None;
1279        if request.sapling != Omit {
1280            #[cfg(not(feature = "sapling"))]
1281            if request.sapling == Require {
1282                return Err(AddressGenerationError::ReceiverTypeNotSupported(
1283                    Typecode::Sapling,
1284                ));
1285            }
1286
1287            #[cfg(feature = "sapling")]
1288            if let Some(divk) = &self.sapling {
1289                // If a Sapling receiver type is requested, we must be able to construct an
1290                // address; if we're unable to do so, then no Unified Address exists at this
1291                // diversifier and we use `?` to early-return from this method.
1292                sapling = match (request.sapling, divk.address_at(_j)) {
1293                    (Require | Allow, Some(addr)) => Ok(Some(addr)),
1294                    (Require, None) => {
1295                        Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_j))
1296                    }
1297                    _ => Ok(None),
1298                }?;
1299            } else if request.sapling == Require {
1300                return Err(AddressGenerationError::KeyNotAvailable(Typecode::Sapling));
1301            }
1302        }
1303
1304        #[cfg(feature = "transparent-inputs")]
1305        let mut transparent = None;
1306        if request.p2pkh != Omit {
1307            #[cfg(not(feature = "transparent-inputs"))]
1308            if request.p2pkh == Require {
1309                return Err(AddressGenerationError::ReceiverTypeNotSupported(
1310                    Typecode::P2pkh,
1311                ));
1312            }
1313
1314            #[cfg(feature = "transparent-inputs")]
1315            if let Some(tivk) = self.transparent.as_ref() {
1316                // If a transparent receiver type is requested, we must be able to construct an
1317                // address; if we're unable to do so, then no Unified Address exists at this
1318                // diversifier.
1319                let j = to_transparent_child_index(_j);
1320
1321                transparent = match (request.p2pkh, j.and_then(|j| tivk.derive_address(j).ok())) {
1322                    (Require | Allow, Some(addr)) => Ok(Some(addr)),
1323                    (Require, None) => {
1324                        Err(AddressGenerationError::InvalidTransparentChildIndex(_j))
1325                    }
1326                    _ => Ok(None),
1327                }?;
1328            } else if request.p2pkh == Require {
1329                return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2pkh));
1330            }
1331        }
1332        #[cfg(not(feature = "transparent-inputs"))]
1333        let transparent = None;
1334
1335        UnifiedAddress::from_receivers(
1336            #[cfg(feature = "orchard")]
1337            orchard,
1338            #[cfg(feature = "sapling")]
1339            sapling,
1340            transparent,
1341        )
1342        .ok_or(AddressGenerationError::ShieldedReceiverRequired)
1343    }
1344
1345    /// Searches the diversifier space starting at diversifier index `j` for one which will produce
1346    /// a valid address that conforms to the provided request, and returns that Unified Address
1347    /// along with the index at which the valid diversifier was found.
1348    ///
1349    /// If [`None`] is specified for the `request` parameter, a default request that [`Require`]s a
1350    /// receiver be present for each key item enabled by the feature flags in use will be used to
1351    /// search the diversifier space.
1352    ///
1353    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
1354    /// required to satisfy the unified address request are not enabled.
1355    ///
1356    /// [`Require`]: ReceiverRequirement::Require
1357    #[allow(unused_mut)]
1358    pub fn find_address(
1359        &self,
1360        mut j: DiversifierIndex,
1361        request: UnifiedAddressRequest,
1362    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1363        // Find a working diversifier and construct the associated address.
1364        loop {
1365            let res = self.address(j, request);
1366            match res {
1367                Ok(ua) => {
1368                    return Ok((ua, j));
1369                }
1370                #[cfg(feature = "sapling")]
1371                Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => {
1372                    if j.increment().is_err() {
1373                        return Err(AddressGenerationError::DiversifierSpaceExhausted);
1374                    }
1375                }
1376                Err(other) => {
1377                    return Err(other);
1378                }
1379            }
1380        }
1381    }
1382
1383    /// Find the Unified Address corresponding to the smallest valid diversifier index, along with
1384    /// that index. If `request` is None, the address will be derived to contain a receiver for
1385    /// each data item in this UFVK.
1386    ///
1387    /// Returns an error if the this key does not produce a valid receiver for a required receiver
1388    /// type at any diversifier index.
1389    pub fn default_address(
1390        &self,
1391        request: UnifiedAddressRequest,
1392    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1393        self.find_address(DiversifierIndex::new(), request)
1394    }
1395
1396    /// Convenience method for choosing a set of receiver requirements based upon the given unified
1397    /// address request and the available items of this key.
1398    ///
1399    /// Returns an error if the provided request cannot be satisfied in address generation using
1400    /// this key.
1401    pub fn receiver_requirements(
1402        &self,
1403        request: UnifiedAddressRequest,
1404    ) -> Result<ReceiverRequirements, AddressGenerationError> {
1405        use ReceiverRequirement::*;
1406        match request {
1407            UnifiedAddressRequest::AllAvailableKeys => self
1408                .to_receiver_requirements()
1409                .map_err(|_| AddressGenerationError::ShieldedReceiverRequired),
1410            UnifiedAddressRequest::Custom(req) => {
1411                if req.orchard() == Require && !self.has_orchard() {
1412                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
1413                        Typecode::Orchard,
1414                    ));
1415                }
1416
1417                if req.sapling() == Require && !self.has_sapling() {
1418                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
1419                        Typecode::Sapling,
1420                    ));
1421                }
1422
1423                if req.p2pkh() == Require && !self.has_transparent() {
1424                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
1425                        Typecode::P2pkh,
1426                    ));
1427                }
1428
1429                Ok(req)
1430            }
1431        }
1432    }
1433
1434    /// Constructs the [`ReceiverRequirements`] that requires a receiver for each data item of this UIVK.
1435    ///
1436    /// Returns [`Err`] if the resulting request would not include a shielded receiver.
1437    #[allow(unused_mut)]
1438    pub fn to_receiver_requirements(&self) -> Result<ReceiverRequirements, ()> {
1439        use ReceiverRequirement::*;
1440
1441        let mut orchard = Omit;
1442        #[cfg(feature = "orchard")]
1443        if self.orchard.is_some() {
1444            orchard = Require;
1445        }
1446
1447        let mut sapling = Omit;
1448        #[cfg(feature = "sapling")]
1449        if self.sapling.is_some() {
1450            sapling = Require;
1451        }
1452
1453        let mut p2pkh = Omit;
1454        #[cfg(feature = "transparent-inputs")]
1455        if self.transparent.is_some() {
1456            p2pkh = Require;
1457        }
1458
1459        ReceiverRequirements::new(orchard, sapling, p2pkh)
1460    }
1461}
1462
1463#[cfg(any(test, feature = "test-dependencies"))]
1464pub mod testing {
1465    use proptest::prelude::*;
1466
1467    use super::UnifiedSpendingKey;
1468    use zcash_protocol::consensus::Network;
1469    use zip32::AccountId;
1470
1471    pub fn arb_unified_spending_key(params: Network) -> impl Strategy<Value = UnifiedSpendingKey> {
1472        prop::array::uniform32(prop::num::u8::ANY).prop_flat_map(move |seed| {
1473            prop::num::u32::ANY
1474                .prop_map(move |account| {
1475                    UnifiedSpendingKey::from_seed(
1476                        &params,
1477                        &seed,
1478                        AccountId::try_from(account & ((1 << 31) - 1)).unwrap(),
1479                    )
1480                })
1481                .prop_filter("seeds must generate valid USKs", |v| v.is_ok())
1482                .prop_map(|v| v.unwrap())
1483        })
1484    }
1485}
1486
1487#[cfg(test)]
1488mod tests {
1489    use proptest::prelude::proptest;
1490
1491    use zcash_protocol::consensus::MAIN_NETWORK;
1492    use zip32::AccountId;
1493
1494    #[cfg(any(feature = "sapling", feature = "orchard"))]
1495    use {
1496        super::{UnifiedFullViewingKey, UnifiedIncomingViewingKey},
1497        zcash_address::unified::{Encoding, Uivk},
1498    };
1499
1500    #[cfg(feature = "orchard")]
1501    use zip32::Scope;
1502
1503    #[cfg(feature = "sapling")]
1504    use super::sapling;
1505
1506    #[cfg(feature = "transparent-inputs")]
1507    use {
1508        crate::{address::Address, encoding::AddressCodec},
1509        alloc::string::ToString,
1510        alloc::vec::Vec,
1511        transparent::keys::{AccountPrivKey, IncomingViewingKey},
1512        zcash_address::test_vectors,
1513        zip32::DiversifierIndex,
1514    };
1515
1516    #[cfg(feature = "unstable")]
1517    use super::{testing::arb_unified_spending_key, Era, UnifiedSpendingKey};
1518
1519    #[cfg(all(feature = "orchard", feature = "unstable"))]
1520    use subtle::ConstantTimeEq;
1521
1522    #[cfg(feature = "transparent-inputs")]
1523    fn seed() -> Vec<u8> {
1524        let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f";
1525        hex::decode(seed_hex).unwrap()
1526    }
1527
1528    #[test]
1529    #[should_panic]
1530    #[cfg(feature = "sapling")]
1531    fn spending_key_panics_on_short_seed() {
1532        let _ = sapling::spending_key(&[0; 31][..], 0, AccountId::ZERO);
1533    }
1534
1535    #[cfg(feature = "transparent-inputs")]
1536    #[test]
1537    fn pk_to_taddr() {
1538        use transparent::keys::NonHardenedChildIndex;
1539
1540        let taddr = AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO)
1541            .unwrap()
1542            .to_account_pubkey()
1543            .derive_external_ivk()
1544            .unwrap()
1545            .derive_address(NonHardenedChildIndex::ZERO)
1546            .unwrap()
1547            .encode(&MAIN_NETWORK);
1548        assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string());
1549    }
1550
1551    #[test]
1552    #[cfg(any(feature = "orchard", feature = "sapling"))]
1553    fn ufvk_round_trip() {
1554        #[cfg(feature = "orchard")]
1555        let orchard = {
1556            let sk =
1557                orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
1558            Some(orchard::keys::FullViewingKey::from(&sk))
1559        };
1560
1561        #[cfg(feature = "sapling")]
1562        let sapling = {
1563            let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
1564            Some(extsk.to_diversifiable_full_viewing_key())
1565        };
1566
1567        #[cfg(feature = "transparent-inputs")]
1568        let transparent = {
1569            let privkey =
1570                AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::ZERO).unwrap();
1571            Some(privkey.to_account_pubkey())
1572        };
1573
1574        let ufvk = UnifiedFullViewingKey::new(
1575            #[cfg(feature = "transparent-inputs")]
1576            transparent,
1577            #[cfg(feature = "sapling")]
1578            sapling,
1579            #[cfg(feature = "orchard")]
1580            orchard,
1581        );
1582
1583        let ufvk = ufvk.expect("Orchard or Sapling fvk is present.");
1584        let encoded = ufvk.encode(&MAIN_NETWORK);
1585
1586        // Test encoded form against known values; these test vectors contain Orchard receivers
1587        // that will be treated as unknown if the `orchard` feature is not enabled.
1588        let encoded_with_t = "uview1tg6rpjgju2s2j37gkgjq79qrh5lvzr6e0ed3n4sf4hu5qd35vmsh7avl80xa6mx7ryqce9hztwaqwrdthetpy4pc0kce25x453hwcmax02p80pg5savlg865sft9reat07c5vlactr6l2pxtlqtqunt2j9gmvr8spcuzf07af80h5qmut38h0gvcfa9k4rwujacwwca9vu8jev7wq6c725huv8qjmhss3hdj2vh8cfxhpqcm2qzc34msyrfxk5u6dqttt4vv2mr0aajreww5yufpk0gn4xkfm888467k7v6fmw7syqq6cceu078yw8xja502jxr0jgum43lhvpzmf7eu5dmnn6cr6f7p43yw8znzgxg598mllewnx076hljlvynhzwn5es94yrv65tdg3utuz2u3sras0wfcq4adxwdvlk387d22g3q98t5z74quw2fa4wed32escx8dwh4mw35t4jwf35xyfxnu83mk5s4kw2glkgsshmxk";
1589        let _encoded_no_t = "uview12z384wdq76ceewlsu0esk7d97qnd23v2qnvhujxtcf2lsq8g4hwzpx44fwxssnm5tg8skyh4tnc8gydwxefnnm0hd0a6c6etmj0pp9jqkdsllkr70u8gpf7ndsfqcjlqn6dec3faumzqlqcmtjf8vp92h7kj38ph2786zx30hq2wru8ae3excdwc8w0z3t9fuw7mt7xy5sn6s4e45kwm0cjp70wytnensgdnev286t3vew3yuwt2hcz865y037k30e428dvgne37xvyeal2vu8yjnznphf9t2rw3gdp0hk5zwq00ws8f3l3j5n3qkqgsyzrwx4qzmgq0xwwk4vz2r6vtsykgz089jncvycmem3535zjwvvtvjw8v98y0d5ydwte575gjm7a7k";
1590
1591        // We test the full roundtrip only with the `sapling` and `orchard` features enabled,
1592        // because we will not generate these parts of the encoding if the UFVK does not have an
1593        // these parts.
1594        #[cfg(all(feature = "sapling", feature = "orchard"))]
1595        {
1596            #[cfg(feature = "transparent-inputs")]
1597            assert_eq!(encoded, encoded_with_t);
1598            #[cfg(not(feature = "transparent-inputs"))]
1599            assert_eq!(encoded, _encoded_no_t);
1600        }
1601
1602        let decoded = UnifiedFullViewingKey::decode(&MAIN_NETWORK, &encoded).unwrap();
1603        let reencoded = decoded.encode(&MAIN_NETWORK);
1604        assert_eq!(encoded, reencoded);
1605
1606        #[cfg(feature = "transparent-inputs")]
1607        assert_eq!(
1608            decoded.transparent.map(|t| t.serialize()),
1609            ufvk.transparent.as_ref().map(|t| t.serialize()),
1610        );
1611        #[cfg(feature = "sapling")]
1612        assert_eq!(
1613            decoded.sapling.map(|s| s.to_bytes()),
1614            ufvk.sapling.map(|s| s.to_bytes()),
1615        );
1616        #[cfg(feature = "orchard")]
1617        assert_eq!(
1618            decoded.orchard.map(|o| o.to_bytes()),
1619            ufvk.orchard.map(|o| o.to_bytes()),
1620        );
1621
1622        let decoded_with_t = UnifiedFullViewingKey::decode(&MAIN_NETWORK, encoded_with_t).unwrap();
1623        #[cfg(feature = "transparent-inputs")]
1624        assert_eq!(
1625            decoded_with_t.transparent.map(|t| t.serialize()),
1626            ufvk.transparent.as_ref().map(|t| t.serialize()),
1627        );
1628
1629        // Both Orchard and Sapling enabled
1630        #[cfg(all(
1631            feature = "orchard",
1632            feature = "sapling",
1633            feature = "transparent-inputs"
1634        ))]
1635        assert_eq!(decoded_with_t.unknown.len(), 0);
1636        #[cfg(all(
1637            feature = "orchard",
1638            feature = "sapling",
1639            not(feature = "transparent-inputs")
1640        ))]
1641        assert_eq!(decoded_with_t.unknown.len(), 1);
1642
1643        // Orchard enabled
1644        #[cfg(all(
1645            feature = "orchard",
1646            not(feature = "sapling"),
1647            feature = "transparent-inputs"
1648        ))]
1649        assert_eq!(decoded_with_t.unknown.len(), 1);
1650        #[cfg(all(
1651            feature = "orchard",
1652            not(feature = "sapling"),
1653            not(feature = "transparent-inputs")
1654        ))]
1655        assert_eq!(decoded_with_t.unknown.len(), 2);
1656
1657        // Sapling enabled
1658        #[cfg(all(
1659            not(feature = "orchard"),
1660            feature = "sapling",
1661            feature = "transparent-inputs"
1662        ))]
1663        assert_eq!(decoded_with_t.unknown.len(), 1);
1664        #[cfg(all(
1665            not(feature = "orchard"),
1666            feature = "sapling",
1667            not(feature = "transparent-inputs")
1668        ))]
1669        assert_eq!(decoded_with_t.unknown.len(), 2);
1670    }
1671
1672    #[test]
1673    #[cfg(feature = "transparent-inputs")]
1674    fn ufvk_derivation() {
1675        use crate::keys::UnifiedAddressRequest;
1676
1677        use super::{ReceiverRequirement::*, UnifiedSpendingKey};
1678
1679        for tv in test_vectors::UNIFIED {
1680            let usk = UnifiedSpendingKey::from_seed(
1681                &MAIN_NETWORK,
1682                &tv.root_seed,
1683                AccountId::try_from(tv.account).unwrap(),
1684            )
1685            .expect("seed produced a valid unified spending key");
1686
1687            let d_idx = DiversifierIndex::from(tv.diversifier_index);
1688            let ufvk = usk.to_unified_full_viewing_key();
1689
1690            // The test vectors contain some diversifier indices that do not generate
1691            // valid Sapling addresses, so skip those.
1692            #[cfg(feature = "sapling")]
1693            if ufvk.sapling().unwrap().address(d_idx).is_none() {
1694                continue;
1695            }
1696
1697            let ua = ufvk
1698                .address(
1699                    d_idx,
1700                    UnifiedAddressRequest::unsafe_custom(Omit, Require, Require),
1701                )
1702                .unwrap_or_else(|err| {
1703                    panic!(
1704                        "unified address generation failed for account {}: {:?}",
1705                        tv.account, err
1706                    )
1707                });
1708
1709            match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
1710                Some(Address::Unified(tvua)) => {
1711                    // We always derive transparent and Sapling receivers, but not
1712                    // every value in the test vectors has these present.
1713                    if tvua.has_transparent() {
1714                        assert_eq!(tvua.transparent(), ua.transparent());
1715                    }
1716                    #[cfg(feature = "sapling")]
1717                    if tvua.has_sapling() {
1718                        assert_eq!(tvua.sapling(), ua.sapling());
1719                    }
1720                }
1721                _other => {
1722                    panic!(
1723                        "{} did not decode to a valid unified address",
1724                        tv.unified_addr
1725                    );
1726                }
1727            }
1728        }
1729    }
1730
1731    #[test]
1732    #[cfg(any(feature = "orchard", feature = "sapling"))]
1733    fn uivk_round_trip() {
1734        use zcash_protocol::consensus::NetworkType;
1735
1736        #[cfg(feature = "orchard")]
1737        let orchard = {
1738            let sk =
1739                orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
1740            Some(orchard::keys::FullViewingKey::from(&sk).to_ivk(Scope::External))
1741        };
1742
1743        #[cfg(feature = "sapling")]
1744        let sapling = {
1745            let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO);
1746            Some(extsk.to_diversifiable_full_viewing_key().to_external_ivk())
1747        };
1748
1749        #[cfg(feature = "transparent-inputs")]
1750        let transparent = {
1751            let privkey =
1752                AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::ZERO).unwrap();
1753            Some(privkey.to_account_pubkey().derive_external_ivk().unwrap())
1754        };
1755
1756        let uivk = UnifiedIncomingViewingKey::new(
1757            #[cfg(feature = "transparent-inputs")]
1758            transparent,
1759            #[cfg(feature = "sapling")]
1760            sapling,
1761            #[cfg(feature = "orchard")]
1762            orchard,
1763        );
1764
1765        let encoded = uivk.render().encode(&NetworkType::Main);
1766
1767        // Test encoded form against known values; these test vectors contain Orchard receivers
1768        // that will be treated as unknown if the `orchard` feature is not enabled.
1769        let encoded_with_t = "uivk1z28yg638vjwusmf0zc9ad2j0mpv6s42wc5kqt004aaqfu5xxxgu7mdcydn9qf723fnryt34s6jyxyw0jt7spq04c3v9ze6qe9gjjc5aglz8zv5pqtw58czd0actynww5n85z3052kzgy6cu0fyjafyp4sr4kppyrrwhwev2rr0awq6m8d66esvk6fgacggqnswg5g9gkv6t6fj9ajhyd0gmel4yzscprpzduncc0e2lywufup6fvzf6y8cefez2r99pgge5yyfuus0r60khgu895pln5e7nn77q6s9kh2uwf6lrfu06ma2kd7r05jjvl4hn6nupge8fajh0cazd7mkmz23t79w";
1770        let _encoded_no_t = "uivk1020vq9j5zeqxh303sxa0zv2hn9wm9fev8x0p8yqxdwyzde9r4c90fcglc63usj0ycl2scy8zxuhtser0qrq356xfy8x3vyuxu7f6gas75svl9v9m3ctuazsu0ar8e8crtx7x6zgh4kw8xm3q4rlkpm9er2wefxhhf9pn547gpuz9vw27gsdp6c03nwlrxgzhr2g6xek0x8l5avrx9ue9lf032tr7kmhqf3nfdxg7ldfgx6yf09g";
1771
1772        // We test the full roundtrip only with the `sapling` and `orchard` features enabled,
1773        // because we will not generate these parts of the encoding if the UIVK does not have an
1774        // these parts.
1775        #[cfg(all(feature = "sapling", feature = "orchard"))]
1776        {
1777            #[cfg(feature = "transparent-inputs")]
1778            assert_eq!(encoded, encoded_with_t);
1779            #[cfg(not(feature = "transparent-inputs"))]
1780            assert_eq!(encoded, _encoded_no_t);
1781        }
1782
1783        let decoded = UnifiedIncomingViewingKey::parse(&Uivk::decode(&encoded).unwrap().1).unwrap();
1784        let reencoded = decoded.render().encode(&NetworkType::Main);
1785        assert_eq!(encoded, reencoded);
1786
1787        #[cfg(feature = "transparent-inputs")]
1788        assert_eq!(
1789            decoded.transparent.map(|t| t.serialize()),
1790            uivk.transparent.as_ref().map(|t| t.serialize()),
1791        );
1792        #[cfg(feature = "sapling")]
1793        assert_eq!(
1794            decoded.sapling.map(|s| s.to_bytes()),
1795            uivk.sapling.map(|s| s.to_bytes()),
1796        );
1797        #[cfg(feature = "orchard")]
1798        assert_eq!(
1799            decoded.orchard.map(|o| o.to_bytes()),
1800            uivk.orchard.map(|o| o.to_bytes()),
1801        );
1802
1803        let decoded_with_t =
1804            UnifiedIncomingViewingKey::parse(&Uivk::decode(encoded_with_t).unwrap().1).unwrap();
1805        #[cfg(feature = "transparent-inputs")]
1806        assert_eq!(
1807            decoded_with_t.transparent.map(|t| t.serialize()),
1808            uivk.transparent.as_ref().map(|t| t.serialize()),
1809        );
1810
1811        // Both Orchard and Sapling enabled
1812        #[cfg(all(
1813            feature = "orchard",
1814            feature = "sapling",
1815            feature = "transparent-inputs"
1816        ))]
1817        assert_eq!(decoded_with_t.unknown.len(), 0);
1818        #[cfg(all(
1819            feature = "orchard",
1820            feature = "sapling",
1821            not(feature = "transparent-inputs")
1822        ))]
1823        assert_eq!(decoded_with_t.unknown.len(), 1);
1824
1825        // Orchard enabled
1826        #[cfg(all(
1827            feature = "orchard",
1828            not(feature = "sapling"),
1829            feature = "transparent-inputs"
1830        ))]
1831        assert_eq!(decoded_with_t.unknown.len(), 1);
1832        #[cfg(all(
1833            feature = "orchard",
1834            not(feature = "sapling"),
1835            not(feature = "transparent-inputs")
1836        ))]
1837        assert_eq!(decoded_with_t.unknown.len(), 2);
1838
1839        // Sapling enabled
1840        #[cfg(all(
1841            not(feature = "orchard"),
1842            feature = "sapling",
1843            feature = "transparent-inputs"
1844        ))]
1845        assert_eq!(decoded_with_t.unknown.len(), 1);
1846        #[cfg(all(
1847            not(feature = "orchard"),
1848            feature = "sapling",
1849            not(feature = "transparent-inputs")
1850        ))]
1851        assert_eq!(decoded_with_t.unknown.len(), 2);
1852    }
1853
1854    #[test]
1855    #[cfg(feature = "transparent-inputs")]
1856    fn uivk_derivation() {
1857        use crate::keys::UnifiedAddressRequest;
1858
1859        use super::{ReceiverRequirement::*, UnifiedSpendingKey};
1860
1861        for tv in test_vectors::UNIFIED {
1862            let usk = UnifiedSpendingKey::from_seed(
1863                &MAIN_NETWORK,
1864                &tv.root_seed,
1865                AccountId::try_from(tv.account).unwrap(),
1866            )
1867            .expect("seed produced a valid unified spending key");
1868
1869            let d_idx = DiversifierIndex::from(tv.diversifier_index);
1870            let uivk = usk
1871                .to_unified_full_viewing_key()
1872                .to_unified_incoming_viewing_key();
1873
1874            // The test vectors contain some diversifier indices that do not generate
1875            // valid Sapling addresses, so skip those.
1876            #[cfg(feature = "sapling")]
1877            if uivk.sapling().as_ref().unwrap().address_at(d_idx).is_none() {
1878                continue;
1879            }
1880
1881            let ua = uivk
1882                .address(
1883                    d_idx,
1884                    UnifiedAddressRequest::unsafe_custom(Omit, Require, Require),
1885                )
1886                .unwrap_or_else(|err| {
1887                    panic!(
1888                        "unified address generation failed for account {}: {:?}",
1889                        tv.account, err
1890                    )
1891                });
1892
1893            match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
1894                Some(Address::Unified(tvua)) => {
1895                    // We always derive transparent and Sapling receivers, but not
1896                    // every value in the test vectors has these present.
1897                    if tvua.has_transparent() {
1898                        assert_eq!(tvua.transparent(), ua.transparent());
1899                    }
1900                    #[cfg(feature = "sapling")]
1901                    if tvua.has_sapling() {
1902                        assert_eq!(tvua.sapling(), ua.sapling());
1903                    }
1904                }
1905                _other => {
1906                    panic!(
1907                        "{} did not decode to a valid unified address",
1908                        tv.unified_addr
1909                    );
1910                }
1911            }
1912        }
1913    }
1914
1915    proptest! {
1916        #[test]
1917        #[cfg(feature = "unstable")]
1918        fn prop_usk_roundtrip(usk in arb_unified_spending_key(zcash_protocol::consensus::Network::MainNetwork)) {
1919            let encoded = usk.to_bytes(Era::Orchard);
1920
1921            #[allow(clippy::let_and_return)]
1922            let encoded_len = {
1923                let len = 4;
1924
1925                #[cfg(feature = "orchard")]
1926                let len = len + 2 + 32;
1927
1928                let len = len + 2 + 169;
1929
1930                // Transparent part is an `xprv` transparent extended key deserialized
1931                // into bytes from Base58, minus the 4 prefix bytes.
1932                #[cfg(feature = "transparent-inputs")]
1933                let len = len + 2 + 74;
1934
1935                #[allow(clippy::let_and_return)]
1936                len
1937            };
1938            assert_eq!(encoded.len(), encoded_len);
1939
1940            let decoded = UnifiedSpendingKey::from_bytes(Era::Orchard, &encoded);
1941            let decoded = decoded.unwrap_or_else(|e| panic!("Error decoding USK: {:?}", e));
1942
1943            #[cfg(feature = "orchard")]
1944            assert!(bool::from(decoded.orchard().ct_eq(usk.orchard())));
1945
1946            assert_eq!(decoded.sapling(), usk.sapling());
1947
1948            #[cfg(feature = "transparent-inputs")]
1949            assert_eq!(decoded.transparent().to_bytes(), usk.transparent().to_bytes());
1950        }
1951    }
1952}