Skip to main content

zcash_keys/
keys.rs

1//! Helper functions for managing light client key material.
2use alloc::{collections::BTreeSet, string::String, vec::Vec};
3use core::fmt::{self, Display};
4use nonempty::NonEmpty;
5#[cfg(feature = "transparent-inputs")]
6use {
7    ::transparent::keys::{IncomingViewingKey, NonHardenedChildIndex, TransparentKeyScope},
8    core::convert::TryInto,
9};
10
11use zcash_address::unified::{self, Container, Encoding, MetadataItem, Revision, Typecode, Uitem};
12use zcash_protocol::{PoolType, consensus};
13use zip32::{AccountId, DiversifierIndex};
14
15use crate::{
16    address::UnifiedAddress,
17    encoding::{UnifiedDecodingError, UnifiedEncodingError},
18};
19
20// The requirement combinators and the `ReceiverRequirements` constants below name these
21// variants bare.
22use ReceiverRequirement::*;
23
24#[cfg(any(feature = "sapling", feature = "orchard"))]
25use zcash_protocol::consensus::NetworkConstants;
26
27#[cfg(feature = "transparent-inputs")]
28#[cfg(all(
29    feature = "transparent-inputs",
30    any(test, feature = "test-dependencies")
31))]
32use ::transparent::address::TransparentAddress;
33#[cfg(feature = "transparent-inputs")]
34use ::transparent::zip48::{P2shFullViewingKey, P2shIncomingViewingKey};
35
36#[cfg(feature = "unstable")]
37use {
38    byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt},
39    core::convert::TryFrom,
40    corez::io::{Read, Write},
41    secrecy::SecretVec,
42    zcash_encoding::CompactSize,
43    zcash_protocol::consensus::BranchId,
44};
45
46#[cfg(all(
47    feature = "unstable",
48    any(feature = "sapling", feature = "transparent-inputs")
49))]
50use secrecy::zeroize::Zeroizing;
51
52#[cfg(feature = "orchard")]
53use orchard::{self, keys::Scope};
54
55#[cfg(all(feature = "sapling", feature = "unstable"))]
56use ::sapling::zip32::ExtendedFullViewingKey;
57
58#[cfg(test)]
59pub(crate) mod test_vectors {
60    pub(crate) mod unified_viewing_keys_r2;
61}
62
63#[cfg(feature = "sapling")]
64pub mod sapling {
65    pub use sapling::zip32::{
66        DiversifiableFullViewingKey, ExtendedFullViewingKey, ExtendedSpendingKey,
67    };
68    use zip32::{AccountId, ChildIndex};
69
70    /// Derives the ZIP 32 [`ExtendedSpendingKey`] for a given coin type and account from the
71    /// given seed.
72    ///
73    /// Returns `None` if the derivation path produces an invalid Sapling spending key.
74    ///
75    /// # Panics
76    ///
77    /// Panics if `seed` is shorter than 32 bytes.
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use zcash_protocol::constants::testnet::COIN_TYPE;
83    /// use zcash_keys::keys::sapling;
84    /// use zip32::AccountId;
85    ///
86    /// let extsk = sapling::spending_key(&[0; 32][..], COIN_TYPE, AccountId::ZERO).expect("the derivation path yields a valid key");
87    /// ```
88    /// [`ExtendedSpendingKey`]: sapling::zip32::ExtendedSpendingKey
89    pub fn spending_key(
90        seed: &[u8],
91        coin_type: u32,
92        account: AccountId,
93    ) -> Option<ExtendedSpendingKey> {
94        if seed.len() < 32 {
95            panic!("ZIP 32 seeds MUST be at least 32 bytes");
96        }
97
98        ExtendedSpendingKey::from_path(
99            &ExtendedSpendingKey::master(seed)?,
100            &[
101                ChildIndex::hardened(32),
102                ChildIndex::hardened(coin_type),
103                account.into(),
104            ],
105        )
106    }
107}
108
109#[cfg(any(feature = "transparent-key-encoding", feature = "transparent-inputs"))]
110pub mod transparent;
111
112#[cfg(feature = "zcashd-compat")]
113pub mod zcashd;
114
115#[cfg(feature = "transparent-inputs")]
116fn to_transparent_child_index(j: DiversifierIndex) -> Option<NonHardenedChildIndex> {
117    let (low_4_bytes, rest) = j.as_bytes().split_at(4);
118    let transparent_j = u32::from_le_bytes(low_4_bytes.try_into().unwrap());
119    if rest.iter().any(|b| b != &0) {
120        None
121    } else {
122        NonHardenedChildIndex::from_index(transparent_j)
123    }
124}
125
126#[derive(Debug)]
127pub enum DerivationError {
128    /// Sapling key derivation produced an invalid spending key.
129    #[cfg(feature = "sapling")]
130    Sapling,
131    #[cfg(feature = "orchard")]
132    Orchard(orchard::zip32::Error),
133    #[cfg(feature = "transparent-inputs")]
134    Transparent(bip32::Error),
135}
136
137impl Display for DerivationError {
138    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        match self {
140            #[cfg(feature = "sapling")]
141            DerivationError::Sapling => {
142                write!(_f, "Sapling error: derived an invalid spending key")
143            }
144            #[cfg(feature = "orchard")]
145            DerivationError::Orchard(e) => write!(_f, "Orchard error: {e}"),
146            #[cfg(feature = "transparent-inputs")]
147            DerivationError::Transparent(e) => write!(_f, "Transparent error: {e}"),
148            #[cfg(not(any(
149                feature = "sapling",
150                feature = "orchard",
151                feature = "transparent-inputs"
152            )))]
153            other => {
154                unreachable!("Unhandled DerivationError variant {:?}", other)
155            }
156        }
157    }
158}
159
160#[cfg(feature = "std")]
161impl std::error::Error for DerivationError {}
162
163/// A version identifier for the encoding of unified spending keys.
164///
165/// Each era corresponds to a range of block heights. During an era, the unified spending key
166/// parsed from an encoded form tagged with that era's identifier is expected to provide
167/// sufficient spending authority to spend any non-Sprout shielded note created in a transaction
168/// within the era's block range.
169#[cfg(feature = "unstable")]
170#[derive(Debug, PartialEq, Eq)]
171pub enum Era {
172    /// The Orchard era begins at Orchard activation, and will end if a new pool that requires a
173    /// change to unified spending keys is introduced.
174    Orchard,
175}
176
177/// A type for errors that can occur when decoding keys from their serialized representations.
178#[derive(Debug, PartialEq, Eq)]
179pub enum DecodingError {
180    #[cfg(feature = "unstable")]
181    ReadError(&'static str),
182    #[cfg(feature = "unstable")]
183    EraInvalid,
184    #[cfg(feature = "unstable")]
185    EraMismatch(Era),
186    #[cfg(feature = "unstable")]
187    TypecodeInvalid,
188    #[cfg(feature = "unstable")]
189    LengthInvalid,
190    #[cfg(feature = "unstable")]
191    LengthMismatch(Typecode, u32),
192    #[cfg(feature = "unstable")]
193    InsufficientData(Typecode),
194    /// The key data could not be decoded from its string representation to a valid key.
195    KeyDataInvalid(Typecode),
196}
197
198impl core::fmt::Display for DecodingError {
199    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
200        match self {
201            #[cfg(feature = "unstable")]
202            DecodingError::ReadError(s) => write!(f, "Read error: {s}"),
203            #[cfg(feature = "unstable")]
204            DecodingError::EraInvalid => write!(f, "Invalid era"),
205            #[cfg(feature = "unstable")]
206            DecodingError::EraMismatch(e) => write!(f, "Era mismatch: actual {e:?}"),
207            #[cfg(feature = "unstable")]
208            DecodingError::TypecodeInvalid => write!(f, "Invalid typecode"),
209            #[cfg(feature = "unstable")]
210            DecodingError::LengthInvalid => write!(f, "Invalid length"),
211            #[cfg(feature = "unstable")]
212            DecodingError::LengthMismatch(t, l) => {
213                write!(f, "Length mismatch: received {l} bytes for typecode {t:?}")
214            }
215            #[cfg(feature = "unstable")]
216            DecodingError::InsufficientData(t) => {
217                write!(f, "Insufficient data for typecode {t:?}")
218            }
219            DecodingError::KeyDataInvalid(t) => write!(f, "Invalid key data for key type {t:?}"),
220        }
221    }
222}
223
224#[cfg(feature = "std")]
225impl std::error::Error for DecodingError {}
226
227#[cfg(feature = "unstable")]
228impl Era {
229    /// Returns the unique identifier for the era.
230    fn id(&self) -> u32 {
231        // We use the consensus branch id of the network upgrade that introduced a
232        // new USK format as the identifier for the era.
233        match self {
234            Era::Orchard => u32::from(BranchId::Nu5),
235        }
236    }
237
238    fn try_from_id(id: u32) -> Option<Self> {
239        BranchId::try_from(id).ok().and_then(|b| match b {
240            BranchId::Nu5 => Some(Era::Orchard),
241            _ => None,
242        })
243    }
244}
245
246/// A set of spending keys that are all associated with a single ZIP-0032 account identifier.
247///
248/// With the `zeroize` feature, the Sapling and Orchard spending keys are erased from
249/// memory when this key is dropped. The transparent spending key is not erased.
250#[derive(Clone)]
251pub struct UnifiedSpendingKey {
252    #[cfg(feature = "transparent-inputs")]
253    transparent: ::transparent::keys::AccountPrivKey,
254    #[cfg(feature = "sapling")]
255    sapling: sapling::ExtendedSpendingKey,
256    #[cfg(feature = "orchard")]
257    orchard: orchard::keys::SpendingKey,
258}
259
260impl core::fmt::Debug for UnifiedSpendingKey {
261    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
262        let mut d = f.debug_struct("UnifiedSpendingKey");
263        #[cfg(feature = "transparent-inputs")]
264        d.field("transparent", &"...");
265        #[cfg(feature = "sapling")]
266        d.field("sapling", &"...");
267        #[cfg(feature = "orchard")]
268        d.field("orchard", &"...");
269        d.finish()
270    }
271}
272
273impl UnifiedSpendingKey {
274    pub fn from_seed<P: consensus::Parameters>(
275        _params: &P,
276        seed: &[u8],
277        _account: AccountId,
278    ) -> Result<UnifiedSpendingKey, DerivationError> {
279        if seed.len() < 32 {
280            panic!("ZIP 32 seeds MUST be at least 32 bytes");
281        }
282
283        UnifiedSpendingKey::from_checked_parts(
284            #[cfg(feature = "transparent-inputs")]
285            ::transparent::keys::AccountPrivKey::from_seed(_params, seed, _account)
286                .map_err(DerivationError::Transparent)?,
287            #[cfg(feature = "sapling")]
288            sapling::spending_key(seed, _params.coin_type(), _account)
289                .ok_or(DerivationError::Sapling)?,
290            #[cfg(feature = "orchard")]
291            orchard::keys::SpendingKey::from_zip32_seed(seed, _params.coin_type(), _account)
292                .map_err(DerivationError::Orchard)?,
293        )
294    }
295
296    /// Construct a USK from its constituent parts, after verifying that UIVK derivation can
297    /// succeed.
298    fn from_checked_parts(
299        #[cfg(feature = "transparent-inputs")] transparent: ::transparent::keys::AccountPrivKey,
300        #[cfg(feature = "sapling")] sapling: sapling::ExtendedSpendingKey,
301        #[cfg(feature = "orchard")] orchard: orchard::keys::SpendingKey,
302    ) -> Result<UnifiedSpendingKey, DerivationError> {
303        // Verify that FVK and IVK derivation succeed; we don't want to construct a USK
304        // that can't derive transparent addresses.
305        #[cfg(feature = "transparent-inputs")]
306        let _ = transparent.to_account_pubkey().derive_external_ivk()?;
307
308        Ok(UnifiedSpendingKey {
309            #[cfg(feature = "transparent-inputs")]
310            transparent,
311            #[cfg(feature = "sapling")]
312            sapling,
313            #[cfg(feature = "orchard")]
314            orchard,
315        })
316    }
317
318    pub fn to_unified_full_viewing_key(&self) -> UnifiedFullViewingKey {
319        UnifiedFullViewingKey {
320            #[cfg(feature = "transparent-inputs")]
321            transparent: Some(TransparentFvk::P2pkh(self.transparent.to_account_pubkey())),
322            #[cfg(feature = "sapling")]
323            sapling: Some(self.sapling.to_diversifiable_full_viewing_key()),
324            #[cfg(feature = "orchard")]
325            orchard: Some((&self.orchard).into()),
326            unknown: vec![],
327            expiry_height: None,
328            expiry_time: None,
329            unknown_metadata: vec![],
330        }
331    }
332
333    /// Returns the transparent component of the unified key at the
334    /// BIP44 path `m/44'/<coin_type>'/<account>'`.
335    #[cfg(feature = "transparent-inputs")]
336    pub fn transparent(&self) -> &::transparent::keys::AccountPrivKey {
337        &self.transparent
338    }
339
340    /// Returns the Sapling extended spending key component of this unified spending key.
341    #[cfg(feature = "sapling")]
342    pub fn sapling(&self) -> &sapling::ExtendedSpendingKey {
343        &self.sapling
344    }
345
346    /// Returns the Orchard spending key component of this unified spending key.
347    #[cfg(feature = "orchard")]
348    pub fn orchard(&self) -> &orchard::keys::SpendingKey {
349        &self.orchard
350    }
351
352    /// Returns a binary encoding of this key suitable for decoding with [`Self::from_bytes`].
353    ///
354    /// The encoded form of a unified spending key is only intended for use
355    /// within wallets when required for storage and/or crossing FFI boundaries;
356    /// unified spending keys should not be exposed to users, and consequently
357    /// no string-based encoding is defined. This encoding does not include any
358    /// internal validation metadata (such as checksums) as keys decoded from
359    /// this form will necessarily be validated when the attempt is made to
360    /// spend a note that they have authority for.
361    #[cfg(feature = "unstable")]
362    pub fn to_bytes(&self, era: Era) -> SecretVec<u8> {
363        #[cfg(feature = "sapling")]
364        let sapling_key_bytes = Zeroizing::new(self.sapling().to_bytes());
365        #[cfg(feature = "transparent-inputs")]
366        let transparent_key_bytes = Zeroizing::new(self.transparent().to_bytes());
367
368        let items: Vec<(Typecode, &[u8])> = vec![
369            #[cfg(feature = "orchard")]
370            (Typecode::ORCHARD, &self.orchard().to_bytes()[..]),
371            #[cfg(feature = "sapling")]
372            (Typecode::SAPLING, &sapling_key_bytes[..]),
373            #[cfg(feature = "transparent-inputs")]
374            (Typecode::P2PKH, &transparent_key_bytes[..]),
375        ];
376
377        // Reserve the full length up front, so that growing the buffer does not leave
378        // copies of the key material in freed memory.
379        let encoded_len =
380            items
381                .iter()
382                .fold(core::mem::size_of::<u32>(), |len, (typecode, key_bytes)| {
383                    len + CompactSize::serialized_size(usize::try_from(*typecode).unwrap())
384                        + CompactSize::serialized_size(key_bytes.len())
385                        + key_bytes.len()
386                });
387        let mut result = Vec::with_capacity(encoded_len);
388        result.write_u32::<LittleEndian>(era.id()).unwrap();
389        for (typecode, key_bytes) in items {
390            CompactSize::write(&mut result, usize::try_from(typecode).unwrap()).unwrap();
391            CompactSize::write(&mut result, key_bytes.len()).unwrap();
392            result.write_all(key_bytes).unwrap();
393        }
394
395        SecretVec::new(result)
396    }
397
398    /// Decodes a [`UnifiedSpendingKey`] value from its serialized representation.
399    ///
400    /// See [`Self::to_bytes`] for additional detail about the encoded form.
401    #[allow(clippy::unnecessary_unwrap)]
402    #[cfg(feature = "unstable")]
403    pub fn from_bytes(era: Era, encoded: &[u8]) -> Result<Self, DecodingError> {
404        let mut source = corez::io::Cursor::new(encoded);
405        let decoded_era = source
406            .read_u32::<LittleEndian>()
407            .map_err(|_| DecodingError::ReadError("era"))
408            .and_then(|id| Era::try_from_id(id).ok_or(DecodingError::EraInvalid))?;
409
410        if decoded_era != era {
411            return Err(DecodingError::EraMismatch(decoded_era));
412        }
413
414        #[cfg(feature = "orchard")]
415        let mut orchard = None;
416        #[cfg(feature = "sapling")]
417        let mut sapling = None;
418        #[cfg(feature = "transparent-inputs")]
419        let mut transparent = None;
420        loop {
421            let tc = CompactSize::read_t::<_, u32>(&mut source)
422                .map_err(|_| DecodingError::ReadError("typecode"))
423                .and_then(|v| Typecode::try_from(v).map_err(|_| DecodingError::TypecodeInvalid))?;
424
425            let len = CompactSize::read_t::<_, u32>(&mut source)
426                .map_err(|_| DecodingError::ReadError("key length"))?;
427
428            match tc {
429                Typecode::ORCHARD => {
430                    if len != 32 {
431                        return Err(DecodingError::LengthMismatch(Typecode::ORCHARD, len));
432                    }
433
434                    let mut key = [0u8; 32];
435                    source
436                        .read_exact(&mut key)
437                        .map_err(|_| DecodingError::InsufficientData(Typecode::ORCHARD))?;
438
439                    #[cfg(feature = "orchard")]
440                    {
441                        orchard = Some(
442                            Option::<orchard::keys::SpendingKey>::from(
443                                orchard::keys::SpendingKey::from_bytes(key),
444                            )
445                            .ok_or(DecodingError::KeyDataInvalid(Typecode::ORCHARD))?,
446                        );
447                    }
448                }
449                Typecode::SAPLING => {
450                    if len != 169 {
451                        return Err(DecodingError::LengthMismatch(Typecode::SAPLING, len));
452                    }
453
454                    let mut key = [0u8; 169];
455                    source
456                        .read_exact(&mut key)
457                        .map_err(|_| DecodingError::InsufficientData(Typecode::SAPLING))?;
458
459                    #[cfg(feature = "sapling")]
460                    {
461                        sapling = Some(
462                            sapling::ExtendedSpendingKey::from_bytes(&key)
463                                .map_err(|_| DecodingError::KeyDataInvalid(Typecode::SAPLING))?,
464                        );
465                    }
466                }
467                Typecode::P2PKH => {
468                    if len != 74 {
469                        return Err(DecodingError::LengthMismatch(Typecode::P2PKH, len));
470                    }
471
472                    let mut key = [0u8; 74];
473                    source
474                        .read_exact(&mut key)
475                        .map_err(|_| DecodingError::InsufficientData(Typecode::P2PKH))?;
476
477                    #[cfg(feature = "transparent-inputs")]
478                    {
479                        transparent = Some(
480                            ::transparent::keys::AccountPrivKey::from_bytes(&key)
481                                .ok_or(DecodingError::KeyDataInvalid(Typecode::P2PKH))?,
482                        );
483                    }
484                }
485                _ => {
486                    return Err(DecodingError::TypecodeInvalid);
487                }
488            }
489
490            #[cfg(feature = "orchard")]
491            let has_orchard = orchard.is_some();
492            #[cfg(not(feature = "orchard"))]
493            let has_orchard = true;
494
495            #[cfg(feature = "sapling")]
496            let has_sapling = sapling.is_some();
497            #[cfg(not(feature = "sapling"))]
498            let has_sapling = true;
499
500            #[cfg(feature = "transparent-inputs")]
501            let has_transparent = transparent.is_some();
502            #[cfg(not(feature = "transparent-inputs"))]
503            let has_transparent = true;
504
505            if has_orchard && has_sapling && has_transparent {
506                return UnifiedSpendingKey::from_checked_parts(
507                    #[cfg(feature = "transparent-inputs")]
508                    transparent.unwrap(),
509                    #[cfg(feature = "sapling")]
510                    sapling.unwrap(),
511                    #[cfg(feature = "orchard")]
512                    orchard.unwrap(),
513                )
514                .map_err(|_| DecodingError::KeyDataInvalid(Typecode::P2PKH));
515            }
516        }
517    }
518
519    /// Returns the unified address corresponding to the smallest valid diversifier index,
520    /// along with that diversifier index.
521    ///
522    /// See [`UnifiedFullViewingKey::default_address`] for additional details.
523    #[cfg(any(test, feature = "test-dependencies"))]
524    pub fn default_address(
525        &self,
526        request: UnifiedAddressRequest,
527    ) -> (UnifiedAddress, DiversifierIndex) {
528        self.to_unified_full_viewing_key()
529            .default_address(request)
530            .unwrap()
531    }
532
533    /// Returns the default external transparent address using the transparent account pubkey.
534    ///
535    /// See [`ExternalIvk::default_address`] for more information.
536    ///
537    /// [`ExternalIvk::default_address`]: ::transparent::keys::ExternalIvk::default_address
538    #[cfg(all(
539        feature = "transparent-inputs",
540        any(test, feature = "test-dependencies")
541    ))]
542    pub fn default_transparent_address(&self) -> (TransparentAddress, NonHardenedChildIndex) {
543        self.transparent()
544            .to_account_pubkey()
545            .derive_external_ivk()
546            .unwrap()
547            .default_address()
548    }
549}
550
551/// Errors that can occur in the generation of unified addresses.
552#[derive(Clone, Debug)]
553pub enum AddressGenerationError {
554    /// The requested diversifier index was outside the range of valid transparent
555    /// child address indices.
556    #[cfg(feature = "transparent-inputs")]
557    InvalidTransparentChildIndex(DiversifierIndex),
558    /// The requested key scope is not supported for address derivation.
559    #[cfg(feature = "transparent-inputs")]
560    UnsupportedTransparentKeyScope(TransparentKeyScope),
561    /// An error occurred in [`bip32`] derivation of a transparent address.
562    #[cfg(feature = "transparent-inputs")]
563    Bip32DerivationError(bip32::Error),
564    /// The diversifier index could not be mapped to a valid Sapling diversifier.
565    #[cfg(feature = "sapling")]
566    InvalidSaplingDiversifierIndex(DiversifierIndex),
567    /// The space of available diversifier indices has been exhausted.
568    DiversifierSpaceExhausted,
569    /// A requested address typecode was not recognized, so we are unable to generate the address
570    /// as requested.
571    ReceiverTypeNotSupported(Typecode),
572    /// A requested address typecode was recognized, but the unified key being used to generate the
573    /// address lacks an item of the requested type.
574    KeyNotAvailable(Typecode),
575    /// The address request permits no receiver that the unified key being used to generate the
576    /// address can provide.
577    ///
578    /// Omitting both shielded receiver types is permitted only for a key that has no shielded
579    /// item; see [`ReceiverRequirements::TRANSPARENT_ONLY`].
580    NoSatisfiableReceiver,
581}
582
583impl fmt::Display for AddressGenerationError {
584    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
585        match &self {
586            #[cfg(feature = "transparent-inputs")]
587            AddressGenerationError::InvalidTransparentChildIndex(i) => {
588                write!(
589                    f,
590                    "Child index {i:?} does not generate a valid transparent receiver"
591                )
592            }
593            #[cfg(feature = "transparent-inputs")]
594            AddressGenerationError::UnsupportedTransparentKeyScope(i) => {
595                write!(f, "Key scope {i:?} is not supported for key derivation")
596            }
597            #[cfg(feature = "transparent-inputs")]
598            AddressGenerationError::Bip32DerivationError(e) => {
599                write!(f, "{e}")
600            }
601            #[cfg(feature = "sapling")]
602            AddressGenerationError::InvalidSaplingDiversifierIndex(i) => {
603                write!(
604                    f,
605                    "Child index {i:?} does not generate a valid Sapling receiver"
606                )
607            }
608            AddressGenerationError::DiversifierSpaceExhausted => {
609                write!(
610                    f,
611                    "Exhausted the space of diversifier indices without finding an address."
612                )
613            }
614            AddressGenerationError::ReceiverTypeNotSupported(t) => {
615                write!(
616                    f,
617                    "Unified Address generation does not yet support receivers of type {t:?}."
618                )
619            }
620            AddressGenerationError::KeyNotAvailable(t) => {
621                write!(
622                    f,
623                    "The Unified Viewing Key does not contain a key for typecode {t:?}."
624                )
625            }
626            AddressGenerationError::NoSatisfiableReceiver => {
627                write!(
628                    f,
629                    "The address request permits no receiver that this Unified Viewing Key can provide."
630                )
631            }
632        }
633    }
634}
635
636#[cfg(feature = "std")]
637impl std::error::Error for AddressGenerationError {}
638
639#[cfg(feature = "transparent-inputs")]
640impl From<bip32::Error> for AddressGenerationError {
641    fn from(value: bip32::Error) -> Self {
642        AddressGenerationError::Bip32DerivationError(value)
643    }
644}
645
646/// An error type for failures in combining [`ReceiverRequirement`] values.
647#[derive(Clone, Copy, Debug, PartialEq, Eq)]
648pub enum ReceiverRequirementError {
649    /// The two requirements are incompatible: one requires inclusion and the other requires
650    /// omission.
651    Conflict,
652    /// A set of receiver requirements would not include any shielded receiver.
653    ///
654    /// See [`ReceiverRequirements::TRANSPARENT_ONLY`] for the one set of requirements that
655    /// permits no shielded receiver.
656    NoShieldedReceiver,
657}
658
659impl Display for ReceiverRequirementError {
660    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
661        match self {
662            ReceiverRequirementError::Conflict => {
663                write!(f, "Require and Omit receiver requirements are incompatible")
664            }
665            ReceiverRequirementError::NoShieldedReceiver => {
666                write!(
667                    f,
668                    "These receiver requirements must permit at least one shielded receiver"
669                )
670            }
671        }
672    }
673}
674
675/// An enumeration of the ways in which a receiver may be requested to be present in a generated
676/// [`UnifiedAddress`].
677#[derive(Clone, Copy, Debug, PartialEq, Eq)]
678pub enum ReceiverRequirement {
679    /// A receiver of the associated type is required to be present in the generated
680    /// `[UnifiedAddress`], and if it is not possible to generate a receiver of this type, the
681    /// address generation method should return an error. When calling [`Self::intersect`], this
682    /// variant will be preferred over [`ReceiverRequirement::Allow`].
683    Require,
684    /// The associated receiver should be included, if a corresponding item exists in the IVK from
685    /// which the address is being derived and derivation of the receiver succeeds at the given
686    /// diversifier index.
687    Allow,
688    /// No receiver of the associated type may be included in the generated [`UnifiedAddress`]
689    /// under any circumstances. When calling [`Self::intersect`], this variant will be preferred
690    /// over [`ReceiverRequirement::Allow`].
691    Omit,
692}
693
694impl ReceiverRequirement {
695    /// Return the intersection of two requirements that chooses the stronger requirement, if one
696    /// exists. [`ReceiverRequirement::Require`] and [`ReceiverRequirement::Omit`] are
697    /// incompatible; attempting an intersection between these will return an error.
698    pub fn intersect(self, other: Self) -> Result<Self, ReceiverRequirementError> {
699        match (self, other) {
700            (Require, Omit) | (Omit, Require) => Err(ReceiverRequirementError::Conflict),
701            (Require, Require) | (Require, Allow) | (Allow, Require) => Ok(Require),
702            (Allow, Allow) => Ok(Allow),
703            (Allow, Omit) | (Omit, Allow) | (Omit, Omit) => Ok(Omit),
704        }
705    }
706}
707
708/// Specification for how a unified address should be generated from a unified viewing key.
709#[derive(Clone, Copy, Debug)]
710pub enum UnifiedAddressRequest {
711    AllAvailableKeys,
712    Custom(ReceiverRequirements),
713}
714
715impl UnifiedAddressRequest {
716    /// Constructs a new unified address request that allows a receiver of each type.
717    pub const ALLOW_ALL: Self = Self::Custom(ReceiverRequirements::ALLOW_ALL);
718
719    /// Constructs a new unified address request that allows only shielded receivers.
720    pub const SHIELDED: Self = Self::Custom(ReceiverRequirements::SHIELDED);
721
722    /// Constructs a new unified address request that requires an Orchard receiver and no others.
723    pub const ORCHARD: Self = Self::Custom(ReceiverRequirements::ORCHARD);
724
725    /// Constructs a new unified address request that requires a transparent P2PKH receiver and
726    /// no others.
727    ///
728    /// See [`ReceiverRequirements::TRANSPARENT_ONLY`] for the constraints on the resulting
729    /// address.
730    pub const TRANSPARENT_ONLY: Self = Self::Custom(ReceiverRequirements::TRANSPARENT_ONLY);
731
732    pub fn custom(
733        orchard: ReceiverRequirement,
734        sapling: ReceiverRequirement,
735        p2pkh: ReceiverRequirement,
736    ) -> Result<Self, ReceiverRequirementError> {
737        ReceiverRequirements::new(orchard, sapling, p2pkh).map(UnifiedAddressRequest::Custom)
738    }
739
740    pub const fn unsafe_custom(
741        orchard: ReceiverRequirement,
742        sapling: ReceiverRequirement,
743        p2pkh: ReceiverRequirement,
744    ) -> Self {
745        UnifiedAddressRequest::Custom(ReceiverRequirements::unsafe_new(orchard, sapling, p2pkh))
746    }
747}
748
749/// Specification for how a unified address should be generated from a unified viewing key.
750#[derive(Clone, Copy, Debug)]
751pub struct ReceiverRequirements {
752    orchard: ReceiverRequirement,
753    sapling: ReceiverRequirement,
754    p2pkh: ReceiverRequirement,
755}
756
757impl ReceiverRequirements {
758    /// Construct a new unified address request from its constituent parts.
759    ///
760    /// Returns an error if the resulting unified address would not include at least one shielded
761    /// receiver. A unified address containing only a transparent receiver cannot be requested
762    /// this way; use [`Self::TRANSPARENT_ONLY`], which is applicable only to keys that have no
763    /// shielded item at all.
764    pub fn new(
765        orchard: ReceiverRequirement,
766        sapling: ReceiverRequirement,
767        p2pkh: ReceiverRequirement,
768    ) -> Result<Self, ReceiverRequirementError> {
769        if orchard == Omit && sapling == Omit {
770            Err(ReceiverRequirementError::NoShieldedReceiver)
771        } else {
772            Ok(Self {
773                orchard,
774                sapling,
775                p2pkh,
776            })
777        }
778    }
779
780    /// Constructs a new unified address request that allows a receiver of each type.
781    pub const ALLOW_ALL: ReceiverRequirements = { Self::unsafe_new(Allow, Allow, Allow) };
782
783    /// Constructs a new unified address request that allows only shielded receivers.
784    pub const SHIELDED: ReceiverRequirements = { Self::unsafe_new(Allow, Allow, Omit) };
785
786    /// Constructs a new unified address request that requires an Orchard receiver, and no others.
787    pub const ORCHARD: ReceiverRequirements = { Self::unsafe_new(Require, Omit, Omit) };
788
789    /// Constructs a new unified address request that requires a transparent P2PKH receiver, and
790    /// no others.
791    ///
792    /// An address satisfying these requirements has no shielded receiver, and so is
793    /// representable only as a [ZIP 316] Revision 2 transparent-including (`tu`) unified
794    /// address. Consequently this is the appropriate request only for a key that has no
795    /// shielded item; requesting it from a key that has one produces an address that
796    /// needlessly discards the recipient's shielded receiving capability. It is not reachable
797    /// via [`Self::new`] or [`Self::unsafe_new`] for that reason.
798    ///
799    /// [ZIP 316]: https://zips.z.cash/zip-0316
800    pub const TRANSPARENT_ONLY: ReceiverRequirements = Self {
801        orchard: Omit,
802        sapling: Omit,
803        p2pkh: Require,
804    };
805
806    /// Constructs a new unified address request that includes only the receivers that are allowed
807    /// both in itself and a given other request. Returns an error if requirements are incompatible
808    /// or if no shielded receiver type is allowed.
809    ///
810    /// Intersection preserves [`Omit`], so its result permits no shielded receiver only if at
811    /// least one of the operands already permitted none. In that case the result is no less
812    /// honest than the operand it came from and is returned; otherwise the shielded-receiver
813    /// requirement of [`Self::new`] applies.
814    ///
815    /// This operation is commutative and idempotent, but it is not associative. The rule above
816    /// inspects the operands and not only the meet, so an intermediate result that permits no
817    /// shielded receiver can be rejected under one bracketing and admitted under another.
818    ///
819    /// [`Omit`]: ReceiverRequirement::Omit
820    pub fn intersect(
821        &self,
822        other: &ReceiverRequirements,
823    ) -> Result<ReceiverRequirements, ReceiverRequirementError> {
824        let orchard = self.orchard.intersect(other.orchard)?;
825        let sapling = self.sapling.intersect(other.sapling)?;
826        let p2pkh = self.p2pkh.intersect(other.p2pkh)?;
827        match (orchard, sapling, p2pkh) {
828            (Omit, Omit, Require) if self.is_transparent_only() || other.is_transparent_only() => {
829                Ok(Self::TRANSPARENT_ONLY)
830            }
831            _ => Self::new(orchard, sapling, p2pkh),
832        }
833    }
834
835    /// Construct a new unified address request from its constituent parts.
836    ///
837    /// Panics: at least one of `orchard` or `sapling` must be allowed. Use
838    /// [`Self::TRANSPARENT_ONLY`] to describe a key that has no shielded item.
839    pub const fn unsafe_new(
840        orchard: ReceiverRequirement,
841        sapling: ReceiverRequirement,
842        p2pkh: ReceiverRequirement,
843    ) -> Self {
844        if matches!(orchard, Omit) && matches!(sapling, Omit) {
845            panic!("At least one shielded receiver must be allowed.")
846        }
847
848        Self {
849            orchard,
850            sapling,
851            p2pkh,
852        }
853    }
854
855    /// Returns whether these requirements permit no shielded receiver.
856    fn is_transparent_only(&self) -> bool {
857        self.orchard == Omit && self.sapling == Omit
858    }
859
860    /// Returns the [`ReceiverRequirement`] for inclusion of an Orchard receiver.
861    pub fn orchard(&self) -> ReceiverRequirement {
862        self.orchard
863    }
864
865    /// Returns the [`ReceiverRequirement`] for inclusion of a Sapling receiver.
866    pub fn sapling(&self) -> ReceiverRequirement {
867        self.sapling
868    }
869
870    /// Returns the [`ReceiverRequirement`] for inclusion of a P2PKH receiver.
871    pub fn p2pkh(&self) -> ReceiverRequirement {
872        self.p2pkh
873    }
874}
875
876#[cfg(feature = "transparent-inputs")]
877impl From<bip32::Error> for DerivationError {
878    fn from(e: bip32::Error) -> Self {
879        DerivationError::Transparent(e)
880    }
881}
882
883/// A key that provides the capability to recover outgoing transaction information from
884/// the block chain.
885#[derive(Clone, Copy)]
886pub struct OutgoingViewingKey([u8; 32]);
887
888impl core::fmt::Debug for OutgoingViewingKey {
889    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
890        f.debug_tuple("OutgoingViewingKey").field(&"...").finish()
891    }
892}
893
894impl From<[u8; 32]> for OutgoingViewingKey {
895    fn from(ovk: [u8; 32]) -> Self {
896        OutgoingViewingKey(ovk)
897    }
898}
899
900#[cfg(feature = "sapling")]
901impl From<OutgoingViewingKey> for ::sapling::keys::OutgoingViewingKey {
902    fn from(value: OutgoingViewingKey) -> Self {
903        ::sapling::keys::OutgoingViewingKey(value.0)
904    }
905}
906
907#[cfg(feature = "sapling")]
908impl From<::sapling::keys::OutgoingViewingKey> for OutgoingViewingKey {
909    fn from(value: ::sapling::keys::OutgoingViewingKey) -> Self {
910        OutgoingViewingKey(value.0)
911    }
912}
913
914#[cfg(feature = "orchard")]
915impl From<OutgoingViewingKey> for ::orchard::keys::OutgoingViewingKey {
916    fn from(value: OutgoingViewingKey) -> Self {
917        ::orchard::keys::OutgoingViewingKey::from(value.0)
918    }
919}
920
921#[cfg(feature = "orchard")]
922impl From<::orchard::keys::OutgoingViewingKey> for OutgoingViewingKey {
923    fn from(value: ::orchard::keys::OutgoingViewingKey) -> Self {
924        OutgoingViewingKey(*value.as_ref())
925    }
926}
927
928impl AsRef<[u8; 32]> for OutgoingViewingKey {
929    fn as_ref(&self) -> &[u8; 32] {
930        &self.0
931    }
932}
933
934/// A [ZIP 316](https://zips.z.cash/zip-0316) unified full viewing key.
935#[derive(Clone)]
936pub struct UnifiedFullViewingKey {
937    #[cfg(feature = "transparent-inputs")]
938    transparent: Option<TransparentFvk>,
939    #[cfg(feature = "sapling")]
940    sapling: Option<sapling::DiversifiableFullViewingKey>,
941    #[cfg(feature = "orchard")]
942    orchard: Option<orchard::keys::FullViewingKey>,
943    unknown: Vec<(u32, Vec<u8>)>,
944    expiry_height: Option<consensus::BlockHeight>,
945    expiry_time: Option<u64>,
946    unknown_metadata: Vec<(u32, Vec<u8>)>,
947}
948
949/// The transparent component of a [`UnifiedFullViewingKey`].
950///
951/// ZIP 316 permits a unified viewing key to contain at most one transparent item, and
952/// requires consumers to reject a key that carries both a P2PKH and a P2SH item.
953#[cfg(feature = "transparent-inputs")]
954#[derive(Clone, PartialEq, Eq)]
955enum TransparentFvk {
956    P2pkh(::transparent::keys::AccountPubKey),
957    P2sh(P2shFullViewingKey),
958}
959
960/// The transparent component of a [`UnifiedIncomingViewingKey`].
961///
962/// ZIP 316 permits a unified viewing key to contain at most one transparent item, and
963/// requires consumers to reject a key that carries both a P2PKH and a P2SH item.
964#[cfg(feature = "transparent-inputs")]
965#[derive(Clone, PartialEq, Eq)]
966enum TransparentIvk {
967    P2pkh(::transparent::keys::ExternalIvk),
968    P2sh(P2shIncomingViewingKey),
969}
970
971impl core::fmt::Debug for UnifiedFullViewingKey {
972    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
973        let mut d = f.debug_struct("UnifiedFullViewingKey");
974        #[cfg(feature = "transparent-inputs")]
975        d.field("p2pkh", &self.p2pkh().map(|_| "..."));
976        #[cfg(feature = "transparent-inputs")]
977        d.field("p2sh", &self.p2sh());
978        #[cfg(feature = "sapling")]
979        d.field("sapling", &self.sapling.as_ref().map(|_| "..."));
980        #[cfg(feature = "orchard")]
981        d.field("orchard", &self.orchard.as_ref().map(|_| "..."));
982        d.field(
983            "unknown_typecodes",
984            &self
985                .unknown
986                .iter()
987                .map(|(typecode, _)| *typecode)
988                .collect::<Vec<_>>(),
989        )
990        .field("expiry_height", &self.expiry_height)
991        .field("expiry_time", &self.expiry_time)
992        .field(
993            "unknown_metadata_typecodes",
994            &self
995                .unknown_metadata
996                .iter()
997                .map(|(typecode, _)| *typecode)
998                .collect::<Vec<_>>(),
999        )
1000        .finish()
1001    }
1002}
1003
1004impl UnifiedFullViewingKey {
1005    /// Construct a new unified full viewing key.
1006    ///
1007    /// This method is only available when the `test-dependencies` feature is enabled,
1008    /// as derivation from the USK or deserialization from the serialized form should
1009    /// be used instead.
1010    #[cfg(any(test, feature = "test-dependencies"))]
1011    pub fn new(
1012        #[cfg(feature = "transparent-inputs")] transparent: Option<
1013            ::transparent::keys::AccountPubKey,
1014        >,
1015        #[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
1016        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
1017        // TODO: Implement construction of UFVKs with metadata items.
1018    ) -> Result<UnifiedFullViewingKey, DerivationError> {
1019        Self::from_checked_parts(
1020            #[cfg(feature = "transparent-inputs")]
1021            transparent.map(TransparentFvk::P2pkh),
1022            #[cfg(feature = "sapling")]
1023            sapling,
1024            #[cfg(feature = "orchard")]
1025            orchard,
1026            vec![],
1027            None,
1028            None,
1029            vec![],
1030        )
1031    }
1032
1033    #[cfg(feature = "unstable-frost")]
1034    pub fn from_orchard_fvk(
1035        orchard: orchard::keys::FullViewingKey,
1036    ) -> Result<UnifiedFullViewingKey, DerivationError> {
1037        Self::from_checked_parts(
1038            #[cfg(feature = "transparent-inputs")]
1039            None,
1040            #[cfg(feature = "sapling")]
1041            None,
1042            #[cfg(feature = "orchard")]
1043            Some(orchard),
1044            vec![],
1045            None,
1046            None,
1047            vec![],
1048        )
1049    }
1050
1051    #[cfg(all(feature = "sapling", feature = "unstable"))]
1052    pub fn from_sapling_extended_full_viewing_key(
1053        sapling: ExtendedFullViewingKey,
1054    ) -> Result<UnifiedFullViewingKey, DerivationError> {
1055        Self::from_checked_parts(
1056            #[cfg(feature = "transparent-inputs")]
1057            None,
1058            #[cfg(feature = "sapling")]
1059            Some(sapling.to_diversifiable_full_viewing_key()),
1060            #[cfg(feature = "orchard")]
1061            None,
1062            vec![],
1063            None,
1064            None,
1065            vec![],
1066        )
1067    }
1068
1069    /// Constructs a unified full viewing key for a transparent-only account defined by a
1070    /// [ZIP 316] Revision 2 P2SH viewing key item.
1071    ///
1072    /// This is the one unified full viewing key that cannot be derived. Every other kind
1073    /// is reached through [`UnifiedSpendingKey::to_unified_full_viewing_key`], because a
1074    /// seed determines it; a P2SH account is instead defined by a set of cosigners'
1075    /// keys, so it has no spending key to derive from and must be assembled.
1076    ///
1077    /// The resulting key has no shielded components, so it encodes with the `tu` HRP and
1078    /// derives transparent-only unified addresses.
1079    ///
1080    /// [ZIP 316]: https://zips.z.cash/zip-0316
1081    #[cfg(feature = "transparent-inputs")]
1082    pub fn from_p2sh(p2sh: P2shFullViewingKey) -> Result<UnifiedFullViewingKey, DerivationError> {
1083        Self::from_checked_parts(
1084            Some(TransparentFvk::P2sh(p2sh)),
1085            #[cfg(feature = "sapling")]
1086            None,
1087            #[cfg(feature = "orchard")]
1088            None,
1089            vec![],
1090            None,
1091            None,
1092            vec![],
1093        )
1094    }
1095
1096    /// Construct a UFVK from its constituent parts, after verifying that UIVK derivation can
1097    /// succeed.
1098    #[allow(clippy::too_many_arguments)]
1099    fn from_checked_parts(
1100        #[cfg(feature = "transparent-inputs")] transparent: Option<TransparentFvk>,
1101        #[cfg(feature = "sapling")] sapling: Option<sapling::DiversifiableFullViewingKey>,
1102        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::FullViewingKey>,
1103        unknown: Vec<(u32, Vec<u8>)>,
1104        expiry_height: Option<consensus::BlockHeight>,
1105        expiry_time: Option<u64>,
1106        unknown_metadata: Vec<(u32, Vec<u8>)>,
1107    ) -> Result<UnifiedFullViewingKey, DerivationError> {
1108        // Verify that the incoming viewing key form of the transparent component is
1109        // derivable; we don't want to construct a UFVK that can't derive transparent
1110        // addresses.
1111        #[cfg(feature = "transparent-inputs")]
1112        match transparent.as_ref() {
1113            Some(TransparentFvk::P2pkh(k)) => {
1114                let _ = k.derive_external_ivk()?;
1115            }
1116            Some(TransparentFvk::P2sh(p)) => {
1117                let _ = p.to_incoming_viewing_key()?;
1118            }
1119            None => {}
1120        }
1121
1122        Ok(UnifiedFullViewingKey {
1123            #[cfg(feature = "transparent-inputs")]
1124            transparent,
1125            #[cfg(feature = "sapling")]
1126            sapling,
1127            #[cfg(feature = "orchard")]
1128            orchard,
1129            unknown,
1130            expiry_height,
1131            expiry_time,
1132            unknown_metadata,
1133        })
1134    }
1135
1136    /// Parses a `UnifiedFullViewingKey` from its [ZIP 316] string encoding at any
1137    /// revision.
1138    ///
1139    /// # Errors
1140    ///
1141    /// - [`UnifiedDecodingError::Parse`] if the string is not a valid UFVK.
1142    /// - [`UnifiedDecodingError::NetworkMismatch`] if the key is for a network other than
1143    ///   that of `params`.
1144    /// - [`UnifiedDecodingError::InvalidItem`] if an item's data is not a valid key of its
1145    ///   type.
1146    ///
1147    /// [ZIP 316]: https://zips.z.cash/zip-0316
1148    pub fn decode<P: consensus::Parameters>(
1149        params: &P,
1150        encoding: &str,
1151    ) -> Result<Self, UnifiedDecodingError> {
1152        let (actual, _revision, ufvk) =
1153            unified::Ufvk::decode(encoding).map_err(UnifiedDecodingError::Parse)?;
1154        let expected = params.network_type();
1155        if actual != expected {
1156            return Err(UnifiedDecodingError::NetworkMismatch { expected, actual });
1157        }
1158
1159        Self::from_container(&ufvk).map_err(UnifiedDecodingError::InvalidItem)
1160    }
1161
1162    /// Constructs a `UnifiedFullViewingKey` from a parsed [ZIP 316] unified container.
1163    ///
1164    /// Returns [`DecodingError::KeyDataInvalid`] if an item's data is not a valid key of
1165    /// its type.
1166    ///
1167    /// [ZIP 316]: https://zips.z.cash/zip-0316
1168    pub fn parse(ufvk: &unified::Ufvk) -> Result<Self, DecodingError> {
1169        Self::from_container(ufvk).map_err(DecodingError::KeyDataInvalid)
1170    }
1171
1172    /// Constructs a `UnifiedFullViewingKey` from a parsed unified container.
1173    ///
1174    /// On error, returns the typecode of the first item whose data is not a valid key of
1175    /// its type.
1176    fn from_container(ufvk: &unified::Ufvk) -> Result<Self, Typecode> {
1177        #[cfg(feature = "orchard")]
1178        let mut orchard = None;
1179        #[cfg(feature = "sapling")]
1180        let mut sapling = None;
1181        #[cfg(feature = "transparent-inputs")]
1182        let mut transparent = None;
1183        let mut unknown = vec![];
1184        let mut expiry_height = None;
1185        let mut expiry_time = None;
1186        let mut unknown_metadata = vec![];
1187
1188        for item in ufvk.items_as_parsed() {
1189            match item {
1190                Uitem::Data(unified::Fvk::Orchard(data)) => {
1191                    #[cfg(feature = "orchard")]
1192                    {
1193                        orchard = Some(
1194                            orchard::keys::FullViewingKey::from_bytes(data)
1195                                .ok_or(Typecode::ORCHARD)?,
1196                        );
1197                    }
1198                    #[cfg(not(feature = "orchard"))]
1199                    unknown.push((u32::from(unified::Typecode::ORCHARD), data.to_vec()));
1200                }
1201                Uitem::Data(unified::Fvk::Sapling(data)) => {
1202                    #[cfg(feature = "sapling")]
1203                    {
1204                        sapling = Some(
1205                            sapling::DiversifiableFullViewingKey::from_bytes(data)
1206                                .ok_or(Typecode::SAPLING)?,
1207                        );
1208                    }
1209                    #[cfg(not(feature = "sapling"))]
1210                    unknown.push((u32::from(unified::Typecode::SAPLING), data.to_vec()));
1211                }
1212                Uitem::Data(unified::Fvk::P2pkh(data)) => {
1213                    #[cfg(feature = "transparent-inputs")]
1214                    {
1215                        transparent = Some(TransparentFvk::P2pkh(
1216                            ::transparent::keys::AccountPubKey::deserialize(data)
1217                                .map_err(|_| Typecode::P2PKH)?,
1218                        ));
1219                    }
1220                    #[cfg(not(feature = "transparent-inputs"))]
1221                    unknown.push((u32::from(unified::Typecode::P2PKH), data.to_vec()));
1222                }
1223                Uitem::Data(unified::Fvk::P2sh(data)) => {
1224                    #[cfg(feature = "transparent-inputs")]
1225                    {
1226                        transparent = Some(TransparentFvk::P2sh(
1227                            P2shFullViewingKey::parse(data).map_err(|_| Typecode::P2SH)?,
1228                        ));
1229                    }
1230                    #[cfg(not(feature = "transparent-inputs"))]
1231                    unknown.push((u32::from(unified::Typecode::P2SH), data.to_vec()));
1232                }
1233                Uitem::Data(unified::Fvk::Unknown { typecode, data }) => {
1234                    unknown.push((*typecode, data.clone()));
1235                }
1236                Uitem::Metadata(MetadataItem::ExpiryHeight(h)) => {
1237                    expiry_height = Some(consensus::BlockHeight::from_u32(*h));
1238                }
1239                Uitem::Metadata(MetadataItem::ExpiryTime(t)) => {
1240                    expiry_time = Some(*t);
1241                }
1242                Uitem::Metadata(MetadataItem::Unknown { typecode, data }) => {
1243                    unknown_metadata.push((*typecode, data.clone()));
1244                }
1245            }
1246        }
1247
1248        // Only the derivation of the transparent item's incoming viewing key can fail.
1249        #[cfg(feature = "transparent-inputs")]
1250        let transparent_typecode = match transparent {
1251            Some(TransparentFvk::P2sh(_)) => Typecode::P2SH,
1252            _ => Typecode::P2PKH,
1253        };
1254        #[cfg(not(feature = "transparent-inputs"))]
1255        let transparent_typecode = Typecode::P2PKH;
1256
1257        Self::from_checked_parts(
1258            #[cfg(feature = "transparent-inputs")]
1259            transparent,
1260            #[cfg(feature = "sapling")]
1261            sapling,
1262            #[cfg(feature = "orchard")]
1263            orchard,
1264            unknown,
1265            expiry_height,
1266            expiry_time,
1267            unknown_metadata,
1268        )
1269        .map_err(|_| transparent_typecode)
1270    }
1271
1272    /// Returns the [ZIP 316] string encoding of this `UnifiedFullViewingKey` for the given
1273    /// network at the given revision.
1274    ///
1275    /// Returns [`UnifiedEncodingError::NotRepresentable`] if this key has no encoding at
1276    /// `revision`: [`Revision::R0`] cannot encode expiry metadata, a P2SH viewing key
1277    /// item, or a key whose only data items are transparent.
1278    ///
1279    /// [ZIP 316]: https://zips.z.cash/zip-0316
1280    pub fn encode_revision<P: consensus::Parameters>(
1281        &self,
1282        params: &P,
1283        revision: Revision,
1284    ) -> Result<String, UnifiedEncodingError> {
1285        self.to_ufvk(revision)
1286            .map(|ufvk| ufvk.encode(&params.network_type()))
1287    }
1288
1289    /// Returns the [ZIP 316] string encoding of this `UnifiedFullViewingKey` for the given
1290    /// network at the most widely supported revision that can represent it:
1291    /// [`Revision::R0`] if possible, and [`Revision::R2`] otherwise. Use
1292    /// [`encode_revision`](Self::encode_revision) to encode at a specific revision.
1293    ///
1294    /// Returns [`UnifiedEncodingError::NotRepresentable`] if this key has no encoding at
1295    /// either revision.
1296    ///
1297    /// [ZIP 316]: https://zips.z.cash/zip-0316
1298    pub fn encode<P: consensus::Parameters>(
1299        &self,
1300        params: &P,
1301    ) -> Result<String, UnifiedEncodingError> {
1302        self.encode_revision(params, Revision::R0)
1303            .or_else(|_| self.encode_revision(params, Revision::R2))
1304    }
1305
1306    /// Converts this key to a unified container at the given revision.
1307    fn to_ufvk(&self, revision: Revision) -> Result<unified::Ufvk, UnifiedEncodingError> {
1308        let data_items = core::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
1309            unified::Fvk::Unknown {
1310                typecode: *typecode,
1311                data: data.clone(),
1312            }
1313        }));
1314        #[cfg(feature = "orchard")]
1315        let data_items = data_items.chain(
1316            self.orchard
1317                .as_ref()
1318                .map(|fvk| fvk.to_bytes())
1319                .map(unified::Fvk::Orchard),
1320        );
1321        #[cfg(feature = "sapling")]
1322        let data_items = data_items.chain(
1323            self.sapling
1324                .as_ref()
1325                .map(|dfvk| dfvk.to_bytes())
1326                .map(unified::Fvk::Sapling),
1327        );
1328        #[cfg(feature = "transparent-inputs")]
1329        let data_items = data_items.chain(self.transparent.as_ref().map(|t| match t {
1330            TransparentFvk::P2pkh(k) => {
1331                unified::Fvk::P2pkh(k.serialize().try_into().expect("key is 65 bytes"))
1332            }
1333            TransparentFvk::P2sh(p) => unified::Fvk::P2sh(p.to_bytes()),
1334        }));
1335
1336        let meta_items = core::iter::empty()
1337            .chain(self.unknown_metadata.iter().map(|(typecode, data)| {
1338                unified::MetadataItem::Unknown {
1339                    typecode: *typecode,
1340                    data: data.clone(),
1341                }
1342            }))
1343            .chain(
1344                self.expiry_height
1345                    .map(|h| unified::MetadataItem::ExpiryHeight(u32::from(h))),
1346            )
1347            .chain(self.expiry_time.map(unified::MetadataItem::ExpiryTime));
1348
1349        unified::Ufvk::try_from_items(
1350            revision,
1351            data_items
1352                .map(Uitem::Data)
1353                .chain(meta_items.map(Uitem::Metadata))
1354                .collect(),
1355        )
1356        .map_err(|cause| UnifiedEncodingError::NotRepresentable { revision, cause })
1357    }
1358
1359    /// Derives a Unified Incoming Viewing Key from this Unified Full Viewing Key.
1360    pub fn to_unified_incoming_viewing_key(&self) -> UnifiedIncomingViewingKey {
1361        UnifiedIncomingViewingKey {
1362            #[cfg(feature = "transparent-inputs")]
1363            transparent: self.transparent.as_ref().map(|t| match t {
1364                TransparentFvk::P2pkh(k) => TransparentIvk::P2pkh(
1365                    k.derive_external_ivk()
1366                        .expect("Transparent IVK derivation was checked at construction."),
1367                ),
1368                TransparentFvk::P2sh(p) => TransparentIvk::P2sh(
1369                    p.to_incoming_viewing_key()
1370                        .expect("P2SH IVK derivation was checked at construction."),
1371                ),
1372            }),
1373            #[cfg(feature = "sapling")]
1374            sapling: self.sapling.as_ref().map(|s| s.to_external_ivk()),
1375            #[cfg(feature = "orchard")]
1376            orchard: self.orchard.as_ref().map(|o| o.to_ivk(Scope::External)),
1377            expiry_height: self.expiry_height,
1378            expiry_time: self.expiry_time,
1379            // An item that this build cannot interpret has no derivable incoming viewing key
1380            // form, so it cannot be carried over. Record its typecode: the derived key is a
1381            // lossy description of the account, and callers that describe the account from it
1382            // must not present it as complete.
1383            unknown: Vec::new(),
1384            unconverted_typecodes: self.unknown.iter().map(|(typecode, _)| *typecode).collect(),
1385            unknown_metadata: vec![],
1386        }
1387    }
1388
1389    /// Returns the P2PKH component of the unified key at the
1390    /// BIP44 path `m/44'/<coin_type>'/<account>'`, if present.
1391    #[cfg(feature = "transparent-inputs")]
1392    #[deprecated(note = "Use `UnifiedFullViewingKey::p2pkh` instead.")]
1393    pub fn transparent(&self) -> Option<&::transparent::keys::AccountPubKey> {
1394        self.p2pkh()
1395    }
1396
1397    /// Returns the P2PKH component of the unified key at the
1398    /// BIP44 path `m/44'/<coin_type>'/<account>'`, if present.
1399    ///
1400    /// Returns `None` if the key has no transparent component, or carries a P2SH viewing
1401    /// key item instead; the two are mutually exclusive.
1402    #[cfg(feature = "transparent-inputs")]
1403    pub fn p2pkh(&self) -> Option<&::transparent::keys::AccountPubKey> {
1404        match self.transparent.as_ref() {
1405            Some(TransparentFvk::P2pkh(k)) => Some(k),
1406            Some(TransparentFvk::P2sh(_)) | None => None,
1407        }
1408    }
1409
1410    /// Returns the P2SH viewing key item of this unified key, if present.
1411    ///
1412    /// Returns `None` if the key has no transparent component, or carries a P2PKH
1413    /// viewing key instead; the two are mutually exclusive.
1414    #[cfg(feature = "transparent-inputs")]
1415    pub fn p2sh(&self) -> Option<&P2shFullViewingKey> {
1416        match self.transparent.as_ref() {
1417            Some(TransparentFvk::P2sh(p)) => Some(p),
1418            Some(TransparentFvk::P2pkh(_)) | None => None,
1419        }
1420    }
1421
1422    /// Returns the Sapling diversifiable full viewing key component of this unified key.
1423    #[cfg(feature = "sapling")]
1424    pub fn sapling(&self) -> Option<&sapling::DiversifiableFullViewingKey> {
1425        self.sapling.as_ref()
1426    }
1427
1428    /// Returns the Orchard full viewing key component of this unified key.
1429    #[cfg(feature = "orchard")]
1430    pub fn orchard(&self) -> Option<&orchard::keys::FullViewingKey> {
1431        self.orchard.as_ref()
1432    }
1433
1434    /// Returns whether this key has a Sapling component.
1435    pub fn has_sapling(&self) -> bool {
1436        #[cfg(feature = "sapling")]
1437        return self.sapling.is_some();
1438        #[cfg(not(feature = "sapling"))]
1439        return false;
1440    }
1441
1442    /// Returns whether this key has an Orchard component.
1443    pub fn has_orchard(&self) -> bool {
1444        #[cfg(feature = "orchard")]
1445        return self.orchard.is_some();
1446        #[cfg(not(feature = "orchard"))]
1447        return false;
1448    }
1449
1450    /// Returns the expiry height metadata for this key, if present.
1451    pub fn expiry_height(&self) -> Option<consensus::BlockHeight> {
1452        self.expiry_height
1453    }
1454
1455    /// Returns the expiry time metadata for this key, if present.
1456    pub fn expiry_time(&self) -> Option<u64> {
1457        self.expiry_time
1458    }
1459
1460    /// Returns `true` if this UFVK subsumes the given UIVK: every IVK item present in
1461    /// `other` has a matching item derivable from `self`. The UFVK may have additional
1462    /// items representing capabilities not present in the UIVK.
1463    pub fn subsumes_uivk(&self, other: &UnifiedIncomingViewingKey) -> bool {
1464        self.to_unified_incoming_viewing_key().subsumes(other)
1465    }
1466
1467    /// Returns `true` if this UFVK subsumes `other`: every FVK item in `other` has a
1468    /// matching item in `self`, including both incoming and outgoing viewing capability.
1469    ///
1470    /// For unknown items, exact equality of (typecode, data) is required.
1471    pub fn subsumes_ufvk(&self, other: &UnifiedFullViewingKey) -> bool {
1472        #[cfg(feature = "orchard")]
1473        match (&other.orchard, &self.orchard) {
1474            (Some(e), Some(n)) if e != n => {
1475                return false;
1476            }
1477            (Some(_), None) => return false,
1478            _ => {}
1479        }
1480
1481        #[cfg(feature = "sapling")]
1482        match (&other.sapling, &self.sapling) {
1483            (Some(e), Some(n)) if e != n => {
1484                return false;
1485            }
1486            (Some(_), None) => return false,
1487            _ => {}
1488        }
1489
1490        #[cfg(feature = "transparent-inputs")]
1491        match (&other.transparent, &self.transparent) {
1492            (Some(e), Some(n)) if e != n => {
1493                return false;
1494            }
1495            (Some(_), None) => return false,
1496            _ => {}
1497        }
1498
1499        // Every unknown item in `other` must have an identical entry in `self`.
1500        for item in &other.unknown {
1501            if !self.unknown.contains(item) {
1502                return false;
1503            }
1504        }
1505
1506        true
1507    }
1508
1509    /// Returns `true` if this UFVK and `other` carry the same viewing capability: each
1510    /// [subsumes](Self::subsumes_ufvk) the other. Metadata items are not compared.
1511    pub fn is_equivalent_to(&self, other: &UnifiedFullViewingKey) -> bool {
1512        self.subsumes_ufvk(other) && other.subsumes_ufvk(self)
1513    }
1514
1515    /// Attempts to derive the Unified Address for the given diversifier index and receiver types.
1516    ///
1517    /// Returns `None` if the specified index does not produce a valid diversifier.
1518    pub fn address(
1519        &self,
1520        j: DiversifierIndex,
1521        request: UnifiedAddressRequest,
1522    ) -> Result<UnifiedAddress, AddressGenerationError> {
1523        self.to_unified_incoming_viewing_key().address(j, request)
1524    }
1525
1526    /// Searches the diversifier space starting at diversifier index `j` for one which will produce
1527    /// a valid diversifier, and return the Unified Address constructed using that diversifier
1528    /// along with the index at which the valid diversifier was found.
1529    ///
1530    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
1531    /// required to satisfy the unified address request are not properly enabled.
1532    pub fn find_address(
1533        &self,
1534        j: DiversifierIndex,
1535        request: UnifiedAddressRequest,
1536    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1537        self.to_unified_incoming_viewing_key()
1538            .find_address(j, request)
1539    }
1540
1541    /// Find the Unified Address corresponding to the smallest valid diversifier index, along with
1542    /// that index.
1543    ///
1544    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
1545    /// required to satisfy the unified address request are not properly enabled.
1546    pub fn default_address(
1547        &self,
1548        request: UnifiedAddressRequest,
1549    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1550        self.find_address(DiversifierIndex::new(), request)
1551    }
1552
1553    /// Returns the default external transparent address using the transparent account pubkey.
1554    ///
1555    /// See [`ExternalIvk::default_address`] for more information.
1556    ///
1557    /// [`ExternalIvk::default_address`]: ::transparent::keys::ExternalIvk::default_address
1558    #[cfg(all(
1559        feature = "transparent-inputs",
1560        any(test, feature = "test-dependencies")
1561    ))]
1562    pub fn default_transparent_address(
1563        &self,
1564    ) -> Option<(TransparentAddress, NonHardenedChildIndex)> {
1565        self.p2pkh().map(|k| {
1566            k.derive_external_ivk()
1567                .expect("ability to derive the external IVK was checked at construction")
1568                .default_address()
1569        })
1570    }
1571
1572    /// Selects an outgoing viewing key for a transaction's outputs based upon the type(s) of
1573    /// inputs spent in the transaction.
1574    ///
1575    /// This method selects the OVK of the highest-preference item in [ZIP 316] preference order
1576    /// from among the item types represented by the provided input types. It will return `None` in
1577    /// the case that this FVK does not contain any items corresponding to the provided input
1578    /// sources; this should ordinarily never occur because in order to spend an input, a spending
1579    /// key of the appropriate type must be available and that spending key should be represented
1580    /// among this UFVK's items.
1581    pub fn select_ovk(
1582        &self,
1583        scope: zip32::Scope,
1584        input_sources: &NonEmpty<PoolType>,
1585    ) -> Option<OutgoingViewingKey> {
1586        #[cfg(feature = "orchard")]
1587        if let Some(ovk) = input_sources
1588            .contains(&PoolType::ORCHARD)
1589            .then(|| {
1590                self.orchard()
1591                    .map(|k| OutgoingViewingKey::from(k.to_ovk(scope)))
1592            })
1593            .flatten()
1594        {
1595            return Some(ovk);
1596        };
1597
1598        #[cfg(feature = "sapling")]
1599        if let Some(ovk) = input_sources
1600            .contains(&PoolType::SAPLING)
1601            .then(|| {
1602                self.sapling()
1603                    .map(|k| OutgoingViewingKey::from(k.to_ovk(scope)))
1604            })
1605            .flatten()
1606        {
1607            return Some(ovk);
1608        }
1609
1610        #[cfg(feature = "transparent-inputs")]
1611        if let Some(ovk) = input_sources
1612            .contains(&PoolType::Transparent)
1613            .then(|| {
1614                self.p2pkh().map(|k| {
1615                    OutgoingViewingKey(match scope {
1616                        zip32::Scope::External => k.external_ovk().as_bytes(),
1617                        zip32::Scope::Internal => k.internal_ovk().as_bytes(),
1618                    })
1619                })
1620            })
1621            .flatten()
1622        {
1623            return Some(ovk);
1624        }
1625
1626        None
1627    }
1628}
1629
1630/// A [ZIP 316](https://zips.z.cash/zip-0316) unified incoming viewing key.
1631#[derive(Clone)]
1632pub struct UnifiedIncomingViewingKey {
1633    #[cfg(feature = "transparent-inputs")]
1634    transparent: Option<TransparentIvk>,
1635    #[cfg(feature = "sapling")]
1636    sapling: Option<::sapling::zip32::IncomingViewingKey>,
1637    #[cfg(feature = "orchard")]
1638    orchard: Option<orchard::keys::IncomingViewingKey>,
1639    unknown: Vec<(u32, Vec<u8>)>,
1640    /// The typecodes of the data items of the full viewing key that this key was derived from
1641    /// that could not be carried over, because this build cannot interpret them.
1642    ///
1643    /// These items are not part of this key's encoding, and take no part in equality or
1644    /// subsumption. They record only that the account has receiving capability that this key
1645    /// does not describe.
1646    unconverted_typecodes: Vec<u32>,
1647    expiry_height: Option<consensus::BlockHeight>,
1648    expiry_time: Option<u64>,
1649    unknown_metadata: Vec<(u32, Vec<u8>)>,
1650}
1651
1652impl core::fmt::Debug for UnifiedIncomingViewingKey {
1653    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1654        let mut d = f.debug_struct("UnifiedIncomingViewingKey");
1655        #[cfg(feature = "transparent-inputs")]
1656        d.field("p2pkh", &self.p2pkh().map(|_| "..."));
1657        #[cfg(feature = "transparent-inputs")]
1658        d.field("p2sh", &self.p2sh());
1659        #[cfg(feature = "sapling")]
1660        d.field("sapling", &self.sapling.as_ref().map(|_| "..."));
1661        #[cfg(feature = "orchard")]
1662        d.field("orchard", &self.orchard.as_ref().map(|_| "..."));
1663        d.field(
1664            "unknown_typecodes",
1665            &self
1666                .unknown
1667                .iter()
1668                .map(|(typecode, _)| *typecode)
1669                .collect::<Vec<_>>(),
1670        )
1671        .field("unconverted_typecodes", &self.unconverted_typecodes)
1672        .field("expiry_height", &self.expiry_height)
1673        .field("expiry_time", &self.expiry_time)
1674        .field(
1675            "unknown_metadata_typecodes",
1676            &self
1677                .unknown_metadata
1678                .iter()
1679                .map(|(typecode, _)| *typecode)
1680                .collect::<Vec<_>>(),
1681        )
1682        .finish()
1683    }
1684}
1685
1686impl PartialEq for UnifiedIncomingViewingKey {
1687    fn eq(&self, other: &Self) -> bool {
1688        self.subsumes(other) && other.subsumes(self)
1689    }
1690}
1691
1692impl Eq for UnifiedIncomingViewingKey {}
1693
1694impl UnifiedIncomingViewingKey {
1695    /// Construct a new unified incoming viewing key.
1696    ///
1697    /// This method is only available when the `test-dependencies` feature is enabled,
1698    /// as derivation from the UFVK or deserialization from the serialized form should
1699    /// be used instead.
1700    #[cfg(any(test, feature = "test-dependencies"))]
1701    pub fn new(
1702        #[cfg(feature = "transparent-inputs")] transparent: Option<
1703            ::transparent::keys::ExternalIvk,
1704        >,
1705        #[cfg(feature = "sapling")] sapling: Option<::sapling::zip32::IncomingViewingKey>,
1706        #[cfg(feature = "orchard")] orchard: Option<orchard::keys::IncomingViewingKey>,
1707        unknown: Vec<(u32, Vec<u8>)>,
1708        expiry_height: Option<consensus::BlockHeight>,
1709        expiry_time: Option<u64>,
1710        unknown_metadata: Vec<(u32, Vec<u8>)>,
1711    ) -> UnifiedIncomingViewingKey {
1712        UnifiedIncomingViewingKey {
1713            #[cfg(feature = "transparent-inputs")]
1714            transparent: transparent.map(TransparentIvk::P2pkh),
1715            #[cfg(feature = "sapling")]
1716            sapling,
1717            #[cfg(feature = "orchard")]
1718            orchard,
1719            unknown,
1720            unconverted_typecodes: vec![],
1721            expiry_height,
1722            expiry_time,
1723            unknown_metadata,
1724        }
1725    }
1726
1727    /// Parses a `UnifiedIncomingViewingKey` from its [ZIP 316] string encoding at any
1728    /// revision.
1729    ///
1730    /// # Errors
1731    ///
1732    /// - [`UnifiedDecodingError::Parse`] if the string is not a valid UIVK.
1733    /// - [`UnifiedDecodingError::NetworkMismatch`] if the key is for a network other than
1734    ///   that of `params`.
1735    /// - [`UnifiedDecodingError::InvalidItem`] if an item's data is not a valid key of its
1736    ///   type.
1737    ///
1738    /// [ZIP 316]: https://zips.z.cash/zip-0316
1739    pub fn decode<P: consensus::Parameters>(
1740        params: &P,
1741        encoding: &str,
1742    ) -> Result<Self, UnifiedDecodingError> {
1743        let (actual, _revision, uivk) =
1744            unified::Uivk::decode(encoding).map_err(UnifiedDecodingError::Parse)?;
1745        let expected = params.network_type();
1746        if actual != expected {
1747            return Err(UnifiedDecodingError::NetworkMismatch { expected, actual });
1748        }
1749
1750        Self::from_container(&uivk).map_err(UnifiedDecodingError::InvalidItem)
1751    }
1752
1753    /// Constructs a `UnifiedIncomingViewingKey` from a parsed unified container.
1754    ///
1755    /// On error, returns the typecode of the first item whose data is not a valid key of
1756    /// its type.
1757    fn from_container(uivk: &unified::Uivk) -> Result<Self, Typecode> {
1758        #[cfg(feature = "orchard")]
1759        let mut orchard = None;
1760        #[cfg(feature = "sapling")]
1761        let mut sapling = None;
1762        #[cfg(feature = "transparent-inputs")]
1763        let mut transparent = None;
1764        let mut unknown = vec![];
1765        let mut expiry_height = None;
1766        let mut expiry_time = None;
1767        let mut unknown_metadata = vec![];
1768
1769        for item in uivk.items_as_parsed() {
1770            match item {
1771                Uitem::Data(unified::Ivk::Orchard(data)) => {
1772                    #[cfg(feature = "orchard")]
1773                    {
1774                        orchard = Some(
1775                            Option::from(orchard::keys::IncomingViewingKey::from_bytes(data))
1776                                .ok_or(Typecode::ORCHARD)?,
1777                        );
1778                    }
1779                    #[cfg(not(feature = "orchard"))]
1780                    unknown.push((u32::from(unified::Typecode::ORCHARD), data.to_vec()));
1781                }
1782                Uitem::Data(unified::Ivk::Sapling(data)) => {
1783                    #[cfg(feature = "sapling")]
1784                    {
1785                        sapling = Some(
1786                            Option::from(::sapling::zip32::IncomingViewingKey::from_bytes(data))
1787                                .ok_or(Typecode::SAPLING)?,
1788                        );
1789                    }
1790                    #[cfg(not(feature = "sapling"))]
1791                    unknown.push((u32::from(unified::Typecode::SAPLING), data.to_vec()));
1792                }
1793                Uitem::Data(unified::Ivk::P2pkh(data)) => {
1794                    #[cfg(feature = "transparent-inputs")]
1795                    {
1796                        transparent = Some(TransparentIvk::P2pkh(
1797                            ::transparent::keys::ExternalIvk::deserialize(data)
1798                                .map_err(|_| Typecode::P2PKH)?,
1799                        ));
1800                    }
1801                    #[cfg(not(feature = "transparent-inputs"))]
1802                    unknown.push((u32::from(unified::Typecode::P2PKH), data.to_vec()));
1803                }
1804                Uitem::Data(unified::Ivk::P2sh(data)) => {
1805                    #[cfg(feature = "transparent-inputs")]
1806                    {
1807                        transparent = Some(TransparentIvk::P2sh(
1808                            P2shIncomingViewingKey::parse(data).map_err(|_| Typecode::P2SH)?,
1809                        ));
1810                    }
1811                    #[cfg(not(feature = "transparent-inputs"))]
1812                    unknown.push((u32::from(unified::Typecode::P2SH), data.to_vec()));
1813                }
1814                Uitem::Data(unified::Ivk::Unknown { typecode, data }) => {
1815                    unknown.push((*typecode, data.clone()));
1816                }
1817                Uitem::Metadata(MetadataItem::ExpiryHeight(h)) => {
1818                    expiry_height = Some(consensus::BlockHeight::from_u32(*h));
1819                }
1820                Uitem::Metadata(MetadataItem::ExpiryTime(t)) => {
1821                    expiry_time = Some(*t);
1822                }
1823                Uitem::Metadata(MetadataItem::Unknown { typecode, data }) => {
1824                    unknown_metadata.push((*typecode, data.clone()));
1825                }
1826            }
1827        }
1828
1829        Ok(Self {
1830            #[cfg(feature = "transparent-inputs")]
1831            transparent,
1832            #[cfg(feature = "sapling")]
1833            sapling,
1834            #[cfg(feature = "orchard")]
1835            orchard,
1836            unknown,
1837            // A parsed key keeps every item that it cannot interpret in `unknown`, and so
1838            // describes itself completely.
1839            unconverted_typecodes: vec![],
1840            expiry_height,
1841            expiry_time,
1842            unknown_metadata,
1843        })
1844    }
1845
1846    /// Returns the [ZIP 316] string encoding of this `UnifiedIncomingViewingKey` for the
1847    /// given network at the given revision.
1848    ///
1849    /// Returns [`UnifiedEncodingError::NotRepresentable`] if this key has no encoding at
1850    /// `revision`: [`Revision::R0`] cannot encode expiry metadata, a P2SH viewing key
1851    /// item, or a key whose only data items are transparent.
1852    ///
1853    /// [ZIP 316]: https://zips.z.cash/zip-0316
1854    pub fn encode_revision<P: consensus::Parameters>(
1855        &self,
1856        params: &P,
1857        revision: Revision,
1858    ) -> Result<String, UnifiedEncodingError> {
1859        self.render(revision)
1860            .map(|uivk| uivk.encode(&params.network_type()))
1861    }
1862
1863    /// Returns the [ZIP 316] string encoding of this `UnifiedIncomingViewingKey` for the
1864    /// given network at the most widely supported revision that can represent it:
1865    /// [`Revision::R0`] if possible, and [`Revision::R2`] otherwise. Use
1866    /// [`encode_revision`](Self::encode_revision) to encode at a specific revision.
1867    ///
1868    /// Returns [`UnifiedEncodingError::NotRepresentable`] if this key has no encoding at
1869    /// either revision.
1870    ///
1871    /// [ZIP 316]: https://zips.z.cash/zip-0316
1872    pub fn encode<P: consensus::Parameters>(
1873        &self,
1874        params: &P,
1875    ) -> Result<String, UnifiedEncodingError> {
1876        self.encode_revision(params, Revision::R0)
1877            .or_else(|_| self.encode_revision(params, Revision::R2))
1878    }
1879
1880    /// Converts this unified incoming viewing key to a unified container at the given
1881    /// revision.
1882    fn render(&self, revision: Revision) -> Result<unified::Uivk, UnifiedEncodingError> {
1883        let data_items = core::iter::empty().chain(self.unknown.iter().map(|(typecode, data)| {
1884            unified::Ivk::Unknown {
1885                typecode: *typecode,
1886                data: data.clone(),
1887            }
1888        }));
1889        #[cfg(feature = "orchard")]
1890        let data_items = data_items.chain(
1891            self.orchard
1892                .as_ref()
1893                .map(|ivk| ivk.to_bytes())
1894                .map(unified::Ivk::Orchard),
1895        );
1896        #[cfg(feature = "sapling")]
1897        let data_items = data_items.chain(
1898            self.sapling
1899                .as_ref()
1900                .map(|divk| divk.to_bytes())
1901                .map(unified::Ivk::Sapling),
1902        );
1903        #[cfg(feature = "transparent-inputs")]
1904        let data_items = data_items.chain(self.transparent.as_ref().map(|t| match t {
1905            TransparentIvk::P2pkh(k) => {
1906                unified::Ivk::P2pkh(k.serialize().try_into().expect("key is 65 bytes"))
1907            }
1908            TransparentIvk::P2sh(p) => unified::Ivk::P2sh(p.to_bytes()),
1909        }));
1910
1911        let meta_items = core::iter::empty()
1912            .chain(self.unknown_metadata.iter().map(|(typecode, data)| {
1913                unified::MetadataItem::Unknown {
1914                    typecode: *typecode,
1915                    data: data.clone(),
1916                }
1917            }))
1918            .chain(
1919                self.expiry_height
1920                    .map(|h| unified::MetadataItem::ExpiryHeight(u32::from(h))),
1921            )
1922            .chain(self.expiry_time.map(unified::MetadataItem::ExpiryTime));
1923
1924        unified::Uivk::try_from_items(
1925            revision,
1926            data_items
1927                .map(Uitem::Data)
1928                .chain(meta_items.map(Uitem::Metadata))
1929                .collect(),
1930        )
1931        .map_err(|cause| UnifiedEncodingError::NotRepresentable { revision, cause })
1932    }
1933
1934    /// Returns whether this uivk has a transparent key item.
1935    ///
1936    /// This method is available irrespective of whether the `transparent-inputs` feature flag is enabled.
1937    pub fn has_transparent(&self) -> bool {
1938        #[cfg(not(feature = "transparent-inputs"))]
1939        return false;
1940        #[cfg(feature = "transparent-inputs")]
1941        return self.transparent.is_some();
1942    }
1943
1944    /// Returns the transparent external IVK, if present.
1945    #[cfg(feature = "transparent-inputs")]
1946    #[deprecated(note = "Use `UnifiedIncomingViewingKey::p2pkh` instead.")]
1947    pub fn transparent(&self) -> Option<&::transparent::keys::ExternalIvk> {
1948        self.p2pkh()
1949    }
1950
1951    /// Returns the transparent external IVK, if present.
1952    ///
1953    /// Returns `None` if the key has no transparent component, or carries a P2SH viewing
1954    /// key item instead; the two are mutually exclusive.
1955    #[cfg(feature = "transparent-inputs")]
1956    pub fn p2pkh(&self) -> Option<&::transparent::keys::ExternalIvk> {
1957        match self.transparent.as_ref() {
1958            Some(TransparentIvk::P2pkh(k)) => Some(k),
1959            Some(TransparentIvk::P2sh(_)) | None => None,
1960        }
1961    }
1962
1963    /// Returns the P2SH viewing key item of this key, if present.
1964    ///
1965    /// Returns `None` if the key has no transparent component, or carries a P2PKH
1966    /// viewing key instead; the two are mutually exclusive.
1967    #[cfg(feature = "transparent-inputs")]
1968    pub fn p2sh(&self) -> Option<&P2shIncomingViewingKey> {
1969        match self.transparent.as_ref() {
1970            Some(TransparentIvk::P2sh(p)) => Some(p),
1971            Some(TransparentIvk::P2pkh(_)) | None => None,
1972        }
1973    }
1974
1975    /// Returns whether this uivk has a Sapling key item.
1976    ///
1977    /// This method is available irrespective of whether the `sapling` feature flag is enabled.
1978    pub fn has_sapling(&self) -> bool {
1979        #[cfg(not(feature = "sapling"))]
1980        return false;
1981        #[cfg(feature = "sapling")]
1982        return self.sapling.is_some();
1983    }
1984
1985    /// Returns the Sapling IVK, if present.
1986    #[cfg(feature = "sapling")]
1987    pub fn sapling(&self) -> &Option<::sapling::zip32::IncomingViewingKey> {
1988        &self.sapling
1989    }
1990
1991    /// Returns whether this uivk has an Orchard key item.
1992    ///
1993    /// This method is available irrespective of whether the `orchard` feature flag is enabled.
1994    pub fn has_orchard(&self) -> bool {
1995        #[cfg(not(feature = "orchard"))]
1996        return false;
1997        #[cfg(feature = "orchard")]
1998        return self.orchard.is_some();
1999    }
2000
2001    /// Returns the Orchard IVK, if present.
2002    #[cfg(feature = "orchard")]
2003    pub fn orchard(&self) -> &Option<orchard::keys::IncomingViewingKey> {
2004        &self.orchard
2005    }
2006
2007    /// Returns the expiry height metadata for this key, if present.
2008    pub fn expiry_height(&self) -> Option<consensus::BlockHeight> {
2009        self.expiry_height
2010    }
2011
2012    /// Returns the expiry time metadata for this key, if present.
2013    pub fn expiry_time(&self) -> Option<u64> {
2014        self.expiry_time
2015    }
2016
2017    /// Returns `true` if this UIVK subsumes `other`: every IVK item present in `other`
2018    /// has a matching item in `self`. This key may have additional items representing
2019    /// capabilities not present in `other`.
2020    ///
2021    /// For unknown items, exact equality of (typecode, data) is required. Future
2022    /// revisions may apply more specific semantics for known metadata typecodes.
2023    pub fn subsumes(&self, other: &UnifiedIncomingViewingKey) -> bool {
2024        #[cfg(feature = "orchard")]
2025        match (other.orchard(), &self.orchard) {
2026            (Some(e), Some(n)) if e != n => {
2027                return false;
2028            }
2029            (Some(_), None) => return false,
2030            _ => {}
2031        }
2032
2033        #[cfg(feature = "sapling")]
2034        match (other.sapling(), &self.sapling) {
2035            (Some(e), Some(n)) if e != n => {
2036                return false;
2037            }
2038            (Some(_), None) => return false,
2039            _ => {}
2040        }
2041
2042        #[cfg(feature = "transparent-inputs")]
2043        match (&other.transparent, &self.transparent) {
2044            (Some(e), Some(n)) if e != n => {
2045                return false;
2046            }
2047            (Some(_), None) => return false,
2048            _ => {}
2049        }
2050
2051        // Every unknown item in `other` must have an identical entry in `self`.
2052        for item in &other.unknown {
2053            if !self.unknown.contains(item) {
2054                return false;
2055            }
2056        }
2057
2058        true
2059    }
2060
2061    /// Attempts to derive the Unified Address for the given diversifier index and receiver types.
2062    ///
2063    /// Returns an error if the this key does not produce a valid receiver for a required receiver
2064    /// type at the given diversifier index.
2065    pub fn address(
2066        &self,
2067        _j: DiversifierIndex,
2068        request: UnifiedAddressRequest,
2069    ) -> Result<UnifiedAddress, AddressGenerationError> {
2070        let request = self
2071            .receiver_requirements(request)
2072            .map_err(|_| AddressGenerationError::NoSatisfiableReceiver)?;
2073
2074        // If we need to generate a transparent receiver, check that the user has not
2075        // specified an invalid transparent child index, from which we can never search to
2076        // find a valid index.
2077        #[cfg(feature = "transparent-inputs")]
2078        if request.p2pkh == ReceiverRequirement::Require
2079            && self.transparent.is_some()
2080            && to_transparent_child_index(_j).is_none()
2081        {
2082            return Err(AddressGenerationError::InvalidTransparentChildIndex(_j));
2083        }
2084
2085        #[cfg(feature = "orchard")]
2086        let mut orchard = None;
2087        if request.orchard != Omit {
2088            #[cfg(not(feature = "orchard"))]
2089            if request.orchard == Require {
2090                return Err(AddressGenerationError::ReceiverTypeNotSupported(
2091                    Typecode::ORCHARD,
2092                ));
2093            }
2094
2095            #[cfg(feature = "orchard")]
2096            if let Some(oivk) = &self.orchard {
2097                let orchard_j = orchard::keys::DiversifierIndex::from(*_j.as_bytes());
2098                orchard = Some(oivk.address_at(orchard_j))
2099            } else if request.orchard == Require {
2100                return Err(AddressGenerationError::KeyNotAvailable(Typecode::ORCHARD));
2101            }
2102        }
2103
2104        #[cfg(feature = "sapling")]
2105        let mut sapling = None;
2106        if request.sapling != Omit {
2107            #[cfg(not(feature = "sapling"))]
2108            if request.sapling == Require {
2109                return Err(AddressGenerationError::ReceiverTypeNotSupported(
2110                    Typecode::SAPLING,
2111                ));
2112            }
2113
2114            #[cfg(feature = "sapling")]
2115            if let Some(divk) = &self.sapling {
2116                // If a Sapling receiver type is requested, we must be able to construct an
2117                // address; if we're unable to do so, then no Unified Address exists at this
2118                // diversifier and we use `?` to early-return from this method.
2119                sapling = match (request.sapling, divk.address_at(_j)) {
2120                    (Require | Allow, Some(addr)) => Ok(Some(addr)),
2121                    (Require, None) => {
2122                        Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_j))
2123                    }
2124                    _ => Ok(None),
2125                }?;
2126            } else if request.sapling == Require {
2127                return Err(AddressGenerationError::KeyNotAvailable(Typecode::SAPLING));
2128            }
2129        }
2130
2131        #[cfg(feature = "transparent-inputs")]
2132        let mut transparent = None;
2133        if request.p2pkh != Omit {
2134            #[cfg(not(feature = "transparent-inputs"))]
2135            if request.p2pkh == Require {
2136                return Err(AddressGenerationError::ReceiverTypeNotSupported(
2137                    Typecode::P2PKH,
2138                ));
2139            }
2140
2141            #[cfg(feature = "transparent-inputs")]
2142            if let Some(tivk) = self.transparent.as_ref() {
2143                // If a transparent receiver type is requested, we must be able to construct an
2144                // address; if we're unable to do so, then no Unified Address exists at this
2145                // diversifier.
2146                let j = to_transparent_child_index(_j);
2147                let derived = match tivk {
2148                    TransparentIvk::P2pkh(k) => j.and_then(|j| k.derive_address(j).ok()),
2149                    TransparentIvk::P2sh(p) => j.map(|j| p.derive_address(j).0),
2150                };
2151
2152                transparent = match (request.p2pkh, derived) {
2153                    (Require | Allow, Some(addr)) => Ok(Some(addr)),
2154                    (Require, None) => {
2155                        Err(AddressGenerationError::InvalidTransparentChildIndex(_j))
2156                    }
2157                    _ => Ok(None),
2158                }?;
2159            } else if request.p2pkh == Require {
2160                return Err(AddressGenerationError::KeyNotAvailable(Typecode::P2PKH));
2161            }
2162        }
2163        #[cfg(not(feature = "transparent-inputs"))]
2164        let transparent = None;
2165
2166        UnifiedAddress::from_receivers(
2167            #[cfg(feature = "orchard")]
2168            orchard,
2169            #[cfg(feature = "sapling")]
2170            sapling,
2171            transparent,
2172            self.expiry_height,
2173            self.expiry_time,
2174        )
2175        .ok_or(AddressGenerationError::NoSatisfiableReceiver)
2176    }
2177
2178    /// Searches the diversifier space starting at diversifier index `j` for one which will produce
2179    /// a valid address that conforms to the provided request, and returns that Unified Address
2180    /// along with the index at which the valid diversifier was found.
2181    ///
2182    /// If [`None`] is specified for the `request` parameter, a default request that [`Require`]s a
2183    /// receiver be present for each key item enabled by the feature flags in use will be used to
2184    /// search the diversifier space.
2185    ///
2186    /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features
2187    /// required to satisfy the unified address request are not enabled.
2188    ///
2189    /// [`Require`]: ReceiverRequirement::Require
2190    #[allow(unused_mut)]
2191    pub fn find_address(
2192        &self,
2193        mut j: DiversifierIndex,
2194        request: UnifiedAddressRequest,
2195    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
2196        // Only a Sapling receiver can reject a diversifier index, so only that configuration
2197        // has to search.
2198        #[cfg(feature = "sapling")]
2199        loop {
2200            match self.address(j, request) {
2201                Ok(ua) => return Ok((ua, j)),
2202                Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => {
2203                    if j.increment().is_err() {
2204                        return Err(AddressGenerationError::DiversifierSpaceExhausted);
2205                    }
2206                }
2207                Err(other) => return Err(other),
2208            }
2209        }
2210
2211        #[cfg(not(feature = "sapling"))]
2212        self.address(j, request).map(|ua| (ua, j))
2213    }
2214
2215    /// Find the Unified Address corresponding to the smallest valid diversifier index, along with
2216    /// that index.
2217    ///
2218    /// Returns an error if the this key does not produce a valid receiver for a required receiver
2219    /// type at any diversifier index.
2220    pub fn default_address(
2221        &self,
2222        request: UnifiedAddressRequest,
2223    ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
2224        self.find_address(DiversifierIndex::new(), request)
2225    }
2226
2227    /// Attempts to recover a diversifier index for each of the receivers of the given
2228    /// [`UnifiedAddress`].
2229    ///
2230    /// Returns the empty set if no shielded receiver of `ua` can be attributed to this key.
2231    /// Transparent receivers are not considered here, as recovering a diversifier index from a
2232    /// transparent receiver alone is not possible without additional context.
2233    pub fn decrypt_diversifiers(&self, ua: &UnifiedAddress) -> BTreeSet<DiversifierIndex> {
2234        #[cfg(not(any(feature = "sapling", feature = "orchard")))]
2235        let _ = ua;
2236
2237        #[cfg(not(feature = "sapling"))]
2238        let sapling_di: Option<DiversifierIndex> = None;
2239
2240        #[cfg(feature = "sapling")]
2241        let sapling_di = ua
2242            .sapling()
2243            .zip(self.sapling().as_ref())
2244            .and_then(|(receiver, ivk)| ivk.decrypt_diversifier(receiver));
2245
2246        #[cfg(not(feature = "orchard"))]
2247        let orchard_di: Option<DiversifierIndex> = None;
2248
2249        #[cfg(feature = "orchard")]
2250        let orchard_di = ua
2251            .orchard()
2252            .zip(self.orchard().as_ref())
2253            .and_then(|(receiver, ivk)| ivk.diversifier_index(receiver));
2254
2255        sapling_di.into_iter().chain(orchard_di).collect()
2256    }
2257
2258    /// Convenience method for choosing a set of receiver requirements based upon the given unified
2259    /// address request and the available items of this key.
2260    ///
2261    /// Returns an error if the provided request cannot be satisfied in address generation using
2262    /// this key.
2263    pub fn receiver_requirements(
2264        &self,
2265        request: UnifiedAddressRequest,
2266    ) -> Result<ReceiverRequirements, AddressGenerationError> {
2267        match request {
2268            UnifiedAddressRequest::AllAvailableKeys => self
2269                .to_receiver_requirements()
2270                .map_err(|_| AddressGenerationError::NoSatisfiableReceiver),
2271            UnifiedAddressRequest::Custom(req) => {
2272                if req.orchard() == Require && !self.has_orchard() {
2273                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
2274                        Typecode::ORCHARD,
2275                    ));
2276                }
2277
2278                if req.sapling() == Require && !self.has_sapling() {
2279                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
2280                        Typecode::SAPLING,
2281                    ));
2282                }
2283
2284                if req.p2pkh() == Require && !self.has_transparent() {
2285                    return Err(AddressGenerationError::ReceiverTypeNotSupported(
2286                        Typecode::P2PKH,
2287                    ));
2288                }
2289
2290                Ok(req)
2291            }
2292        }
2293    }
2294
2295    /// Returns whether this key has a data item that this build cannot interpret.
2296    ///
2297    /// An item of a pool whose feature is disabled and an item of a pool that postdates this
2298    /// crate are both uninterpretable, and either may be a shielded receiver.
2299    fn has_uninterpretable_data_item(&self) -> bool {
2300        !self.unknown.is_empty() || !self.unconverted_typecodes.is_empty()
2301    }
2302
2303    /// Constructs the [`ReceiverRequirements`] that requires a receiver for each data item of this UIVK.
2304    ///
2305    /// A key that has nothing but a transparent item describes a transparent-only account, and
2306    /// yields [`ReceiverRequirements::TRANSPARENT_ONLY`]; addresses generated from it are
2307    /// representable as [ZIP 316] Revision 2 transparent-including (`tu`) unified addresses.
2308    ///
2309    /// Returns an error if this key has no item for which a receiver can be derived, and also
2310    /// if the only receiver it can derive is transparent but the key has a data item that this
2311    /// build cannot interpret. A metadata item never prevents address generation.
2312    ///
2313    /// [ZIP 316]: https://zips.z.cash/zip-0316
2314    pub fn to_receiver_requirements(
2315        &self,
2316    ) -> Result<ReceiverRequirements, ReceiverRequirementError> {
2317        let orchard = if self.has_orchard() { Require } else { Omit };
2318        let sapling = if self.has_sapling() { Require } else { Omit };
2319        let p2pkh = if self.has_transparent() {
2320            Require
2321        } else {
2322            Omit
2323        };
2324
2325        match (orchard, sapling, p2pkh) {
2326            // Omitting the shielded receivers is the honest description of a key that has
2327            // nothing but a transparent item, so `ReceiverRequirements::new`'s
2328            // shielded-receiver requirement, which guards against callers discarding a key's
2329            // shielded capability, does not apply here.
2330            //
2331            // `TRANSPARENT_ONLY` silently omits receivers, so it is sound only for a key that
2332            // provably has nothing but transparent. A data item that this build cannot
2333            // interpret defeats that proof: the item may be a shielded receiver, and a
2334            // transparent-only address would discard the recipient's shielded receiving
2335            // capability. Fall through instead, and let the ordinary error surface.
2336            (Omit, Omit, Require) if !self.has_uninterpretable_data_item() => {
2337                Ok(ReceiverRequirements::TRANSPARENT_ONLY)
2338            }
2339            _ => ReceiverRequirements::new(orchard, sapling, p2pkh),
2340        }
2341    }
2342
2343    /// Returns the default external transparent address using the transparent account pubkey.
2344    ///
2345    /// See [`ExternalIvk::default_address`] for more information.
2346    ///
2347    /// [`ExternalIvk::default_address`]: ::transparent::keys::ExternalIvk::default_address
2348    #[cfg(all(
2349        feature = "transparent-inputs",
2350        any(test, feature = "test-dependencies")
2351    ))]
2352    pub fn default_transparent_address(
2353        &self,
2354    ) -> Option<(TransparentAddress, NonHardenedChildIndex)> {
2355        self.p2pkh().map(|k| k.default_address())
2356    }
2357}
2358
2359#[cfg(any(test, feature = "test-dependencies"))]
2360pub mod testing {
2361    use proptest::prelude::*;
2362
2363    use super::UnifiedSpendingKey;
2364    use zcash_protocol::consensus::Network;
2365    use zip32::AccountId;
2366
2367    pub fn arb_unified_spending_key(params: Network) -> impl Strategy<Value = UnifiedSpendingKey> {
2368        prop::array::uniform32(prop::num::u8::ANY).prop_flat_map(move |seed| {
2369            prop::num::u32::ANY
2370                .prop_map(move |account| {
2371                    UnifiedSpendingKey::from_seed(
2372                        &params,
2373                        &seed,
2374                        AccountId::try_from(account & ((1 << 31) - 1)).unwrap(),
2375                    )
2376                })
2377                .prop_filter("seeds must generate valid USKs", |v| v.is_ok())
2378                .prop_map(|v| v.unwrap())
2379        })
2380    }
2381}
2382
2383#[cfg(test)]
2384mod tests {
2385    use proptest::prelude::proptest;
2386    use secp256k1::{PublicKey, SecretKey};
2387    use zcash_encoding::CompactSize;
2388
2389    #[cfg(all(
2390        feature = "transparent-inputs",
2391        any(feature = "orchard", feature = "sapling")
2392    ))]
2393    use {
2394        super::ReceiverRequirement::*,
2395        crate::{address::Address, keys::UnifiedAddressRequest},
2396        zcash_address::test_vectors,
2397        zip32::DiversifierIndex,
2398    };
2399
2400    #[cfg(feature = "transparent-inputs")]
2401    use {
2402        crate::encoding::AddressCodec,
2403        ::transparent::keys::{AccountPrivKey, IncomingViewingKey, NonHardenedChildIndex},
2404        alloc::{string::ToString, vec::Vec},
2405        zcash_address::unified::Revision,
2406    };
2407
2408    use zcash_protocol::consensus::MAIN_NETWORK;
2409    use zip32::AccountId;
2410
2411    #[cfg(any(feature = "sapling", feature = "orchard"))]
2412    use super::{UnifiedFullViewingKey, UnifiedIncomingViewingKey};
2413
2414    #[cfg(all(
2415        feature = "orchard",
2416        feature = "sapling",
2417        feature = "transparent-inputs"
2418    ))]
2419    use {
2420        crate::{
2421            address::UnifiedAddress,
2422            encoding::{UnifiedDecodingError, UnifiedEncodingError},
2423        },
2424        assert_matches::assert_matches,
2425        zcash_address::unified::{self, Encoding},
2426        zcash_protocol::consensus::{NetworkType, TEST_NETWORK},
2427    };
2428
2429    #[cfg(feature = "orchard")]
2430    use zip32::Scope;
2431
2432    #[cfg(feature = "unstable")]
2433    use secrecy::ExposeSecret;
2434
2435    #[cfg(feature = "zeroize")]
2436    use zeroize::ZeroizeOnDrop;
2437
2438    #[cfg(feature = "sapling")]
2439    use super::sapling;
2440
2441    // `UnifiedSpendingKey` is used both by the `unstable` serialization tests and by the
2442    // derivation tests, which are enabled by a different set of features.
2443    #[cfg(any(
2444        feature = "unstable",
2445        all(
2446            feature = "transparent-inputs",
2447            any(feature = "orchard", feature = "sapling")
2448        )
2449    ))]
2450    use super::UnifiedSpendingKey;
2451
2452    #[cfg(feature = "unstable")]
2453    use super::{Era, testing::arb_unified_spending_key};
2454
2455    #[cfg(all(feature = "orchard", feature = "unstable"))]
2456    use subtle::ConstantTimeEq;
2457
2458    #[cfg(feature = "transparent-inputs")]
2459    fn seed() -> Vec<u8> {
2460        let seed_hex = "6ef5f84def6f4b9d38f466586a8380a38593bd47c8cda77f091856176da47f26b5bd1c8d097486e5635df5a66e820d28e1d73346f499801c86228d43f390304f";
2461        hex::decode(seed_hex).unwrap()
2462    }
2463
2464    #[test]
2465    #[should_panic]
2466    #[cfg(feature = "sapling")]
2467    fn spending_key_panics_on_short_seed() {
2468        let _ = sapling::spending_key(&[0; 31][..], 0, AccountId::ZERO);
2469    }
2470
2471    #[cfg(feature = "transparent-inputs")]
2472    #[test]
2473    fn pk_to_taddr() {
2474        let taddr = AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO)
2475            .unwrap()
2476            .to_account_pubkey()
2477            .derive_external_ivk()
2478            .unwrap()
2479            .derive_address(NonHardenedChildIndex::ZERO)
2480            .unwrap()
2481            .encode(&MAIN_NETWORK);
2482        assert_eq!(taddr, "t1PKtYdJJHhc3Pxowmznkg7vdTwnhEsCvR4".to_string());
2483    }
2484
2485    #[test]
2486    #[cfg(all(
2487        feature = "orchard",
2488        feature = "sapling",
2489        feature = "transparent-inputs"
2490    ))]
2491    fn viewing_key_decode_reports_structured_errors() {
2492        use super::test_vectors::unified_viewing_keys_r2::TEST_VECTORS;
2493
2494        let tv = &TEST_VECTORS[0];
2495        let mismatch = Err(UnifiedDecodingError::NetworkMismatch {
2496            expected: NetworkType::Test,
2497            actual: NetworkType::Main,
2498        });
2499        assert_eq!(
2500            UnifiedFullViewingKey::decode(&TEST_NETWORK, tv.unified_fvk).map(|_| ()),
2501            mismatch
2502        );
2503        assert_eq!(
2504            UnifiedIncomingViewingKey::decode(&TEST_NETWORK, tv.unified_ivk).map(|_| ()),
2505            mismatch
2506        );
2507
2508        assert_matches!(
2509            UnifiedFullViewingKey::decode(&MAIN_NETWORK, "not a key"),
2510            Err(UnifiedDecodingError::Parse(_))
2511        );
2512        assert_matches!(
2513            UnifiedIncomingViewingKey::decode(&MAIN_NETWORK, "not a key"),
2514            Err(UnifiedDecodingError::Parse(_))
2515        );
2516    }
2517
2518    /// Re-encoding a key at another revision yields an equivalent key, and a key that lacks
2519    /// one of another key's items is not equivalent to it.
2520    #[test]
2521    #[cfg(all(
2522        feature = "orchard",
2523        feature = "sapling",
2524        feature = "transparent-inputs"
2525    ))]
2526    fn ufvk_equivalence_is_mutual_subsumption() {
2527        let usk = UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::ZERO)
2528            .expect("the seed yields a valid spending key");
2529        let ufvk = usk.to_unified_full_viewing_key();
2530
2531        for revision in [Revision::R0, Revision::R2] {
2532            let reencoded = ufvk.encode_revision(&MAIN_NETWORK, revision).unwrap();
2533            let decoded = UnifiedFullViewingKey::decode(&MAIN_NETWORK, &reencoded).unwrap();
2534            assert!(decoded.is_equivalent_to(&ufvk));
2535            assert!(ufvk.is_equivalent_to(&decoded));
2536        }
2537
2538        let sapling_only = UnifiedFullViewingKey::new(None, ufvk.sapling().cloned(), None).unwrap();
2539        assert!(ufvk.subsumes_ufvk(&sapling_only));
2540        assert!(!ufvk.is_equivalent_to(&sapling_only));
2541        assert!(!sapling_only.is_equivalent_to(&ufvk));
2542    }
2543
2544    /// A viewing key has a Revision 0 encoding exactly when it has a shielded item and no
2545    /// item that Revision 0 does not define (expiry metadata or a P2SH viewing key item).
2546    /// That encoding decodes to the same key, and is the compatible encoding; otherwise
2547    /// the compatible encoding is the Revision 2 encoding.
2548    #[test]
2549    #[cfg(all(
2550        feature = "orchard",
2551        feature = "sapling",
2552        feature = "transparent-inputs"
2553    ))]
2554    fn viewing_keys_encode_at_r0_iff_representable() {
2555        use super::test_vectors::unified_viewing_keys_r2::TEST_VECTORS;
2556
2557        let mut representable = 0;
2558        let mut unrepresentable = 0;
2559        for tv in TEST_VECTORS {
2560            let r0_representable = tv.expiry_height.is_none()
2561                && tv.expiry_time.is_none()
2562                && tv.p2sh_fvk_bytes.is_none()
2563                && (tv.sapling_fvk_bytes.is_some() || tv.orchard_fvk_bytes.is_some());
2564
2565            let ufvk = UnifiedFullViewingKey::decode(&MAIN_NETWORK, tv.unified_fvk).unwrap();
2566            let uivk = UnifiedIncomingViewingKey::decode(&MAIN_NETWORK, tv.unified_ivk).unwrap();
2567            let ufvk_r0 = ufvk.encode_revision(&MAIN_NETWORK, Revision::R0);
2568            let uivk_r0 = uivk.encode_revision(&MAIN_NETWORK, Revision::R0);
2569            let ufvk_compatible = ufvk.encode(&MAIN_NETWORK).unwrap();
2570            let uivk_compatible = uivk.encode(&MAIN_NETWORK).unwrap();
2571
2572            if r0_representable {
2573                representable += 1;
2574
2575                let ufvk_r0 = ufvk_r0.unwrap();
2576                assert_eq!(ufvk_compatible, ufvk_r0);
2577                assert!(
2578                    ufvk_r0
2579                        .starts_with(zcash_address::constants::hrp_unified_fvk(NetworkType::Main))
2580                );
2581                assert_eq!(
2582                    UnifiedFullViewingKey::decode(&MAIN_NETWORK, &ufvk_r0)
2583                        .unwrap()
2584                        .encode_revision(&MAIN_NETWORK, Revision::R2)
2585                        .as_deref(),
2586                    Ok(tv.unified_fvk),
2587                );
2588
2589                let uivk_r0 = uivk_r0.unwrap();
2590                assert_eq!(uivk_compatible, uivk_r0);
2591                assert!(
2592                    uivk_r0
2593                        .starts_with(zcash_address::constants::hrp_unified_ivk(NetworkType::Main))
2594                );
2595                assert_eq!(
2596                    UnifiedIncomingViewingKey::decode(&MAIN_NETWORK, &uivk_r0)
2597                        .unwrap()
2598                        .encode_revision(&MAIN_NETWORK, Revision::R2)
2599                        .as_deref(),
2600                    Ok(tv.unified_ivk),
2601                );
2602            } else {
2603                unrepresentable += 1;
2604                assert_eq!(ufvk_compatible, tv.unified_fvk);
2605                assert_eq!(uivk_compatible, tv.unified_ivk);
2606                assert_matches!(
2607                    ufvk_r0,
2608                    Err(UnifiedEncodingError::NotRepresentable {
2609                        revision: Revision::R0,
2610                        ..
2611                    })
2612                );
2613                assert_matches!(
2614                    uivk_r0,
2615                    Err(UnifiedEncodingError::NotRepresentable {
2616                        revision: Revision::R0,
2617                        ..
2618                    })
2619                );
2620            }
2621        }
2622        assert!(representable > 0 && unrepresentable > 0);
2623    }
2624
2625    /// Tests UFVK encoding round-trip and verification against R2 test vectors from
2626    /// https://github.com/zcash/zcash-test-vectors/blob/667c92954acd7defc6e60e25b022fedf8831dfb3/test-vectors/rust/unified_viewing_keys_r2.rs
2627    #[test]
2628    #[cfg(all(
2629        feature = "orchard",
2630        feature = "sapling",
2631        feature = "transparent-inputs"
2632    ))]
2633    fn ufvk_round_trip() {
2634        use super::test_vectors::unified_viewing_keys_r2::TEST_VECTORS;
2635
2636        for tv in TEST_VECTORS {
2637            let ufvk =
2638                UnifiedFullViewingKey::decode(&MAIN_NETWORK, tv.unified_fvk).unwrap_or_else(|e| {
2639                    panic!("Failed to decode UFVK for account {}: {e}", tv.account)
2640                });
2641
2642            let reencoded = ufvk.encode_revision(&MAIN_NETWORK, Revision::R2);
2643            assert_eq!(
2644                reencoded.as_deref(),
2645                Ok(tv.unified_fvk),
2646                "UFVK round-trip failed for account {}",
2647                tv.account
2648            );
2649
2650            assert_eq!(
2651                ufvk.expiry_height(),
2652                tv.expiry_height
2653                    .map(zcash_protocol::consensus::BlockHeight::from_u32),
2654                "expiry height mismatch for account {}",
2655                tv.account
2656            );
2657            assert_eq!(
2658                ufvk.expiry_time(),
2659                tv.expiry_time,
2660                "expiry time mismatch for account {}",
2661                tv.account
2662            );
2663
2664            // Verify key data matches the test vector bytes.
2665            if let Some(ref expected) = tv.t_p2pkh_fvk_bytes {
2666                let actual = ufvk
2667                    .p2pkh()
2668                    .expect("transparent key present in test vector")
2669                    .serialize();
2670                assert_eq!(
2671                    &actual[..],
2672                    &expected[..],
2673                    "transparent FVK mismatch for account {}",
2674                    tv.account
2675                );
2676            }
2677
2678            if let Some(ref expected) = tv.sapling_fvk_bytes {
2679                let actual = ufvk
2680                    .sapling()
2681                    .expect("sapling key present in test vector")
2682                    .to_bytes();
2683                assert_eq!(
2684                    &actual[..],
2685                    &expected[..],
2686                    "sapling FVK mismatch for account {}",
2687                    tv.account
2688                );
2689            }
2690
2691            if let Some(ref expected) = tv.orchard_fvk_bytes {
2692                let actual = ufvk
2693                    .orchard()
2694                    .expect("orchard key present in test vector")
2695                    .to_bytes();
2696                assert_eq!(
2697                    &actual[..],
2698                    &expected[..],
2699                    "orchard FVK mismatch for account {}",
2700                    tv.account
2701                );
2702            }
2703
2704            assert_eq!(
2705                ufvk.p2sh().map(|p| p.to_bytes()).as_deref(),
2706                tv.p2sh_fvk_bytes,
2707                "P2SH FVK item mismatch for account {}",
2708                tv.account
2709            );
2710
2711            // The UIVK form of the P2SH item derived from the UFVK must match the
2712            // vector's UIVK item.
2713            let derived_uivk = ufvk.to_unified_incoming_viewing_key();
2714            assert_eq!(
2715                derived_uivk.p2sh().map(|p| p.to_bytes()).as_deref(),
2716                tv.p2sh_ivk_bytes,
2717                "derived P2SH IVK item mismatch for account {}",
2718                tv.account
2719            );
2720        }
2721    }
2722
2723    #[test]
2724    #[cfg(all(
2725        feature = "transparent-inputs",
2726        any(feature = "orchard", feature = "sapling")
2727    ))]
2728    fn ufvk_derivation() {
2729        for tv in test_vectors::UNIFIED {
2730            let usk = UnifiedSpendingKey::from_seed(
2731                &MAIN_NETWORK,
2732                &tv.root_seed,
2733                AccountId::try_from(tv.account).unwrap(),
2734            )
2735            .expect("seed produced a valid unified spending key");
2736
2737            let d_idx = DiversifierIndex::from(tv.diversifier_index);
2738            let ufvk = usk.to_unified_full_viewing_key();
2739
2740            // The test vectors contain some diversifier indices that do not generate
2741            // valid Sapling addresses, so skip those.
2742            #[cfg(feature = "sapling")]
2743            if ufvk.sapling().unwrap().address(d_idx).is_none() {
2744                continue;
2745            }
2746
2747            let ua = ufvk
2748                .address(
2749                    d_idx,
2750                    UnifiedAddressRequest::unsafe_custom(Omit, Require, Require),
2751                )
2752                .unwrap_or_else(|err| {
2753                    panic!(
2754                        "unified address generation failed for account {}: {:?}",
2755                        tv.account, err
2756                    )
2757                });
2758
2759            match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
2760                Some(Address::Unified(tvua)) => {
2761                    // We always derive transparent and Sapling receivers, but not
2762                    // every value in the test vectors has these present.
2763                    if tvua.has_transparent() {
2764                        assert_eq!(tvua.transparent(), ua.transparent());
2765                    }
2766                    #[cfg(feature = "sapling")]
2767                    if tvua.has_sapling() {
2768                        assert_eq!(tvua.sapling(), ua.sapling());
2769                    }
2770                }
2771                _other => {
2772                    panic!(
2773                        "{} did not decode to a valid unified address",
2774                        tv.unified_addr
2775                    );
2776                }
2777            }
2778        }
2779    }
2780
2781    /// Tests UIVK encoding round-trip and verification against R2 test vectors from
2782    /// https://github.com/zcash/zcash-test-vectors/blob/667c92954acd7defc6e60e25b022fedf8831dfb3/test-vectors/rust/unified_viewing_keys_r2.rs
2783    #[test]
2784    #[cfg(all(
2785        feature = "orchard",
2786        feature = "sapling",
2787        feature = "transparent-inputs"
2788    ))]
2789    fn uivk_round_trip() {
2790        use super::test_vectors::unified_viewing_keys_r2::TEST_VECTORS;
2791        use zcash_protocol::consensus::NetworkType;
2792
2793        for tv in TEST_VECTORS {
2794            let decoded = UnifiedIncomingViewingKey::from_container(
2795                &unified::Uivk::decode(tv.unified_ivk).unwrap().2,
2796            )
2797            .unwrap_or_else(|e| panic!("Failed to decode UIVK for account {}: {e:?}", tv.account));
2798
2799            let reencoded = decoded
2800                .render(Revision::R2)
2801                .map(|uivk| uivk.encode(&NetworkType::Main));
2802            assert_eq!(
2803                reencoded.as_deref(),
2804                Ok(tv.unified_ivk),
2805                "UIVK round-trip failed for account {}",
2806                tv.account
2807            );
2808
2809            // ZIP 316 requires the source UFVK's expiry metadata to be retained
2810            // unmodified in the derived UIVK.
2811            assert_eq!(
2812                decoded.expiry_height(),
2813                tv.expiry_height
2814                    .map(zcash_protocol::consensus::BlockHeight::from_u32),
2815                "expiry height mismatch for account {}",
2816                tv.account
2817            );
2818            assert_eq!(
2819                decoded.expiry_time(),
2820                tv.expiry_time,
2821                "expiry time mismatch for account {}",
2822                tv.account
2823            );
2824
2825            // The Unified Address derived from the decoded UIVK must match the vector,
2826            // including the P2SH receiver obtained by evaluating a P2SH viewing key
2827            // item's wallet policy.
2828            let ua = decoded
2829                .address(
2830                    DiversifierIndex::from(tv.diversifier_index),
2831                    UnifiedAddressRequest::AllAvailableKeys,
2832                )
2833                .unwrap_or_else(|e| {
2834                    panic!("UA derivation failed for account {}: {e:?}", tv.account)
2835                });
2836            assert_eq!(
2837                ua.encode_revision(&MAIN_NETWORK, Revision::R2).as_deref(),
2838                Ok(tv.derived_ua),
2839                "derived UA mismatch for account {}",
2840                tv.account
2841            );
2842
2843            // Verify key data matches the test vector bytes.
2844            if let Some(ref expected) = tv.t_p2pkh_ivk_bytes {
2845                let actual = decoded
2846                    .p2pkh()
2847                    .expect("transparent key present in test vector")
2848                    .serialize();
2849                assert_eq!(
2850                    &actual[..],
2851                    &expected[..],
2852                    "transparent IVK mismatch for account {}",
2853                    tv.account
2854                );
2855            }
2856
2857            if let Some(ref expected) = tv.sapling_ivk_bytes {
2858                let actual = decoded
2859                    .sapling
2860                    .as_ref()
2861                    .expect("sapling key present in test vector")
2862                    .to_bytes();
2863                assert_eq!(
2864                    &actual[..],
2865                    &expected[..],
2866                    "sapling IVK mismatch for account {}",
2867                    tv.account
2868                );
2869            }
2870
2871            if let Some(ref expected) = tv.orchard_ivk_bytes {
2872                let actual = decoded
2873                    .orchard
2874                    .as_ref()
2875                    .expect("orchard key present in test vector")
2876                    .to_bytes();
2877                assert_eq!(
2878                    &actual[..],
2879                    &expected[..],
2880                    "orchard IVK mismatch for account {}",
2881                    tv.account
2882                );
2883            }
2884
2885            assert_eq!(
2886                decoded.p2sh().map(|p| p.to_bytes()).as_deref(),
2887                tv.p2sh_ivk_bytes,
2888                "P2SH IVK item mismatch for account {}",
2889                tv.account
2890            );
2891        }
2892    }
2893
2894    #[test]
2895    #[cfg(all(
2896        feature = "transparent-inputs",
2897        any(feature = "orchard", feature = "sapling")
2898    ))]
2899    fn uivk_derivation() {
2900        for tv in test_vectors::UNIFIED {
2901            let usk = UnifiedSpendingKey::from_seed(
2902                &MAIN_NETWORK,
2903                &tv.root_seed,
2904                AccountId::try_from(tv.account).unwrap(),
2905            )
2906            .expect("seed produced a valid unified spending key");
2907
2908            let d_idx = DiversifierIndex::from(tv.diversifier_index);
2909            let uivk = usk
2910                .to_unified_full_viewing_key()
2911                .to_unified_incoming_viewing_key();
2912
2913            // The test vectors contain some diversifier indices that do not generate
2914            // valid Sapling addresses, so skip those.
2915            #[cfg(feature = "sapling")]
2916            if uivk.sapling().as_ref().unwrap().address_at(d_idx).is_none() {
2917                continue;
2918            }
2919
2920            let ua = uivk
2921                .address(
2922                    d_idx,
2923                    UnifiedAddressRequest::unsafe_custom(Omit, Require, Require),
2924                )
2925                .unwrap_or_else(|err| {
2926                    panic!(
2927                        "unified address generation failed for account {}: {:?}",
2928                        tv.account, err
2929                    )
2930                });
2931
2932            match Address::decode(&MAIN_NETWORK, tv.unified_addr) {
2933                Some(Address::Unified(tvua)) => {
2934                    // We always derive transparent and Sapling receivers, but not
2935                    // every value in the test vectors has these present.
2936                    if tvua.has_transparent() {
2937                        assert_eq!(tvua.transparent(), ua.transparent());
2938                    }
2939                    #[cfg(feature = "sapling")]
2940                    if tvua.has_sapling() {
2941                        assert_eq!(tvua.sapling(), ua.sapling());
2942                    }
2943                }
2944                _other => {
2945                    panic!(
2946                        "{} did not decode to a valid unified address",
2947                        tv.unified_addr
2948                    );
2949                }
2950            }
2951        }
2952    }
2953
2954    proptest! {
2955        #[test]
2956        #[cfg(feature = "unstable")]
2957        fn prop_usk_roundtrip(usk in arb_unified_spending_key(zcash_protocol::consensus::Network::MainNetwork)) {
2958            let encoded = usk.to_bytes(Era::Orchard);
2959            let encoded = encoded.expose_secret();
2960
2961            #[allow(clippy::let_and_return)]
2962            let encoded_len = {
2963                let len = 4;
2964
2965                #[cfg(feature = "orchard")]
2966                let len = len + 2 + 32;
2967
2968                #[cfg(feature = "sapling")]
2969                let len = len + 2 + 169;
2970
2971                // Transparent part is an `xprv` transparent extended key deserialized
2972                // into bytes from Base58, minus the 4 prefix bytes.
2973                #[cfg(feature = "transparent-inputs")]
2974                let len = len + 2 + 74;
2975
2976                #[allow(clippy::let_and_return)]
2977                len
2978            };
2979            assert_eq!(encoded.len(), encoded_len);
2980
2981            let decoded = UnifiedSpendingKey::from_bytes(Era::Orchard, encoded);
2982            let decoded = decoded.unwrap_or_else(|e| panic!("Error decoding USK: {:?}", e));
2983
2984            #[cfg(feature = "orchard")]
2985            assert!(bool::from(decoded.orchard().ct_eq(usk.orchard())));
2986
2987            #[cfg(feature = "sapling")]
2988            assert_eq!(decoded.sapling(), usk.sapling());
2989
2990            #[cfg(feature = "transparent-inputs")]
2991            assert_eq!(decoded.transparent().to_bytes(), usk.transparent().to_bytes());
2992        }
2993    }
2994
2995    #[test]
2996    #[cfg(all(
2997        feature = "sapling",
2998        feature = "orchard",
2999        feature = "transparent-inputs"
3000    ))]
3001    fn uivk_decrypt_diversifier_matches_own_ua_and_rejects_foreign() {
3002        let ufvk_a = UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &[1u8; 32], AccountId::ZERO)
3003            .unwrap()
3004            .to_unified_full_viewing_key();
3005        let ufvk_b = UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &[2u8; 32], AccountId::ZERO)
3006            .unwrap()
3007            .to_unified_full_viewing_key();
3008
3009        let (ua_a, mut di_a) = ufvk_a
3010            .default_address(UnifiedAddressRequest::AllAvailableKeys)
3011            .unwrap();
3012
3013        // The UIVK for A recovers the diversifier index used to derive A's own UA.
3014        let uivk_a = ufvk_a.to_unified_incoming_viewing_key();
3015        let ua_a_dis = uivk_a.decrypt_diversifiers(&ua_a);
3016        assert_eq!(ua_a_dis.len(), 1);
3017        assert_eq!(ua_a_dis.first(), Some(di_a).as_ref());
3018
3019        // The UIVK for B, which did not derive A's UA, returns None.
3020        let uivk_b = ufvk_b.to_unified_incoming_viewing_key();
3021        assert_eq!(uivk_b.decrypt_diversifiers(&ua_a).len(), 0);
3022
3023        // A frankenstein UA combining A's Sapling receiver with B's Orchard receiver is
3024        // attributed to A (matched via Sapling, tried first).
3025        let (ua_b, di_b) = ufvk_b
3026            .default_address(UnifiedAddressRequest::AllAvailableKeys)
3027            .unwrap();
3028        let franken = UnifiedAddress::from_receivers(
3029            Some(*ua_b.orchard().unwrap()),
3030            Some(*ua_a.sapling().unwrap()),
3031            None,
3032            None,
3033            None,
3034        )
3035        .unwrap();
3036
3037        let franken_dis_a = uivk_a.decrypt_diversifiers(&franken);
3038        assert_eq!(franken_dis_a.len(), 1);
3039        assert_eq!(franken_dis_a.first(), Some(di_a).as_ref());
3040
3041        let franken_dis_b = uivk_b.decrypt_diversifiers(&franken);
3042        assert_eq!(franken_dis_b.len(), 1);
3043        assert_eq!(franken_dis_b.first(), Some(di_b).as_ref());
3044
3045        di_a.increment()
3046            .expect("diversifier space is not exhausted");
3047        let (next_ua_a, _) = ufvk_a
3048            .find_address(di_a, UnifiedAddressRequest::AllAvailableKeys)
3049            .unwrap();
3050        let mixed_ua = UnifiedAddress::from_receivers(
3051            Some(*ua_a.orchard().unwrap()),
3052            Some(*next_ua_a.sapling().unwrap()),
3053            None,
3054            None,
3055            None,
3056        )
3057        .unwrap();
3058        let mixed_dis_a = uivk_a.decrypt_diversifiers(&mixed_ua);
3059        assert_eq!(mixed_dis_a.len(), 2);
3060    }
3061
3062    /// A key with no shielded item describes a transparent-only account, and generates
3063    /// transparent-only ZIP 316 Revision 2 (`tu`) Unified Addresses.
3064    #[test]
3065    #[cfg(feature = "transparent-inputs")]
3066    fn transparent_only_key_generates_tu_address() {
3067        use super::{
3068            ReceiverRequirement::{Omit, Require},
3069            UnifiedAddressRequest,
3070        };
3071        use crate::address::UnifiedAddress;
3072
3073        let account_pubkey = AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO)
3074            .unwrap()
3075            .to_account_pubkey();
3076        let ufvk = super::UnifiedFullViewingKey::new(
3077            Some(account_pubkey),
3078            #[cfg(feature = "sapling")]
3079            None,
3080            #[cfg(feature = "orchard")]
3081            None,
3082        )
3083        .unwrap();
3084        let uivk = ufvk.to_unified_incoming_viewing_key();
3085
3086        let requirements = uivk
3087            .to_receiver_requirements()
3088            .expect("a transparent item is a receiver requirement");
3089        assert_eq!(requirements.orchard(), Omit);
3090        assert_eq!(requirements.sapling(), Omit);
3091        assert_eq!(requirements.p2pkh(), Require);
3092
3093        let (ufvk_addr, _) = ufvk
3094            .default_address(UnifiedAddressRequest::AllAvailableKeys)
3095            .expect("a transparent-only key has a default address");
3096        let (uivk_addr, _) = uivk
3097            .default_address(UnifiedAddressRequest::AllAvailableKeys)
3098            .expect("a transparent-only key has a default address");
3099        assert_eq!(ufvk_addr, uivk_addr);
3100
3101        assert!(ufvk_addr.has_transparent());
3102        assert!(!ufvk_addr.has_sapling());
3103        assert!(!ufvk_addr.has_orchard());
3104        assert_eq!(
3105            ufvk_addr.transparent(),
3106            ufvk.default_transparent_address()
3107                .map(|(addr, _)| addr)
3108                .as_ref()
3109        );
3110
3111        // The address is encoded using the Revision 2 transparent-including HRP, and
3112        // round-trips through that encoding.
3113        let encoded = ufvk_addr
3114            .encode_revision(&MAIN_NETWORK, Revision::R2)
3115            .expect("every address has a Revision 2 encoding");
3116        assert!(
3117            encoded.starts_with(zcash_address::constants::hrp_unified_address_r2_ti(
3118                zcash_protocol::consensus::NetworkType::Main
3119            )),
3120            "{encoded} is not a transparent-including Revision 2 Unified Address",
3121        );
3122        assert_eq!(
3123            UnifiedAddress::decode(&MAIN_NETWORK, &encoded).as_ref(),
3124            Ok(&ufvk_addr)
3125        );
3126    }
3127
3128    /// A key that has a transparent item and a data item that this build cannot interpret is
3129    /// not transparent-only: the uninterpretable item may be a shielded receiver, so no
3130    /// address can be generated for all of the key's items.
3131    #[test]
3132    #[cfg(feature = "transparent-inputs")]
3133    fn key_with_uninterpretable_data_item_is_not_transparent_only() {
3134        use super::{
3135            AddressGenerationError,
3136            ReceiverRequirement::{Omit, Require},
3137            ReceiverRequirementError, UnifiedAddressRequest,
3138        };
3139        use zcash_protocol::consensus::BlockHeight;
3140
3141        // A data typecode that this crate does not know. Metadata typecodes start at 0xC0, so
3142        // this is a data item: a pool that postdates this crate.
3143        const FUTURE_POOL_TYPECODE: u32 = 0x04;
3144
3145        let account_privkey =
3146            AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO).unwrap();
3147        let account_pubkey = account_privkey.to_account_pubkey();
3148        let transparent_ivk = account_pubkey.derive_external_ivk().unwrap();
3149
3150        // The incoming viewing key form, which keeps an uninterpretable item in `unknown`.
3151        let uivk = super::UnifiedIncomingViewingKey::new(
3152            Some(transparent_ivk.clone()),
3153            #[cfg(feature = "sapling")]
3154            None,
3155            #[cfg(feature = "orchard")]
3156            None,
3157            vec![(FUTURE_POOL_TYPECODE, vec![0u8; 32])],
3158            None,
3159            None,
3160            vec![],
3161        );
3162
3163        assert_eq!(
3164            uivk.to_receiver_requirements().err(),
3165            Some(ReceiverRequirementError::NoShieldedReceiver)
3166        );
3167        assert!(matches!(
3168            uivk.default_address(UnifiedAddressRequest::AllAvailableKeys),
3169            Err(AddressGenerationError::NoSatisfiableReceiver)
3170        ));
3171
3172        // The full viewing key form. Deriving its incoming viewing key cannot carry the
3173        // uninterpretable item over, so the derived key must record that it is incomplete;
3174        // otherwise every consumer that describes an account through that derivation, as
3175        // wallet backends do, sees a transparent-only key.
3176        let ufvk = super::UnifiedFullViewingKey::from_checked_parts(
3177            Some(super::TransparentFvk::P2pkh(account_pubkey)),
3178            #[cfg(feature = "sapling")]
3179            None,
3180            #[cfg(feature = "orchard")]
3181            None,
3182            vec![(FUTURE_POOL_TYPECODE, vec![0u8; 32])],
3183            None,
3184            None,
3185            vec![],
3186        )
3187        .unwrap();
3188
3189        assert_eq!(
3190            ufvk.to_unified_incoming_viewing_key()
3191                .to_receiver_requirements()
3192                .err(),
3193            Some(ReceiverRequirementError::NoShieldedReceiver)
3194        );
3195        assert!(matches!(
3196            ufvk.default_address(UnifiedAddressRequest::AllAvailableKeys),
3197            Err(AddressGenerationError::NoSatisfiableReceiver)
3198        ));
3199
3200        // Metadata items are not receivers, so they never prevent address generation.
3201        let with_metadata = super::UnifiedIncomingViewingKey::new(
3202            Some(transparent_ivk),
3203            #[cfg(feature = "sapling")]
3204            None,
3205            #[cfg(feature = "orchard")]
3206            None,
3207            vec![],
3208            Some(BlockHeight::from_u32(1_000_000)),
3209            Some(1730506496),
3210            vec![(0xC5, vec![0u8; 4])],
3211        );
3212
3213        let requirements = with_metadata
3214            .to_receiver_requirements()
3215            .expect("metadata items do not prevent address generation");
3216        assert_eq!(requirements.orchard(), Omit);
3217        assert_eq!(requirements.sapling(), Omit);
3218        assert_eq!(requirements.p2pkh(), Require);
3219    }
3220
3221    /// Decoding a genuine Orchard-and-transparent UFVK in a build that cannot interpret Orchard
3222    /// keeps the Orchard item, and so must not describe the key as transparent-only.
3223    #[test]
3224    #[cfg(all(
3225        feature = "sapling",
3226        feature = "transparent-inputs",
3227        not(feature = "orchard")
3228    ))]
3229    fn orchard_and_transparent_ufvk_is_not_transparent_only_without_orchard() {
3230        use super::test_vectors::unified_viewing_keys_r2::TEST_VECTORS;
3231        use super::{AddressGenerationError, UnifiedAddressRequest};
3232
3233        let mut checked = 0;
3234        for tv in TEST_VECTORS {
3235            if tv.orchard_fvk_bytes.is_none()
3236                || tv.sapling_fvk_bytes.is_some()
3237                || tv.t_p2pkh_fvk_bytes.is_none()
3238            {
3239                continue;
3240            }
3241            checked += 1;
3242
3243            let ufvk =
3244                UnifiedFullViewingKey::decode(&MAIN_NETWORK, tv.unified_fvk).unwrap_or_else(|e| {
3245                    panic!("Failed to decode UFVK for account {}: {e}", tv.account)
3246                });
3247
3248            // The Orchard item is still part of the key: it re-encodes byte-identically.
3249            assert_eq!(
3250                ufvk.encode_revision(&MAIN_NETWORK, Revision::R2).as_deref(),
3251                Ok(tv.unified_fvk),
3252                "UFVK round-trip failed for account {}",
3253                tv.account
3254            );
3255
3256            assert!(
3257                matches!(
3258                    ufvk.default_address(UnifiedAddressRequest::AllAvailableKeys),
3259                    Err(AddressGenerationError::NoSatisfiableReceiver)
3260                ),
3261                "account {} produced an address that discards its Orchard receiver",
3262                tv.account
3263            );
3264        }
3265
3266        assert!(
3267            checked > 0,
3268            "no test vector has an Orchard item, a transparent item, and no Sapling item"
3269        );
3270    }
3271
3272    /// Requirements that permit no shielded receiver cannot arise from constructing a request
3273    /// or from intersecting two requests; [`ReceiverRequirements::TRANSPARENT_ONLY`] is the
3274    /// only way to name them.
3275    #[test]
3276    #[cfg(all(feature = "transparent-inputs", feature = "sapling"))]
3277    fn shielded_less_requirements_unreachable_by_construction_or_intersection() {
3278        use super::{
3279            ReceiverRequirement::{Allow, Omit, Require},
3280            ReceiverRequirementError, ReceiverRequirements, UnifiedAddressRequest,
3281        };
3282
3283        let account_pubkey = AccountPrivKey::from_seed(&MAIN_NETWORK, &seed(), AccountId::ZERO)
3284            .unwrap()
3285            .to_account_pubkey();
3286        let ufvk = super::UnifiedFullViewingKey::new(
3287            Some(account_pubkey),
3288            Some(
3289                sapling::spending_key(&seed(), 1, AccountId::ZERO)
3290                    .expect("the derivation path yields a valid key")
3291                    .to_diversifiable_full_viewing_key(),
3292            ),
3293            #[cfg(feature = "orchard")]
3294            None,
3295        )
3296        .unwrap();
3297        let uivk = ufvk.to_unified_incoming_viewing_key();
3298
3299        // The requirements derived from the key require its Sapling receiver.
3300        let requirements = uivk.to_receiver_requirements().unwrap();
3301        assert_eq!(requirements.sapling(), Require);
3302
3303        // A shielded-less request cannot be constructed through the ordinary request path.
3304        assert_eq!(
3305            UnifiedAddressRequest::custom(Omit, Omit, Require).err(),
3306            Some(ReceiverRequirementError::NoShieldedReceiver)
3307        );
3308        assert_eq!(
3309            ReceiverRequirements::new(Omit, Omit, Require).err(),
3310            Some(ReceiverRequirementError::NoShieldedReceiver)
3311        );
3312
3313        // Nor can it be reached by intersection with the key's own requirements.
3314        assert_eq!(
3315            requirements
3316                .intersect(&ReceiverRequirements::TRANSPARENT_ONLY)
3317                .err(),
3318            Some(ReceiverRequirementError::Conflict)
3319        );
3320
3321        // Intersection does not introduce a shielded-less requirement from two operands that
3322        // each permit a shielded receiver ...
3323        assert_eq!(
3324            ReceiverRequirements::unsafe_new(Omit, Allow, Allow)
3325                .intersect(&ReceiverRequirements::unsafe_new(Allow, Omit, Allow))
3326                .err(),
3327            Some(ReceiverRequirementError::NoShieldedReceiver)
3328        );
3329
3330        // ... but preserves it when an operand is already transparent-only.
3331        let intersected = ReceiverRequirements::TRANSPARENT_ONLY
3332            .intersect(&ReceiverRequirements::ALLOW_ALL)
3333            .expect("a transparent-only operand keeps the intersection transparent-only");
3334        assert_eq!(intersected.orchard(), Omit);
3335        assert_eq!(intersected.sapling(), Omit);
3336        assert_eq!(intersected.p2pkh(), Require);
3337
3338        // The address generated for all of the key's available items retains the Sapling
3339        // receiver.
3340        let (addr, _) = ufvk
3341            .default_address(UnifiedAddressRequest::AllAvailableKeys)
3342            .unwrap();
3343        assert!(addr.has_sapling());
3344        assert!(addr.has_transparent());
3345
3346        // `TRANSPARENT_ONLY` is a deliberate escape hatch: requesting it against a key that
3347        // has a shielded item is documented misuse, not a rejected request.
3348        let (addr, _) = ufvk
3349            .default_address(UnifiedAddressRequest::TRANSPARENT_ONLY)
3350            .unwrap();
3351        assert!(!addr.has_sapling());
3352        assert!(addr.has_transparent());
3353    }
3354
3355    /// ZIP 316 permits at most one transparent item in a unified viewing key, so a key
3356    /// carrying a P2SH item has no P2PKH component, and vice versa.
3357    #[test]
3358    #[cfg(feature = "transparent-inputs")]
3359    fn p2sh_and_p2pkh_components_are_mutually_exclusive() {
3360        use super::test_vectors::unified_viewing_keys_r2::TEST_VECTORS;
3361
3362        let with_p2sh = TEST_VECTORS
3363            .iter()
3364            .find(|tv| tv.p2sh_fvk_bytes.is_some())
3365            .expect("a test vector carries a P2SH viewing key item");
3366        let ufvk =
3367            super::UnifiedFullViewingKey::decode(&MAIN_NETWORK, with_p2sh.unified_fvk).unwrap();
3368        assert!(ufvk.p2sh().is_some());
3369        assert!(ufvk.p2pkh().is_none());
3370
3371        let uivk = ufvk.to_unified_incoming_viewing_key();
3372        assert!(uivk.p2sh().is_some());
3373        assert!(uivk.p2pkh().is_none());
3374        assert!(uivk.has_transparent());
3375
3376        let with_p2pkh = TEST_VECTORS
3377            .iter()
3378            .find(|tv| tv.t_p2pkh_fvk_bytes.is_some())
3379            .expect("a test vector carries a P2PKH viewing key");
3380        let ufvk =
3381            super::UnifiedFullViewingKey::decode(&MAIN_NETWORK, with_p2pkh.unified_fvk).unwrap();
3382        assert!(ufvk.p2pkh().is_some());
3383        assert!(ufvk.p2sh().is_none());
3384    }
3385
3386    /// A P2SH account has no seed to derive a unified key from, so the constructor is the
3387    /// only way to reach one. What it builds has to survive the encoding, or an account
3388    /// registered from a cosigner set is not the account read back from the wallet.
3389    #[test]
3390    #[cfg(feature = "transparent-inputs")]
3391    fn ufvk_from_p2sh_round_trips_and_is_transparent_only() {
3392        use super::{UnifiedAddressRequest, UnifiedFullViewingKey};
3393        use ::transparent::zip48::{P2shFullViewingKey, P2shKey};
3394        use core::num::NonZeroU8;
3395        use secp256k1::{PublicKey, SecretKey};
3396
3397        let key_info = [1u8, 2, 3]
3398            .iter()
3399            .map(|i| {
3400                let sk = SecretKey::from_secret_bytes([*i; 32]).expect("valid secret key");
3401                P2shKey::new([*i; 32], PublicKey::from_secret_key(&sk))
3402            })
3403            .collect::<Vec<_>>();
3404        let item = P2shFullViewingKey::new(NonZeroU8::new(2).unwrap(), key_info)
3405            .expect("valid cosigner set");
3406
3407        let ufvk = UnifiedFullViewingKey::from_p2sh(item.clone()).expect("constructible");
3408
3409        assert_eq!(ufvk.p2sh(), Some(&item));
3410        assert_eq!(ufvk.p2pkh(), None);
3411
3412        let encoded = ufvk.encode(&MAIN_NETWORK).expect("encodes");
3413        let decoded = UnifiedFullViewingKey::decode(&MAIN_NETWORK, &encoded).expect("decodes");
3414        assert_eq!(decoded.p2sh(), Some(&item));
3415
3416        // A key with no shielded items yields a transparent-only unified address, which
3417        // is what makes the account portable between wallets at all.
3418        let (address, _) = ufvk
3419            .default_address(UnifiedAddressRequest::ALLOW_ALL)
3420            .expect("derives an address");
3421        assert!(address.has_transparent());
3422        assert!(
3423            address.encode(&MAIN_NETWORK).starts_with("tu"),
3424            "expected a transparent-only UA, got {}",
3425            address.encode(&MAIN_NETWORK),
3426        );
3427    }
3428
3429    #[test]
3430    fn p2sh_item_is_unconverted_without_transparent_inputs() {
3431        use zcash_address::unified::{Encoding, Fvk, Revision, Ufvk, Uitem};
3432
3433        /// The shortest ZIP 48 P2SH FVK item.
3434        ///
3435        /// The shortest possible P2SH FVK item would be `sh(pk(@0/**))`), but
3436        /// `P2shFullViewingKey::parse` only supports ZIP 48.
3437        const TEMPLATE: &str = "sh(sortedmulti(1,@0/**))";
3438
3439        let secret_key = SecretKey::from_secret_bytes([1; 32]).unwrap();
3440        let public_key = PublicKey::from_secret_key(&secret_key);
3441
3442        let mut payload = vec![];
3443        CompactSize::write(&mut payload, TEMPLATE.len()).unwrap();
3444        payload.extend_from_slice(TEMPLATE.as_bytes());
3445        CompactSize::write(&mut payload, 1).unwrap();
3446        // A BIP 32 chain code is 32 bytes.
3447        payload.extend_from_slice(&[0; 32]);
3448        payload.extend_from_slice(&public_key.serialize());
3449
3450        let ufvk =
3451            Ufvk::try_from_items(Revision::R2, vec![Uitem::Data(Fvk::P2sh(payload.clone()))])
3452                .unwrap();
3453
3454        let parsed = super::UnifiedFullViewingKey::parse(&ufvk).unwrap();
3455
3456        #[cfg(feature = "transparent-inputs")]
3457        assert_eq!(parsed.p2sh().unwrap().to_bytes(), payload);
3458
3459        // Without the `transparent-inputs` feature a P2SH viewing key item cannot be
3460        // interpreted, so it is retained as an unknown item and recorded as unconverted in
3461        // the derived incoming viewing key. Otherwise that derived key describes the account
3462        // as having no transparent receiving capability at all.
3463        #[cfg(not(feature = "transparent-inputs"))]
3464        {
3465            // The typecode ZIP 316 assigns to a P2SH viewing key item.
3466            const P2SH_TYPECODE: u32 = 0x01;
3467
3468            assert_eq!(parsed.unknown, vec![(P2SH_TYPECODE, payload)]);
3469            assert_eq!(
3470                parsed
3471                    .to_unified_incoming_viewing_key()
3472                    .unconverted_typecodes,
3473                vec![P2SH_TYPECODE]
3474            );
3475        }
3476    }
3477
3478    #[cfg(feature = "unstable")]
3479    #[test]
3480    fn usk_debug_redaction() {
3481        let seed = [0u8; 64];
3482        let usk = UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &seed, AccountId::ZERO).unwrap();
3483        assert!(format!("{usk:?}").contains("\"...\""));
3484    }
3485
3486    #[test]
3487    #[cfg(any(feature = "orchard", feature = "sapling"))]
3488    fn ufvk_debug_redaction() {
3489        #[cfg(feature = "orchard")]
3490        let orchard = {
3491            let sk =
3492                orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
3493            Some(orchard::keys::FullViewingKey::from(&sk))
3494        };
3495
3496        #[cfg(feature = "sapling")]
3497        let sapling = {
3498            let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO)
3499                .expect("the derivation path yields a valid key");
3500            Some(extsk.to_diversifiable_full_viewing_key())
3501        };
3502
3503        #[cfg(feature = "transparent-inputs")]
3504        let transparent = {
3505            let privkey =
3506                AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::ZERO).unwrap();
3507            Some(privkey.to_account_pubkey())
3508        };
3509
3510        let ufvk = UnifiedFullViewingKey::new(
3511            #[cfg(feature = "transparent-inputs")]
3512            transparent,
3513            #[cfg(feature = "sapling")]
3514            sapling,
3515            #[cfg(feature = "orchard")]
3516            orchard,
3517        )
3518        .unwrap();
3519
3520        let debug_str = format!("{ufvk:?}");
3521        #[cfg(feature = "transparent-inputs")]
3522        assert!(debug_str.contains("p2pkh: Some(\"...\")"));
3523        #[cfg(feature = "sapling")]
3524        assert!(debug_str.contains("sapling: Some(\"...\")"));
3525        #[cfg(feature = "orchard")]
3526        assert!(debug_str.contains("orchard: Some(\"...\")"));
3527    }
3528
3529    #[test]
3530    #[cfg(any(feature = "orchard", feature = "sapling"))]
3531    fn uivk_debug_redaction() {
3532        #[cfg(feature = "orchard")]
3533        let orchard = {
3534            let sk =
3535                orchard::keys::SpendingKey::from_zip32_seed(&[0; 32], 0, AccountId::ZERO).unwrap();
3536            Some(orchard::keys::FullViewingKey::from(&sk).to_ivk(Scope::External))
3537        };
3538
3539        #[cfg(feature = "sapling")]
3540        let sapling = {
3541            let extsk = sapling::spending_key(&[0; 32], 0, AccountId::ZERO)
3542                .expect("the derivation path yields a valid key");
3543            Some(extsk.to_diversifiable_full_viewing_key().to_external_ivk())
3544        };
3545
3546        #[cfg(feature = "transparent-inputs")]
3547        let transparent = {
3548            let privkey =
3549                AccountPrivKey::from_seed(&MAIN_NETWORK, &[0; 32], AccountId::ZERO).unwrap();
3550            Some(privkey.to_account_pubkey().derive_external_ivk().unwrap())
3551        };
3552
3553        let uivk = UnifiedIncomingViewingKey::new(
3554            #[cfg(feature = "transparent-inputs")]
3555            transparent,
3556            #[cfg(feature = "sapling")]
3557            sapling,
3558            #[cfg(feature = "orchard")]
3559            orchard,
3560            vec![],
3561            None,
3562            None,
3563            vec![],
3564        );
3565
3566        let debug_str = format!("{uivk:?}");
3567        #[cfg(feature = "sapling")]
3568        assert!(debug_str.contains("sapling: Some(\"...\")"));
3569        #[cfg(feature = "orchard")]
3570        assert!(debug_str.contains("orchard: Some(\"...\")"));
3571    }
3572
3573    #[test]
3574    fn ovk_debug_redaction() {
3575        assert_eq!(
3576            format!("{:?}", super::OutgoingViewingKey::from([0u8; 32])),
3577            "OutgoingViewingKey(\"...\")"
3578        );
3579    }
3580
3581    #[cfg(any(feature = "sapling", feature = "orchard"))]
3582    #[test]
3583    fn subsumes_ufvk_same_key() {
3584        let seed = vec![0u8; 32];
3585        let usk =
3586            super::UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &seed, AccountId::ZERO).unwrap();
3587        let ufvk = usk.to_unified_full_viewing_key();
3588
3589        // A UFVK subsumes itself.
3590        assert!(ufvk.subsumes_ufvk(&ufvk));
3591    }
3592
3593    #[cfg(any(feature = "sapling", feature = "orchard"))]
3594    #[test]
3595    fn subsumes_uivk_from_same_ufvk() {
3596        let seed = vec![0u8; 32];
3597        let usk =
3598            super::UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &seed, AccountId::ZERO).unwrap();
3599        let ufvk = usk.to_unified_full_viewing_key();
3600        let uivk = ufvk.to_unified_incoming_viewing_key();
3601
3602        // A UFVK subsumes the UIVK derived from it.
3603        assert!(ufvk.subsumes_uivk(&uivk));
3604    }
3605
3606    #[cfg(all(feature = "sapling", feature = "orchard"))]
3607    #[test]
3608    fn subsumes_ufvk_subset() {
3609        let seed = vec![0u8; 32];
3610        let usk =
3611            super::UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &seed, AccountId::ZERO).unwrap();
3612        let ufvk = usk.to_unified_full_viewing_key();
3613
3614        // A UFVK with all components subsumes one with fewer components.
3615        let subset_ufvk = UnifiedFullViewingKey::new(
3616            #[cfg(feature = "transparent-inputs")]
3617            None,
3618            ufvk.sapling().cloned(),
3619            None, // no Orchard
3620        )
3621        .unwrap();
3622        assert!(ufvk.subsumes_ufvk(&subset_ufvk));
3623        // The subset does not subsume the full key.
3624        assert!(!subset_ufvk.subsumes_ufvk(&ufvk));
3625    }
3626
3627    #[cfg(any(feature = "sapling", feature = "orchard"))]
3628    #[test]
3629    fn subsumes_ufvk_different_keys() {
3630        let seed0 = vec![0u8; 32];
3631        let seed1 = vec![1u8; 32];
3632        let usk0 =
3633            super::UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &seed0, AccountId::ZERO).unwrap();
3634        let usk1 =
3635            super::UnifiedSpendingKey::from_seed(&MAIN_NETWORK, &seed1, AccountId::ZERO).unwrap();
3636        let ufvk0 = usk0.to_unified_full_viewing_key();
3637        let ufvk1 = usk1.to_unified_full_viewing_key();
3638
3639        // UFVKs from different seeds do not subsume each other.
3640        assert!(!ufvk0.subsumes_ufvk(&ufvk1));
3641        assert!(!ufvk1.subsumes_ufvk(&ufvk0));
3642    }
3643
3644    /// Fails to compile unless the shielded spending keys held by a
3645    /// `UnifiedSpendingKey` erase themselves on drop.
3646    #[cfg(feature = "zeroize")]
3647    #[test]
3648    fn shielded_spending_keys_zeroize_on_drop() {
3649        fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
3650        #[cfg(feature = "sapling")]
3651        assert_zeroize_on_drop::<::sapling::zip32::ExtendedSpendingKey>();
3652        #[cfg(feature = "orchard")]
3653        assert_zeroize_on_drop::<::orchard::keys::SpendingKey>();
3654    }
3655}