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