zcash_transparent/
keys.rs

1//! Transparent key components.
2
3use 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/// The scope of a transparent key.
20///
21/// This type can represent [`zip32`] internal and external scopes, as well as custom scopes that
22/// may be used in non-hardened derivation at the `change` level of the BIP 44 key path.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct TransparentKeyScope(u32);
25
26impl TransparentKeyScope {
27    /// Returns an arbitrary custom `TransparentKeyScope`.
28    ///
29    /// This should be used with care: funds associated with keys derived under a custom
30    /// scope may not be recoverable if the wallet seed is restored in another wallet. It
31    /// is usually preferable to use standardized key scopes.
32    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    /// The scope used to derive keys for external transparent addresses,
41    /// intended to be used to send funds to this wallet.
42    pub const EXTERNAL: Self = TransparentKeyScope(0);
43
44    /// The scope used to derive keys for internal wallet operations, e.g.
45    /// change or UTXO management.
46    pub const INTERNAL: Self = TransparentKeyScope(1);
47
48    /// The scope used to derive keys for ephemeral transparent addresses.
49    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/// A child index for a derived transparent address.
68///
69/// Only NON-hardened derivation is supported.
70#[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    /// The minimum valid non-hardened child index.
81    pub const ZERO: NonHardenedChildIndex = NonHardenedChildIndex(0);
82
83    /// The maximum valid non-hardened child index.
84    pub const MAX: NonHardenedChildIndex = NonHardenedChildIndex((1 << 31) - 1);
85
86    /// Parses the given ZIP 32 child index.
87    ///
88    /// Returns `None` if the hardened bit is set.
89    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    /// Constructs a [`NonHardenedChildIndex`] from a ZIP 32 child index.
98    ///
99    /// Panics: if the hardened bit is set.
100    pub const fn const_from_index(i: u32) -> Self {
101        assert!(i <= Self::MAX.0);
102        NonHardenedChildIndex(i)
103    }
104
105    /// Returns the index as a 32-bit integer.
106    pub const fn index(&self) -> u32 {
107        self.0
108    }
109
110    /// Returns the successor to this index.
111    pub const fn next(&self) -> Option<Self> {
112        // overflow cannot happen because self.0 is 31 bits, and the next index is at most 32 bits
113        // which in that case would lead from_index to return None.
114        Self::from_index(self.0 + 1)
115    }
116
117    /// Subtracts the given delta from this index.
118    pub const fn saturating_sub(&self, delta: u32) -> Self {
119        NonHardenedChildIndex(self.0.saturating_sub(delta))
120    }
121
122    /// Adds the given delta to this index, returning a maximum possible value of
123    /// [`NonHardenedChildIndex::MAX`].
124    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
167/// An end-exclusive iterator over a range of non-hardened child indexes.
168pub 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
186/// An end-exclusive range of non-hardened child indexes.
187pub 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/// A [BIP44] private key at the account path level `m/44'/<coin_type>'/<account>'`.
208///
209/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
210#[derive(Clone, Debug)]
211#[cfg(feature = "transparent-inputs")]
212pub struct AccountPrivKey(ExtendedPrivateKey<secp256k1::SecretKey>);
213
214#[cfg(feature = "transparent-inputs")]
215impl AccountPrivKey {
216    /// Performs derivation of the extended private key for the BIP44 path:
217    /// `m/44'/<coin_type>'/<account>'`.
218    ///
219    /// This produces the root of the derivation tree for transparent
220    /// viewing keys and addresses for the provided account.
221    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    /// Derives the BIP44 private spending key for the child path
242    /// `m/44'/<coin_type>'/<account>'/<scope>/<address_index>`.
243    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    /// Derives the BIP44 private spending key for the external (incoming payment) child path
255    /// `m/44'/<coin_type>'/<account>'/0/<address_index>`.
256    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    /// Derives the BIP44 private spending key for the internal (change) child path
264    /// `m/44'/<coin_type>'/<account>'/1/<address_index>`.
265    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    /// Returns the `AccountPrivKey` serialized using the encoding for a
273    /// [BIP 32](https://en.bitcoin.it/wiki/BIP_0032) ExtendedPrivateKey, excluding the
274    /// 4 prefix bytes.
275    pub fn to_bytes(&self) -> Vec<u8> {
276        // Convert to `xprv` encoding.
277        let xprv_encoded = self.0.to_extended_key(Prefix::XPRV).to_string();
278
279        // Now decode it and return the bytes we want.
280        bs58::decode(xprv_encoded)
281            .with_check(None)
282            .into_vec()
283            .expect("correct")
284            .split_off(Prefix::LENGTH)
285    }
286
287    /// Decodes the `AccountPrivKey` from the encoding specified for a
288    /// [BIP 32](https://en.bitcoin.it/wiki/BIP_0032) ExtendedPrivateKey, excluding the
289    /// 4 prefix bytes.
290    pub fn from_bytes(b: &[u8]) -> Option<Self> {
291        // Convert to `xprv` encoding.
292        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        // Now we can parse it.
297        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/// A [BIP44] public key at the account path level `m/44'/<coin_type>'/<account>'`.
306///
307/// This provides the necessary derivation capability for the transparent component of a unified
308/// full viewing key.
309///
310/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
311#[cfg(feature = "transparent-inputs")]
312#[derive(Clone, Debug)]
313pub struct AccountPubKey(ExtendedPublicKey<PublicKey>);
314
315#[cfg(feature = "transparent-inputs")]
316impl AccountPubKey {
317    /// Derives the BIP44 public key at the external "change level" path
318    /// `m/44'/<coin_type>'/<account>'/0`.
319    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    /// Derives the BIP44 public key at the internal "change level" path
326    /// `m/44'/<coin_type>'/<account>'/1`.
327    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    /// Derives the public key at the "ephemeral" path
334    /// `m/44'/<coin_type>'/<account>'/2`.
335    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    /// Derives the BIP44 public key at the "address level" path corresponding to the given scope
342    /// and address index.
343    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    /// Derives the public key corresponding to the given full BIP 32 path.
356    ///
357    /// This enforces that the path has a prefix that could have been used to derive this
358    /// `AccountPubKey`.
359    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    /// Derives the internal ovk and external ovk corresponding to this
390    /// transparent fvk. As specified in [ZIP 316][transparent-ovk].
391    ///
392    /// [transparent-ovk]: https://zips.z.cash/zip-0316#deriving-internal-keys
393    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    /// Derives the internal ovk corresponding to this transparent fvk.
403    pub fn internal_ovk(&self) -> InternalOvk {
404        self.ovks_for_shielding().0
405    }
406
407    /// Derives the external ovk corresponding to this transparent fvk.
408    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                // We do not expose the inner `ExtendedPublicKey`, so we can use dummy
426                // values for the fields that are not encoded in an `AccountPubKey`.
427                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/// Trait representing a transparent "incoming viewing key".
448///
449/// Unlike the Sapling and Orchard shielded protocols (which have viewing keys built into
450/// their key trees and bound to specific spending keys), the transparent protocol has no
451/// "viewing key" concept. Transparent viewing keys are instead emulated by making two
452/// observations:
453///
454/// - [BIP32] hierarchical derivation is structured as a tree.
455/// - The [BIP44] key paths use non-hardened derivation below the account level.
456///
457/// A transparent viewing key for an account is thus defined as the root of a specific
458/// non-hardened subtree underneath the account's path.
459///
460/// [BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
461/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
462#[cfg(feature = "transparent-inputs")]
463pub trait IncomingViewingKey: private::SealedChangeLevelKey + core::marker::Sized {
464    /// Derives a transparent address at the provided child index.
465    #[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    /// Searches the space of child indexes for an index that will
475    /// generate a valid transparent address, and returns the resulting
476    /// address and the index at which it was generated.
477    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                // We do not expose the inner `ExtendedPublicKey`, so we can use a dummy
508                // value for the `parent_fingerprint` that is not encoded in an
509                // `IncomingViewingKey`.
510                parent_fingerprint: [0xff, 0xff, 0xff, 0xff],
511                child_number: Self::SCOPE.into(),
512                chain_code,
513            },
514        )))
515    }
516}
517
518/// An incoming viewing key at the [BIP44] "external" path
519/// `m/44'/<coin_type>'/<account>'/0`.
520///
521/// This allows derivation of child addresses that may be provided to external parties.
522///
523/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
524#[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/// An incoming viewing key at the [BIP44] "internal" path
545/// `m/44'/<coin_type>'/<account>'/1`.
546///
547/// This allows derivation of change addresses for use within the wallet, but which should
548/// not be shared with external parties.
549///
550/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
551#[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/// An incoming viewing key at the "ephemeral" path
572/// `m/44'/<coin_type>'/<account>'/2`.
573///
574/// This allows derivation of ephemeral addresses for use within the wallet.
575#[cfg(feature = "transparent-inputs")]
576#[derive(Clone, Debug)]
577pub struct EphemeralIvk(ExtendedPublicKey<PublicKey>);
578
579#[cfg(feature = "transparent-inputs")]
580impl EphemeralIvk {
581    /// Derives a transparent address at the provided child index.
582    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
592/// Internal outgoing viewing key used for autoshielding.
593pub struct InternalOvk([u8; 32]);
594
595impl InternalOvk {
596    pub fn as_bytes(&self) -> [u8; 32] {
597        self.0
598    }
599}
600
601/// External outgoing viewing key used by `zcashd` for transparent-to-shielded spends to
602/// external receivers.
603pub 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 short paths, we get an error.
658            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            // The truncated-by-one path gives the external IVK.
670            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            // The full path gives the correct pubkey.
680            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            // The test vectors are broken here: they should be deriving an address at the
718            // address level, but instead use the account pubkey as an address.
719            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}