1use 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
20use 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 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 #[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#[cfg(feature = "unstable")]
170#[derive(Debug, PartialEq, Eq)]
171pub enum Era {
172 Orchard,
175}
176
177#[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 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 fn id(&self) -> u32 {
231 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#[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 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 #[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 #[cfg(feature = "transparent-inputs")]
336 pub fn transparent(&self) -> &::transparent::keys::AccountPrivKey {
337 &self.transparent
338 }
339
340 #[cfg(feature = "sapling")]
342 pub fn sapling(&self) -> &sapling::ExtendedSpendingKey {
343 &self.sapling
344 }
345
346 #[cfg(feature = "orchard")]
348 pub fn orchard(&self) -> &orchard::keys::SpendingKey {
349 &self.orchard
350 }
351
352 #[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 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 #[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 #[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 #[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#[derive(Clone, Debug)]
553pub enum AddressGenerationError {
554 #[cfg(feature = "transparent-inputs")]
557 InvalidTransparentChildIndex(DiversifierIndex),
558 #[cfg(feature = "transparent-inputs")]
560 UnsupportedTransparentKeyScope(TransparentKeyScope),
561 #[cfg(feature = "transparent-inputs")]
563 Bip32DerivationError(bip32::Error),
564 #[cfg(feature = "sapling")]
566 InvalidSaplingDiversifierIndex(DiversifierIndex),
567 DiversifierSpaceExhausted,
569 ReceiverTypeNotSupported(Typecode),
572 KeyNotAvailable(Typecode),
575 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
648pub enum ReceiverRequirementError {
649 Conflict,
652 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
678pub enum ReceiverRequirement {
679 Require,
684 Allow,
688 Omit,
692}
693
694impl ReceiverRequirement {
695 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#[derive(Clone, Copy, Debug)]
710pub enum UnifiedAddressRequest {
711 AllAvailableKeys,
712 Custom(ReceiverRequirements),
713}
714
715impl UnifiedAddressRequest {
716 pub const ALLOW_ALL: Self = Self::Custom(ReceiverRequirements::ALLOW_ALL);
718
719 pub const SHIELDED: Self = Self::Custom(ReceiverRequirements::SHIELDED);
721
722 pub const ORCHARD: Self = Self::Custom(ReceiverRequirements::ORCHARD);
724
725 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#[derive(Clone, Copy, Debug)]
751pub struct ReceiverRequirements {
752 orchard: ReceiverRequirement,
753 sapling: ReceiverRequirement,
754 p2pkh: ReceiverRequirement,
755}
756
757impl ReceiverRequirements {
758 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 pub const ALLOW_ALL: ReceiverRequirements = { Self::unsafe_new(Allow, Allow, Allow) };
782
783 pub const SHIELDED: ReceiverRequirements = { Self::unsafe_new(Allow, Allow, Omit) };
785
786 pub const ORCHARD: ReceiverRequirements = { Self::unsafe_new(Require, Omit, Omit) };
788
789 pub const TRANSPARENT_ONLY: ReceiverRequirements = Self {
801 orchard: Omit,
802 sapling: Omit,
803 p2pkh: Require,
804 };
805
806 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 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 fn is_transparent_only(&self) -> bool {
857 self.orchard == Omit && self.sapling == Omit
858 }
859
860 pub fn orchard(&self) -> ReceiverRequirement {
862 self.orchard
863 }
864
865 pub fn sapling(&self) -> ReceiverRequirement {
867 self.sapling
868 }
869
870 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#[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#[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#[cfg(feature = "transparent-inputs")]
954#[derive(Clone, PartialEq, Eq)]
955enum TransparentFvk {
956 P2pkh(::transparent::keys::AccountPubKey),
957 P2sh(P2shFullViewingKey),
958}
959
960#[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 #[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 ) -> 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 #[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 #[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 #[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 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 pub fn parse(ufvk: &unified::Ufvk) -> Result<Self, DecodingError> {
1169 Self::from_container(ufvk).map_err(DecodingError::KeyDataInvalid)
1170 }
1171
1172 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 #[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 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(¶ms.network_type()))
1287 }
1288
1289 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 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 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 unknown: Vec::new(),
1384 unconverted_typecodes: self.unknown.iter().map(|(typecode, _)| *typecode).collect(),
1385 unknown_metadata: vec![],
1386 }
1387 }
1388
1389 #[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 #[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 #[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 #[cfg(feature = "sapling")]
1424 pub fn sapling(&self) -> Option<&sapling::DiversifiableFullViewingKey> {
1425 self.sapling.as_ref()
1426 }
1427
1428 #[cfg(feature = "orchard")]
1430 pub fn orchard(&self) -> Option<&orchard::keys::FullViewingKey> {
1431 self.orchard.as_ref()
1432 }
1433
1434 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 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 pub fn expiry_height(&self) -> Option<consensus::BlockHeight> {
1452 self.expiry_height
1453 }
1454
1455 pub fn expiry_time(&self) -> Option<u64> {
1457 self.expiry_time
1458 }
1459
1460 pub fn subsumes_uivk(&self, other: &UnifiedIncomingViewingKey) -> bool {
1464 self.to_unified_incoming_viewing_key().subsumes(other)
1465 }
1466
1467 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 for item in &other.unknown {
1501 if !self.unknown.contains(item) {
1502 return false;
1503 }
1504 }
1505
1506 true
1507 }
1508
1509 pub fn is_equivalent_to(&self, other: &UnifiedFullViewingKey) -> bool {
1512 self.subsumes_ufvk(other) && other.subsumes_ufvk(self)
1513 }
1514
1515 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 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 pub fn default_address(
1547 &self,
1548 request: UnifiedAddressRequest,
1549 ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
1550 self.find_address(DiversifierIndex::new(), request)
1551 }
1552
1553 #[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 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#[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 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 #[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 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 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 unconverted_typecodes: vec![],
1840 expiry_height,
1841 expiry_time,
1842 unknown_metadata,
1843 })
1844 }
1845
1846 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(¶ms.network_type()))
1861 }
1862
1863 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 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 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 #[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 #[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 #[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 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 #[cfg(feature = "sapling")]
1987 pub fn sapling(&self) -> &Option<::sapling::zip32::IncomingViewingKey> {
1988 &self.sapling
1989 }
1990
1991 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 #[cfg(feature = "orchard")]
2003 pub fn orchard(&self) -> &Option<orchard::keys::IncomingViewingKey> {
2004 &self.orchard
2005 }
2006
2007 pub fn expiry_height(&self) -> Option<consensus::BlockHeight> {
2009 self.expiry_height
2010 }
2011
2012 pub fn expiry_time(&self) -> Option<u64> {
2014 self.expiry_time
2015 }
2016
2017 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 for item in &other.unknown {
2053 if !self.unknown.contains(item) {
2054 return false;
2055 }
2056 }
2057
2058 true
2059 }
2060
2061 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 #[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 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 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 #[allow(unused_mut)]
2191 pub fn find_address(
2192 &self,
2193 mut j: DiversifierIndex,
2194 request: UnifiedAddressRequest,
2195 ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
2196 #[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 pub fn default_address(
2221 &self,
2222 request: UnifiedAddressRequest,
2223 ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> {
2224 self.find_address(DiversifierIndex::new(), request)
2225 }
2226
2227 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 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 fn has_uninterpretable_data_item(&self) -> bool {
2300 !self.unknown.is_empty() || !self.unconverted_typecodes.is_empty()
2301 }
2302
2303 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 (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 #[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 ¶ms,
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 #[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 #[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 #[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 #[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 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 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 #[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 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 #[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 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 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 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 #[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 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 #[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 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 let uivk_b = ufvk_b.to_unified_incoming_viewing_key();
3021 assert_eq!(uivk_b.decrypt_diversifiers(&ua_a).len(), 0);
3022
3023 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 #[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 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 #[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 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 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 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 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 #[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 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 #[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 let requirements = uivk.to_receiver_requirements().unwrap();
3301 assert_eq!(requirements.sapling(), Require);
3302
3303 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 assert_eq!(
3315 requirements
3316 .intersect(&ReceiverRequirements::TRANSPARENT_ONLY)
3317 .err(),
3318 Some(ReceiverRequirementError::Conflict)
3319 );
3320
3321 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 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 let (addr, _) = ufvk
3341 .default_address(UnifiedAddressRequest::AllAvailableKeys)
3342 .unwrap();
3343 assert!(addr.has_sapling());
3344 assert!(addr.has_transparent());
3345
3346 let (addr, _) = ufvk
3349 .default_address(UnifiedAddressRequest::TRANSPARENT_ONLY)
3350 .unwrap();
3351 assert!(!addr.has_sapling());
3352 assert!(addr.has_transparent());
3353 }
3354
3355 #[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 #[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 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 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 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 #[cfg(not(feature = "transparent-inputs"))]
3464 {
3465 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 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 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 let subset_ufvk = UnifiedFullViewingKey::new(
3616 #[cfg(feature = "transparent-inputs")]
3617 None,
3618 ufvk.sapling().cloned(),
3619 None, )
3621 .unwrap();
3622 assert!(ufvk.subsumes_ufvk(&subset_ufvk));
3623 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 assert!(!ufvk0.subsumes_ufvk(&ufvk1));
3641 assert!(!ufvk1.subsumes_ufvk(&ufvk0));
3642 }
3643
3644 #[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}