1use bip32::ChildNumber;
4use subtle::{Choice, ConstantTimeEq};
5use zip32::DiversifierIndex;
6
7#[cfg(feature = "transparent-inputs")]
8use {
9 crate::address::TransparentAddress,
10 alloc::string::ToString,
11 alloc::vec::Vec,
12 bip32::{ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey, ExtendedPublicKey, Prefix},
13 secp256k1::PublicKey,
14 zcash_protocol::consensus::{self, NetworkConstants},
15 zcash_spec::PrfExpand,
16 zip32::AccountId,
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct TransparentKeyScope(u32);
25
26impl TransparentKeyScope {
27 pub const fn custom(i: u32) -> Option<Self> {
33 if i < (1 << 31) {
34 Some(TransparentKeyScope(i))
35 } else {
36 None
37 }
38 }
39
40 pub const EXTERNAL: Self = TransparentKeyScope(0);
43
44 pub const INTERNAL: Self = TransparentKeyScope(1);
47
48 pub const EPHEMERAL: Self = TransparentKeyScope(2);
50}
51
52impl From<zip32::Scope> for TransparentKeyScope {
53 fn from(value: zip32::Scope) -> Self {
54 match value {
55 zip32::Scope::External => TransparentKeyScope::EXTERNAL,
56 zip32::Scope::Internal => TransparentKeyScope::INTERNAL,
57 }
58 }
59}
60
61impl From<TransparentKeyScope> for ChildNumber {
62 fn from(value: TransparentKeyScope) -> Self {
63 ChildNumber::new(value.0, false).expect("TransparentKeyScope is correct by construction")
64 }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
71pub struct NonHardenedChildIndex(u32);
72
73impl ConstantTimeEq for NonHardenedChildIndex {
74 fn ct_eq(&self, other: &Self) -> Choice {
75 self.0.ct_eq(&other.0)
76 }
77}
78
79impl NonHardenedChildIndex {
80 pub const ZERO: NonHardenedChildIndex = NonHardenedChildIndex(0);
82
83 pub const MAX: NonHardenedChildIndex = NonHardenedChildIndex((1 << 31) - 1);
85
86 pub const fn from_index(i: u32) -> Option<Self> {
90 if i <= Self::MAX.0 {
91 Some(NonHardenedChildIndex(i))
92 } else {
93 None
94 }
95 }
96
97 pub const fn const_from_index(i: u32) -> Self {
101 assert!(i <= Self::MAX.0);
102 NonHardenedChildIndex(i)
103 }
104
105 pub const fn index(&self) -> u32 {
107 self.0
108 }
109
110 pub const fn next(&self) -> Option<Self> {
112 Self::from_index(self.0 + 1)
115 }
116
117 pub const fn saturating_sub(&self, delta: u32) -> Self {
119 NonHardenedChildIndex(self.0.saturating_sub(delta))
120 }
121
122 pub const fn saturating_add(&self, delta: u32) -> Self {
125 let idx = self.0.saturating_add(delta);
126 if idx > Self::MAX.0 {
127 Self::MAX
128 } else {
129 NonHardenedChildIndex(idx)
130 }
131 }
132}
133
134impl TryFrom<ChildNumber> for NonHardenedChildIndex {
135 type Error = ();
136
137 fn try_from(value: ChildNumber) -> Result<Self, Self::Error> {
138 if value.is_hardened() {
139 Err(())
140 } else {
141 NonHardenedChildIndex::from_index(value.index()).ok_or(())
142 }
143 }
144}
145
146impl From<NonHardenedChildIndex> for ChildNumber {
147 fn from(value: NonHardenedChildIndex) -> Self {
148 Self::new(value.index(), false).expect("NonHardenedChildIndex is correct by construction")
149 }
150}
151
152impl TryFrom<DiversifierIndex> for NonHardenedChildIndex {
153 type Error = ();
154
155 fn try_from(value: DiversifierIndex) -> Result<Self, Self::Error> {
156 let idx = u32::try_from(value).map_err(|_| ())?;
157 NonHardenedChildIndex::from_index(idx).ok_or(())
158 }
159}
160
161impl From<NonHardenedChildIndex> for DiversifierIndex {
162 fn from(value: NonHardenedChildIndex) -> Self {
163 DiversifierIndex::from(value.0)
164 }
165}
166
167pub struct NonHardenedChildIter {
169 next: Option<NonHardenedChildIndex>,
170 end: NonHardenedChildIndex,
171}
172
173impl Iterator for NonHardenedChildIter {
174 type Item = NonHardenedChildIndex;
175
176 fn next(&mut self) -> Option<Self::Item> {
177 let cur = self.next;
178 self.next = self
179 .next
180 .and_then(|i| i.next())
181 .filter(|succ| succ < &self.end);
182 cur
183 }
184}
185
186pub struct NonHardenedChildRange(core::ops::Range<NonHardenedChildIndex>);
188
189impl From<core::ops::Range<NonHardenedChildIndex>> for NonHardenedChildRange {
190 fn from(value: core::ops::Range<NonHardenedChildIndex>) -> Self {
191 Self(value)
192 }
193}
194
195impl IntoIterator for NonHardenedChildRange {
196 type Item = NonHardenedChildIndex;
197 type IntoIter = NonHardenedChildIter;
198
199 fn into_iter(self) -> Self::IntoIter {
200 NonHardenedChildIter {
201 next: Some(self.0.start),
202 end: self.0.end,
203 }
204 }
205}
206
207#[derive(Clone, Debug)]
211#[cfg(feature = "transparent-inputs")]
212pub struct AccountPrivKey(ExtendedPrivateKey<secp256k1::SecretKey>);
213
214#[cfg(feature = "transparent-inputs")]
215impl AccountPrivKey {
216 pub fn from_seed<P: consensus::Parameters>(
222 params: &P,
223 seed: &[u8],
224 account: AccountId,
225 ) -> Result<AccountPrivKey, bip32::Error> {
226 ExtendedPrivateKey::new(seed)?
227 .derive_child(ChildNumber::new(44, true)?)?
228 .derive_child(ChildNumber::new(params.coin_type(), true)?)?
229 .derive_child(ChildNumber::new(account.into(), true)?)
230 .map(AccountPrivKey)
231 }
232
233 pub fn from_extended_privkey(extprivkey: ExtendedPrivateKey<secp256k1::SecretKey>) -> Self {
234 AccountPrivKey(extprivkey)
235 }
236
237 pub fn to_account_pubkey(&self) -> AccountPubKey {
238 AccountPubKey(ExtendedPublicKey::from(&self.0))
239 }
240
241 pub fn derive_secret_key(
244 &self,
245 scope: TransparentKeyScope,
246 address_index: NonHardenedChildIndex,
247 ) -> Result<secp256k1::SecretKey, bip32::Error> {
248 self.0
249 .derive_child(scope.into())?
250 .derive_child(address_index.into())
251 .map(|k| *k.private_key())
252 }
253
254 pub fn derive_external_secret_key(
257 &self,
258 address_index: NonHardenedChildIndex,
259 ) -> Result<secp256k1::SecretKey, bip32::Error> {
260 self.derive_secret_key(zip32::Scope::External.into(), address_index)
261 }
262
263 pub fn derive_internal_secret_key(
266 &self,
267 address_index: NonHardenedChildIndex,
268 ) -> Result<secp256k1::SecretKey, bip32::Error> {
269 self.derive_secret_key(zip32::Scope::Internal.into(), address_index)
270 }
271
272 pub fn to_bytes(&self) -> Vec<u8> {
276 let xprv_encoded = self.0.to_extended_key(Prefix::XPRV).to_string();
278
279 bs58::decode(xprv_encoded)
281 .with_check(None)
282 .into_vec()
283 .expect("correct")
284 .split_off(Prefix::LENGTH)
285 }
286
287 pub fn from_bytes(b: &[u8]) -> Option<Self> {
291 let mut bytes = Prefix::XPRV.to_bytes().to_vec();
293 bytes.extend_from_slice(b);
294 let xprv_encoded = bs58::encode(bytes).with_check().into_string();
295
296 xprv_encoded
298 .parse::<ExtendedKey>()
299 .ok()
300 .and_then(|k| ExtendedPrivateKey::try_from(k).ok())
301 .map(AccountPrivKey::from_extended_privkey)
302 }
303}
304
305#[cfg(feature = "transparent-inputs")]
312#[derive(Clone, Debug)]
313pub struct AccountPubKey(ExtendedPublicKey<PublicKey>);
314
315#[cfg(feature = "transparent-inputs")]
316impl AccountPubKey {
317 pub fn derive_external_ivk(&self) -> Result<ExternalIvk, bip32::Error> {
320 self.0
321 .derive_child(ChildNumber::new(0, false)?)
322 .map(ExternalIvk)
323 }
324
325 pub fn derive_internal_ivk(&self) -> Result<InternalIvk, bip32::Error> {
328 self.0
329 .derive_child(ChildNumber::new(1, false)?)
330 .map(InternalIvk)
331 }
332
333 pub fn derive_ephemeral_ivk(&self) -> Result<EphemeralIvk, bip32::Error> {
336 self.0
337 .derive_child(ChildNumber::new(2, false)?)
338 .map(EphemeralIvk)
339 }
340
341 pub fn derive_address_pubkey(
344 &self,
345 scope: TransparentKeyScope,
346 address_index: NonHardenedChildIndex,
347 ) -> Result<secp256k1::PublicKey, bip32::Error> {
348 Ok(*self
349 .0
350 .derive_child(scope.into())?
351 .derive_child(address_index.into())?
352 .public_key())
353 }
354
355 pub fn derive_pubkey_at_bip32_path<P: consensus::Parameters>(
360 &self,
361 params: &P,
362 expected_account_index: AccountId,
363 path: &[ChildNumber],
364 ) -> Result<secp256k1::PublicKey, bip32::Error> {
365 if path.len() < 3 {
366 Err(bip32::Error::ChildNumber)
367 } else {
368 match path.split_at(3) {
369 ([purpose, coin_type, account_index], sub_path)
370 if purpose.is_hardened()
371 && purpose.index() == 44
372 && coin_type.is_hardened()
373 && coin_type.index() == params.network_type().coin_type()
374 && account_index.is_hardened()
375 && account_index.index() == expected_account_index.into() =>
376 {
377 sub_path
378 .iter()
379 .try_fold(self.0.clone(), |acc, child_index| {
380 acc.derive_child(*child_index)
381 })
382 .map(|k| *k.public_key())
383 }
384 _ => Err(bip32::Error::ChildNumber),
385 }
386 }
387 }
388
389 pub fn ovks_for_shielding(&self) -> (InternalOvk, ExternalOvk) {
394 let i_ovk = PrfExpand::TRANSPARENT_ZIP316_OVK
395 .with(&self.0.attrs().chain_code, &self.0.public_key().serialize());
396 let ovk_external = ExternalOvk(i_ovk[..32].try_into().unwrap());
397 let ovk_internal = InternalOvk(i_ovk[32..].try_into().unwrap());
398
399 (ovk_internal, ovk_external)
400 }
401
402 pub fn internal_ovk(&self) -> InternalOvk {
404 self.ovks_for_shielding().0
405 }
406
407 pub fn external_ovk(&self) -> ExternalOvk {
409 self.ovks_for_shielding().1
410 }
411
412 pub fn serialize(&self) -> Vec<u8> {
413 let mut buf = self.0.attrs().chain_code.to_vec();
414 buf.extend_from_slice(&self.0.public_key().serialize());
415 buf
416 }
417
418 pub fn deserialize(data: &[u8; 65]) -> Result<Self, bip32::Error> {
419 let chain_code = data[..32].try_into().expect("correct length");
420 let public_key = PublicKey::from_slice(&data[32..])?;
421 Ok(AccountPubKey(ExtendedPublicKey::new(
422 public_key,
423 ExtendedKeyAttrs {
424 depth: 3,
425 parent_fingerprint: [0xff, 0xff, 0xff, 0xff],
428 child_number: ChildNumber::new(0, true).expect("correct"),
429 chain_code,
430 },
431 )))
432 }
433}
434
435#[cfg(feature = "transparent-inputs")]
436pub(crate) mod private {
437 use super::TransparentKeyScope;
438 use bip32::ExtendedPublicKey;
439 use secp256k1::PublicKey;
440 pub trait SealedChangeLevelKey {
441 const SCOPE: TransparentKeyScope;
442 fn extended_pubkey(&self) -> &ExtendedPublicKey<PublicKey>;
443 fn from_extended_pubkey(key: ExtendedPublicKey<PublicKey>) -> Self;
444 }
445}
446
447#[cfg(feature = "transparent-inputs")]
463pub trait IncomingViewingKey: private::SealedChangeLevelKey + core::marker::Sized {
464 #[allow(deprecated)]
466 fn derive_address(
467 &self,
468 address_index: NonHardenedChildIndex,
469 ) -> Result<TransparentAddress, bip32::Error> {
470 let child_key = self.extended_pubkey().derive_child(address_index.into())?;
471 Ok(TransparentAddress::from_pubkey(child_key.public_key()))
472 }
473
474 fn default_address(&self) -> (TransparentAddress, NonHardenedChildIndex) {
478 let mut address_index = NonHardenedChildIndex::ZERO;
479 loop {
480 match self.derive_address(address_index) {
481 Ok(addr) => {
482 return (addr, address_index);
483 }
484 Err(_) => {
485 address_index = address_index.next().unwrap_or_else(|| {
486 panic!("Exhausted child index space attempting to find a default address.");
487 });
488 }
489 }
490 }
491 }
492
493 fn serialize(&self) -> Vec<u8> {
494 let extpubkey = self.extended_pubkey();
495 let mut buf = extpubkey.attrs().chain_code.to_vec();
496 buf.extend_from_slice(&extpubkey.public_key().serialize());
497 buf
498 }
499
500 fn deserialize(data: &[u8; 65]) -> Result<Self, bip32::Error> {
501 let chain_code = data[..32].try_into().expect("correct length");
502 let public_key = PublicKey::from_slice(&data[32..])?;
503 Ok(Self::from_extended_pubkey(ExtendedPublicKey::new(
504 public_key,
505 ExtendedKeyAttrs {
506 depth: 4,
507 parent_fingerprint: [0xff, 0xff, 0xff, 0xff],
511 child_number: Self::SCOPE.into(),
512 chain_code,
513 },
514 )))
515 }
516}
517
518#[cfg(feature = "transparent-inputs")]
525#[derive(Clone, Debug)]
526pub struct ExternalIvk(ExtendedPublicKey<PublicKey>);
527
528#[cfg(feature = "transparent-inputs")]
529impl private::SealedChangeLevelKey for ExternalIvk {
530 const SCOPE: TransparentKeyScope = TransparentKeyScope(0);
531
532 fn extended_pubkey(&self) -> &ExtendedPublicKey<PublicKey> {
533 &self.0
534 }
535
536 fn from_extended_pubkey(key: ExtendedPublicKey<PublicKey>) -> Self {
537 ExternalIvk(key)
538 }
539}
540
541#[cfg(feature = "transparent-inputs")]
542impl IncomingViewingKey for ExternalIvk {}
543
544#[cfg(feature = "transparent-inputs")]
552#[derive(Clone, Debug)]
553pub struct InternalIvk(ExtendedPublicKey<PublicKey>);
554
555#[cfg(feature = "transparent-inputs")]
556impl private::SealedChangeLevelKey for InternalIvk {
557 const SCOPE: TransparentKeyScope = TransparentKeyScope(1);
558
559 fn extended_pubkey(&self) -> &ExtendedPublicKey<PublicKey> {
560 &self.0
561 }
562
563 fn from_extended_pubkey(key: ExtendedPublicKey<PublicKey>) -> Self {
564 InternalIvk(key)
565 }
566}
567
568#[cfg(feature = "transparent-inputs")]
569impl IncomingViewingKey for InternalIvk {}
570
571#[cfg(feature = "transparent-inputs")]
576#[derive(Clone, Debug)]
577pub struct EphemeralIvk(ExtendedPublicKey<PublicKey>);
578
579#[cfg(feature = "transparent-inputs")]
580impl EphemeralIvk {
581 pub fn derive_ephemeral_address(
583 &self,
584 address_index: NonHardenedChildIndex,
585 ) -> Result<TransparentAddress, bip32::Error> {
586 let child_key = self.0.derive_child(address_index.into())?;
587 #[allow(deprecated)]
588 Ok(TransparentAddress::from_pubkey(child_key.public_key()))
589 }
590}
591
592pub struct InternalOvk([u8; 32]);
594
595impl InternalOvk {
596 pub fn as_bytes(&self) -> [u8; 32] {
597 self.0
598 }
599}
600
601pub struct ExternalOvk([u8; 32]);
604
605impl ExternalOvk {
606 pub fn as_bytes(&self) -> [u8; 32] {
607 self.0
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use bip32::ChildNumber;
614 use subtle::ConstantTimeEq;
615 use zcash_protocol::consensus::{NetworkConstants, MAIN_NETWORK};
616
617 use super::AccountPubKey;
618 use super::NonHardenedChildIndex;
619 #[allow(deprecated)]
620 use crate::{
621 address::TransparentAddress,
622 keys::{AccountPrivKey, IncomingViewingKey, TransparentKeyScope},
623 test_vectors,
624 };
625
626 #[test]
627 #[allow(deprecated)]
628 fn address_derivation() {
629 let seed = [
630 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
631 24, 25, 26, 27, 28, 29, 30, 31,
632 ];
633
634 for account_index in 0..5 {
635 let account_index = zip32::AccountId::try_from(account_index).unwrap();
636 let account_sk =
637 AccountPrivKey::from_seed(&MAIN_NETWORK, &seed, account_index).unwrap();
638 let account_pubkey = account_sk.to_account_pubkey();
639
640 let external_ivk = account_pubkey.derive_external_ivk().unwrap();
641 let (address, address_index) = external_ivk.default_address();
642
643 let address_pubkey = account_pubkey
644 .derive_address_pubkey(TransparentKeyScope::EXTERNAL, address_index)
645 .unwrap();
646 #[cfg(feature = "transparent-inputs")]
647 assert_eq!(TransparentAddress::from_pubkey(&address_pubkey), address);
648
649 let expected_path = [
650 ChildNumber::new(44, true).unwrap(),
651 ChildNumber::new(MAIN_NETWORK.coin_type(), true).unwrap(),
652 ChildNumber::new(account_index.into(), true).unwrap(),
653 TransparentKeyScope::EXTERNAL.into(),
654 address_index.into(),
655 ];
656
657 for i in 0..3 {
659 assert_eq!(
660 account_pubkey.derive_pubkey_at_bip32_path(
661 &MAIN_NETWORK,
662 account_index,
663 &expected_path[..i]
664 ),
665 Err(bip32::Error::ChildNumber),
666 );
667 }
668
669 assert_eq!(
671 account_pubkey.derive_pubkey_at_bip32_path(
672 &MAIN_NETWORK,
673 account_index,
674 &expected_path[..4],
675 ),
676 Ok(*external_ivk.0.public_key()),
677 );
678
679 assert_eq!(
681 account_pubkey.derive_pubkey_at_bip32_path(
682 &MAIN_NETWORK,
683 account_index,
684 &expected_path,
685 ),
686 Ok(address_pubkey),
687 );
688 }
689 }
690
691 #[test]
692 #[allow(deprecated)]
693 fn bip_32_test_vectors() {
694 let seed = [
695 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
696 24, 25, 26, 27, 28, 29, 30, 31,
697 ];
698
699 for tv in test_vectors::bip_32() {
700 let account_sk = AccountPrivKey::from_seed(
701 &MAIN_NETWORK,
702 &seed,
703 zip32::AccountId::try_from(tv.account).unwrap(),
704 )
705 .unwrap();
706 let account_pubkey = account_sk.to_account_pubkey();
707
708 let mut key_bytes = [0u8; 65];
709 key_bytes[..32].copy_from_slice(&tv.c);
710 key_bytes[32..].copy_from_slice(&tv.pk);
711 assert_eq!(account_pubkey.serialize(), key_bytes);
712
713 let (internal_ovk, external_ovk) = account_pubkey.ovks_for_shielding();
714 assert_eq!(internal_ovk.as_bytes(), tv.internal_ovk);
715 assert_eq!(external_ovk.as_bytes(), tv.external_ovk);
716
717 let address = TransparentAddress::PublicKeyHash(tv.address);
720
721 #[cfg(feature = "transparent-inputs")]
722 assert_eq!(
723 TransparentAddress::from_pubkey(account_pubkey.0.public_key()),
724 address
725 );
726 }
727 }
728
729 #[test]
730 fn check_ovk_test_vectors() {
731 for tv in test_vectors::transparent_ovk() {
732 let mut key_bytes = [0u8; 65];
733 key_bytes[..32].copy_from_slice(&tv.c);
734 key_bytes[32..].copy_from_slice(&tv.pk);
735 let account_key = AccountPubKey::deserialize(&key_bytes).unwrap();
736
737 let (internal, external) = account_key.ovks_for_shielding();
738
739 assert_eq!(tv.internal_ovk, internal.as_bytes());
740 assert_eq!(tv.external_ovk, external.as_bytes());
741 }
742 }
743
744 #[test]
745 fn nonhardened_indexes_accepted() {
746 assert_eq!(0, NonHardenedChildIndex::from_index(0).unwrap().index());
747 assert_eq!(
748 0x7fffffff,
749 NonHardenedChildIndex::from_index(0x7fffffff)
750 .unwrap()
751 .index()
752 );
753 }
754
755 #[test]
756 fn hardened_indexes_rejected() {
757 assert!(NonHardenedChildIndex::from_index(0x80000000).is_none());
758 assert!(NonHardenedChildIndex::from_index(0xffffffff).is_none());
759 }
760
761 #[test]
762 fn nonhardened_index_next() {
763 assert_eq!(1, NonHardenedChildIndex::ZERO.next().unwrap().index());
764 assert!(NonHardenedChildIndex::from_index(0x7fffffff)
765 .unwrap()
766 .next()
767 .is_none());
768 }
769
770 #[test]
771 fn nonhardened_index_ct_eq() {
772 assert!(check(
773 NonHardenedChildIndex::ZERO,
774 NonHardenedChildIndex::ZERO
775 ));
776 assert!(!check(
777 NonHardenedChildIndex::ZERO,
778 NonHardenedChildIndex::ZERO.next().unwrap()
779 ));
780
781 fn check<T: ConstantTimeEq>(v1: T, v2: T) -> bool {
782 v1.ct_eq(&v2).into()
783 }
784 }
785
786 #[test]
787 fn nonhardened_index_tryfrom_keyindex() {
788 let nh: NonHardenedChildIndex = ChildNumber::new(0, false).unwrap().try_into().unwrap();
789 assert_eq!(nh.index(), 0);
790
791 assert!(NonHardenedChildIndex::try_from(ChildNumber::new(0, true).unwrap()).is_err());
792 }
793}