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