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    sha2::{Digest, Sha256},
15    zcash_protocol::consensus::{self, NetworkConstants},
16    zcash_spec::PrfExpand,
17    zip32::AccountId,
18};
19
20/// The scope of a transparent key.
21///
22/// This type can represent [`zip32`] internal and external scopes, as well as custom scopes that
23/// may be used in non-hardened derivation at the `change` level of the BIP 44 key path.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct TransparentKeyScope(u32);
26
27impl TransparentKeyScope {
28    /// Returns an arbitrary custom `TransparentKeyScope`.
29    ///
30    /// This should be used with care: funds associated with keys derived under a custom
31    /// scope may not be recoverable if the wallet seed is restored in another wallet. It
32    /// is usually preferable to use standardized key scopes.
33    pub const fn custom(i: u32) -> Option<Self> {
34        if i < (1 << 31) {
35            Some(TransparentKeyScope(i))
36        } else {
37            None
38        }
39    }
40
41    /// The scope used to derive keys for external transparent addresses,
42    /// intended to be used to send funds to this wallet.
43    pub const EXTERNAL: Self = TransparentKeyScope(0);
44
45    /// The scope used to derive keys for internal wallet operations, e.g.
46    /// change or UTXO management.
47    pub const INTERNAL: Self = TransparentKeyScope(1);
48
49    /// The scope used to derive keys for ephemeral transparent addresses.
50    pub const EPHEMERAL: Self = TransparentKeyScope(2);
51}
52
53impl From<zip32::Scope> for TransparentKeyScope {
54    fn from(value: zip32::Scope) -> Self {
55        match value {
56            zip32::Scope::External => TransparentKeyScope::EXTERNAL,
57            zip32::Scope::Internal => TransparentKeyScope::INTERNAL,
58        }
59    }
60}
61
62impl From<TransparentKeyScope> for ChildNumber {
63    fn from(value: TransparentKeyScope) -> Self {
64        ChildNumber::new(value.0, false).expect("TransparentKeyScope is correct by construction")
65    }
66}
67
68/// A child index for a derived transparent address.
69///
70/// Only NON-hardened derivation is supported.
71#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
72pub struct NonHardenedChildIndex(u32);
73
74impl ConstantTimeEq for NonHardenedChildIndex {
75    fn ct_eq(&self, other: &Self) -> Choice {
76        self.0.ct_eq(&other.0)
77    }
78}
79
80impl NonHardenedChildIndex {
81    /// The minimum valid non-hardened child index.
82    pub const ZERO: NonHardenedChildIndex = NonHardenedChildIndex(0);
83
84    /// The maximum valid non-hardened child index.
85    pub const MAX: NonHardenedChildIndex = NonHardenedChildIndex((1 << 31) - 1);
86
87    /// Parses the given ZIP 32 child index.
88    ///
89    /// Returns `None` if the hardened bit is set.
90    pub const fn from_index(i: u32) -> Option<Self> {
91        if i <= Self::MAX.0 {
92            Some(NonHardenedChildIndex(i))
93        } else {
94            None
95        }
96    }
97
98    /// Constructs a [`NonHardenedChildIndex`] from a ZIP 32 child index.
99    ///
100    /// Panics: if the hardened bit is set.
101    pub const fn const_from_index(i: u32) -> Self {
102        assert!(i <= Self::MAX.0);
103        NonHardenedChildIndex(i)
104    }
105
106    /// Returns the index as a 32-bit integer.
107    pub const fn index(&self) -> u32 {
108        self.0
109    }
110
111    /// Returns the successor to this index.
112    pub const fn next(&self) -> Option<Self> {
113        // overflow cannot happen because self.0 is 31 bits, and the next index is at most 32 bits
114        // which in that case would lead from_index to return None.
115        Self::from_index(self.0 + 1)
116    }
117
118    /// Subtracts the given delta from this index.
119    pub const fn saturating_sub(&self, delta: u32) -> Self {
120        NonHardenedChildIndex(self.0.saturating_sub(delta))
121    }
122
123    /// Adds the given delta to this index, returning a maximum possible value of
124    /// [`NonHardenedChildIndex::MAX`].
125    pub const fn saturating_add(&self, delta: u32) -> Self {
126        let idx = self.0.saturating_add(delta);
127        if idx > Self::MAX.0 {
128            Self::MAX
129        } else {
130            NonHardenedChildIndex(idx)
131        }
132    }
133}
134
135impl TryFrom<ChildNumber> for NonHardenedChildIndex {
136    type Error = ();
137
138    fn try_from(value: ChildNumber) -> Result<Self, Self::Error> {
139        if value.is_hardened() {
140            Err(())
141        } else {
142            NonHardenedChildIndex::from_index(value.index()).ok_or(())
143        }
144    }
145}
146
147impl From<NonHardenedChildIndex> for ChildNumber {
148    fn from(value: NonHardenedChildIndex) -> Self {
149        Self::new(value.index(), false).expect("NonHardenedChildIndex is correct by construction")
150    }
151}
152
153impl TryFrom<DiversifierIndex> for NonHardenedChildIndex {
154    type Error = ();
155
156    fn try_from(value: DiversifierIndex) -> Result<Self, Self::Error> {
157        let idx = u32::try_from(value).map_err(|_| ())?;
158        NonHardenedChildIndex::from_index(idx).ok_or(())
159    }
160}
161
162impl From<NonHardenedChildIndex> for DiversifierIndex {
163    fn from(value: NonHardenedChildIndex) -> Self {
164        DiversifierIndex::from(value.0)
165    }
166}
167
168/// An end-exclusive iterator over a range of non-hardened child indexes.
169pub struct NonHardenedChildIter {
170    next: Option<NonHardenedChildIndex>,
171    end: NonHardenedChildIndex,
172}
173
174impl Iterator for NonHardenedChildIter {
175    type Item = NonHardenedChildIndex;
176
177    fn next(&mut self) -> Option<Self::Item> {
178        let cur = self.next;
179        self.next = self
180            .next
181            .and_then(|i| i.next())
182            .filter(|succ| succ < &self.end);
183        cur
184    }
185}
186
187/// An end-exclusive range of non-hardened child indexes.
188pub struct NonHardenedChildRange(core::ops::Range<NonHardenedChildIndex>);
189
190impl From<core::ops::Range<NonHardenedChildIndex>> for NonHardenedChildRange {
191    fn from(value: core::ops::Range<NonHardenedChildIndex>) -> Self {
192        Self(value)
193    }
194}
195
196impl IntoIterator for NonHardenedChildRange {
197    type Item = NonHardenedChildIndex;
198    type IntoIter = NonHardenedChildIter;
199
200    fn into_iter(self) -> Self::IntoIter {
201        NonHardenedChildIter {
202            next: Some(self.0.start),
203            end: self.0.end,
204        }
205    }
206}
207
208/// A [BIP44] private key at the account path level `m/44'/<coin_type>'/<account>'`.
209///
210/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
211#[derive(Clone, Debug)]
212#[cfg(feature = "transparent-inputs")]
213pub struct AccountPrivKey(ExtendedPrivateKey<secp256k1::SecretKey>);
214
215#[cfg(feature = "transparent-inputs")]
216impl AccountPrivKey {
217    /// Performs derivation of the extended private key for the BIP44 path:
218    /// `m/44'/<coin_type>'/<account>'`.
219    ///
220    /// This produces the root of the derivation tree for transparent
221    /// viewing keys and addresses for the provided account.
222    pub fn from_seed<P: consensus::Parameters>(
223        params: &P,
224        seed: &[u8],
225        account: AccountId,
226    ) -> Result<AccountPrivKey, bip32::Error> {
227        ExtendedPrivateKey::new(seed)?
228            .derive_child(ChildNumber::new(44, true)?)?
229            .derive_child(ChildNumber::new(params.coin_type(), true)?)?
230            .derive_child(ChildNumber::new(account.into(), true)?)
231            .map(AccountPrivKey)
232    }
233
234    pub fn from_extended_privkey(extprivkey: ExtendedPrivateKey<secp256k1::SecretKey>) -> Self {
235        AccountPrivKey(extprivkey)
236    }
237
238    pub fn to_account_pubkey(&self) -> AccountPubKey {
239        AccountPubKey(ExtendedPublicKey::from(&self.0))
240    }
241
242    /// Derives the BIP44 private spending key for the child path
243    /// `m/44'/<coin_type>'/<account>'/<scope>/<address_index>`.
244    pub fn derive_secret_key(
245        &self,
246        scope: TransparentKeyScope,
247        address_index: NonHardenedChildIndex,
248    ) -> Result<secp256k1::SecretKey, bip32::Error> {
249        self.0
250            .derive_child(scope.into())?
251            .derive_child(address_index.into())
252            .map(|k| *k.private_key())
253    }
254
255    /// Derives the BIP44 private spending key for the external (incoming payment) child path
256    /// `m/44'/<coin_type>'/<account>'/0/<address_index>`.
257    pub fn derive_external_secret_key(
258        &self,
259        address_index: NonHardenedChildIndex,
260    ) -> Result<secp256k1::SecretKey, bip32::Error> {
261        self.derive_secret_key(zip32::Scope::External.into(), address_index)
262    }
263
264    /// Derives the BIP44 private spending key for the internal (change) child path
265    /// `m/44'/<coin_type>'/<account>'/1/<address_index>`.
266    pub fn derive_internal_secret_key(
267        &self,
268        address_index: NonHardenedChildIndex,
269    ) -> Result<secp256k1::SecretKey, bip32::Error> {
270        self.derive_secret_key(zip32::Scope::Internal.into(), address_index)
271    }
272
273    /// Returns the `AccountPrivKey` serialized using the encoding for a
274    /// [BIP 32](https://en.bitcoin.it/wiki/BIP_0032) ExtendedPrivateKey, excluding the
275    /// 4 prefix bytes.
276    pub fn to_bytes(&self) -> Vec<u8> {
277        // Convert to `xprv` encoding.
278        let xprv_encoded = self.0.to_extended_key(Prefix::XPRV).to_string();
279
280        // Now decode it and return the bytes we want.
281        bs58::decode(xprv_encoded)
282            .with_check(None)
283            .into_vec()
284            .expect("correct")
285            .split_off(Prefix::LENGTH)
286    }
287
288    /// Decodes the `AccountPrivKey` from the encoding specified for a
289    /// [BIP 32](https://en.bitcoin.it/wiki/BIP_0032) ExtendedPrivateKey, excluding the
290    /// 4 prefix bytes.
291    pub fn from_bytes(b: &[u8]) -> Option<Self> {
292        // Convert to `xprv` encoding.
293        let mut bytes = Prefix::XPRV.to_bytes().to_vec();
294        bytes.extend_from_slice(b);
295        let xprv_encoded = bs58::encode(bytes).with_check().into_string();
296
297        // Now we can parse it.
298        xprv_encoded
299            .parse::<ExtendedKey>()
300            .ok()
301            .and_then(|k| ExtendedPrivateKey::try_from(k).ok())
302            .map(AccountPrivKey::from_extended_privkey)
303    }
304}
305
306/// A [BIP44] public key at the account path level `m/44'/<coin_type>'/<account>'`.
307///
308/// This provides the necessary derivation capability for the transparent component of a unified
309/// full viewing key.
310///
311/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
312#[cfg(feature = "transparent-inputs")]
313#[derive(Clone, Debug)]
314pub struct AccountPubKey(ExtendedPublicKey<PublicKey>);
315
316#[cfg(feature = "transparent-inputs")]
317impl AccountPubKey {
318    /// Derives the BIP44 public key at the external "change level" path
319    /// `m/44'/<coin_type>'/<account>'/0`.
320    pub fn derive_external_ivk(&self) -> Result<ExternalIvk, bip32::Error> {
321        self.0
322            .derive_child(ChildNumber::new(0, false)?)
323            .map(ExternalIvk)
324    }
325
326    /// Derives the BIP44 public key at the internal "change level" path
327    /// `m/44'/<coin_type>'/<account>'/1`.
328    pub fn derive_internal_ivk(&self) -> Result<InternalIvk, bip32::Error> {
329        self.0
330            .derive_child(ChildNumber::new(1, false)?)
331            .map(InternalIvk)
332    }
333
334    /// Derives the public key at the "ephemeral" path
335    /// `m/44'/<coin_type>'/<account>'/2`.
336    pub fn derive_ephemeral_ivk(&self) -> Result<EphemeralIvk, bip32::Error> {
337        self.0
338            .derive_child(ChildNumber::new(2, false)?)
339            .map(EphemeralIvk)
340    }
341
342    /// Derives the BIP44 public key at the "address level" path corresponding to the given scope
343    /// and address index.
344    pub fn derive_address_pubkey(
345        &self,
346        scope: TransparentKeyScope,
347        address_index: NonHardenedChildIndex,
348    ) -> Result<secp256k1::PublicKey, bip32::Error> {
349        Ok(*self
350            .0
351            .derive_child(scope.into())?
352            .derive_child(address_index.into())?
353            .public_key())
354    }
355
356    /// Derives the public key corresponding to the given full BIP 32 path.
357    ///
358    /// This enforces that the path has a prefix that could have been used to derive this
359    /// `AccountPubKey`.
360    pub fn derive_pubkey_at_bip32_path<P: consensus::Parameters>(
361        &self,
362        params: &P,
363        expected_account_index: AccountId,
364        path: &[ChildNumber],
365    ) -> Result<secp256k1::PublicKey, bip32::Error> {
366        if path.len() < 3 {
367            Err(bip32::Error::ChildNumber)
368        } else {
369            match path.split_at(3) {
370                ([purpose, coin_type, account_index], sub_path)
371                    if purpose.is_hardened()
372                        && purpose.index() == 44
373                        && coin_type.is_hardened()
374                        && coin_type.index() == params.network_type().coin_type()
375                        && account_index.is_hardened()
376                        && account_index.index() == expected_account_index.into() =>
377                {
378                    sub_path
379                        .iter()
380                        .try_fold(self.0.clone(), |acc, child_index| {
381                            acc.derive_child(*child_index)
382                        })
383                        .map(|k| *k.public_key())
384                }
385                _ => Err(bip32::Error::ChildNumber),
386            }
387        }
388    }
389
390    /// Derives the internal ovk and external ovk corresponding to this
391    /// transparent fvk. As specified in [ZIP 316][transparent-ovk].
392    ///
393    /// [transparent-ovk]: https://zips.z.cash/zip-0316#deriving-internal-keys
394    pub fn ovks_for_shielding(&self) -> (InternalOvk, ExternalOvk) {
395        let i_ovk = PrfExpand::TRANSPARENT_ZIP316_OVK
396            .with(&self.0.attrs().chain_code, &self.0.public_key().serialize());
397        let ovk_external = ExternalOvk(i_ovk[..32].try_into().unwrap());
398        let ovk_internal = InternalOvk(i_ovk[32..].try_into().unwrap());
399
400        (ovk_internal, ovk_external)
401    }
402
403    /// Derives the internal ovk corresponding to this transparent fvk.
404    pub fn internal_ovk(&self) -> InternalOvk {
405        self.ovks_for_shielding().0
406    }
407
408    /// Derives the external ovk corresponding to this transparent fvk.
409    pub fn external_ovk(&self) -> ExternalOvk {
410        self.ovks_for_shielding().1
411    }
412
413    pub fn serialize(&self) -> Vec<u8> {
414        let mut buf = self.0.attrs().chain_code.to_vec();
415        buf.extend_from_slice(&self.0.public_key().serialize());
416        buf
417    }
418
419    pub fn deserialize(data: &[u8; 65]) -> Result<Self, bip32::Error> {
420        let chain_code = data[..32].try_into().expect("correct length");
421        let public_key = PublicKey::from_slice(&data[32..])?;
422        Ok(AccountPubKey(ExtendedPublicKey::new(
423            public_key,
424            ExtendedKeyAttrs {
425                depth: 3,
426                // We do not expose the inner `ExtendedPublicKey`, so we can use dummy
427                // values for the fields that are not encoded in an `AccountPubKey`.
428                parent_fingerprint: [0xff, 0xff, 0xff, 0xff],
429                child_number: ChildNumber::new(0, true).expect("correct"),
430                chain_code,
431            },
432        )))
433    }
434}
435
436/// Derives the P2PKH transparent address corresponding to the given pubkey.
437#[cfg(feature = "transparent-inputs")]
438#[deprecated(note = "This function will be removed from the public API in an upcoming refactor.")]
439pub fn pubkey_to_address(pubkey: &secp256k1::PublicKey) -> TransparentAddress {
440    TransparentAddress::PublicKeyHash(
441        *ripemd::Ripemd160::digest(Sha256::digest(pubkey.serialize())).as_ref(),
442    )
443}
444
445#[cfg(feature = "transparent-inputs")]
446pub(crate) mod private {
447    use super::TransparentKeyScope;
448    use bip32::ExtendedPublicKey;
449    use secp256k1::PublicKey;
450    pub trait SealedChangeLevelKey {
451        const SCOPE: TransparentKeyScope;
452        fn extended_pubkey(&self) -> &ExtendedPublicKey<PublicKey>;
453        fn from_extended_pubkey(key: ExtendedPublicKey<PublicKey>) -> Self;
454    }
455}
456
457/// Trait representing a transparent "incoming viewing key".
458///
459/// Unlike the Sapling and Orchard shielded protocols (which have viewing keys built into
460/// their key trees and bound to specific spending keys), the transparent protocol has no
461/// "viewing key" concept. Transparent viewing keys are instead emulated by making two
462/// observations:
463///
464/// - [BIP32] hierarchical derivation is structured as a tree.
465/// - The [BIP44] key paths use non-hardened derivation below the account level.
466///
467/// A transparent viewing key for an account is thus defined as the root of a specific
468/// non-hardened subtree underneath the account's path.
469///
470/// [BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
471/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
472#[cfg(feature = "transparent-inputs")]
473pub trait IncomingViewingKey: private::SealedChangeLevelKey + core::marker::Sized {
474    /// Derives a transparent address at the provided child index.
475    #[allow(deprecated)]
476    fn derive_address(
477        &self,
478        address_index: NonHardenedChildIndex,
479    ) -> Result<TransparentAddress, bip32::Error> {
480        let child_key = self.extended_pubkey().derive_child(address_index.into())?;
481        Ok(pubkey_to_address(child_key.public_key()))
482    }
483
484    /// Searches the space of child indexes for an index that will
485    /// generate a valid transparent address, and returns the resulting
486    /// address and the index at which it was generated.
487    fn default_address(&self) -> (TransparentAddress, NonHardenedChildIndex) {
488        let mut address_index = NonHardenedChildIndex::ZERO;
489        loop {
490            match self.derive_address(address_index) {
491                Ok(addr) => {
492                    return (addr, address_index);
493                }
494                Err(_) => {
495                    address_index = address_index.next().unwrap_or_else(|| {
496                        panic!("Exhausted child index space attempting to find a default address.");
497                    });
498                }
499            }
500        }
501    }
502
503    fn serialize(&self) -> Vec<u8> {
504        let extpubkey = self.extended_pubkey();
505        let mut buf = extpubkey.attrs().chain_code.to_vec();
506        buf.extend_from_slice(&extpubkey.public_key().serialize());
507        buf
508    }
509
510    fn deserialize(data: &[u8; 65]) -> Result<Self, bip32::Error> {
511        let chain_code = data[..32].try_into().expect("correct length");
512        let public_key = PublicKey::from_slice(&data[32..])?;
513        Ok(Self::from_extended_pubkey(ExtendedPublicKey::new(
514            public_key,
515            ExtendedKeyAttrs {
516                depth: 4,
517                // We do not expose the inner `ExtendedPublicKey`, so we can use a dummy
518                // value for the `parent_fingerprint` that is not encoded in an
519                // `IncomingViewingKey`.
520                parent_fingerprint: [0xff, 0xff, 0xff, 0xff],
521                child_number: Self::SCOPE.into(),
522                chain_code,
523            },
524        )))
525    }
526}
527
528/// An incoming viewing key at the [BIP44] "external" path
529/// `m/44'/<coin_type>'/<account>'/0`.
530///
531/// This allows derivation of child addresses that may be provided to external parties.
532///
533/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
534#[cfg(feature = "transparent-inputs")]
535#[derive(Clone, Debug)]
536pub struct ExternalIvk(ExtendedPublicKey<PublicKey>);
537
538#[cfg(feature = "transparent-inputs")]
539impl private::SealedChangeLevelKey for ExternalIvk {
540    const SCOPE: TransparentKeyScope = TransparentKeyScope(0);
541
542    fn extended_pubkey(&self) -> &ExtendedPublicKey<PublicKey> {
543        &self.0
544    }
545
546    fn from_extended_pubkey(key: ExtendedPublicKey<PublicKey>) -> Self {
547        ExternalIvk(key)
548    }
549}
550
551#[cfg(feature = "transparent-inputs")]
552impl IncomingViewingKey for ExternalIvk {}
553
554/// An incoming viewing key at the [BIP44] "internal" path
555/// `m/44'/<coin_type>'/<account>'/1`.
556///
557/// This allows derivation of change addresses for use within the wallet, but which should
558/// not be shared with external parties.
559///
560/// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
561#[cfg(feature = "transparent-inputs")]
562#[derive(Clone, Debug)]
563pub struct InternalIvk(ExtendedPublicKey<PublicKey>);
564
565#[cfg(feature = "transparent-inputs")]
566impl private::SealedChangeLevelKey for InternalIvk {
567    const SCOPE: TransparentKeyScope = TransparentKeyScope(1);
568
569    fn extended_pubkey(&self) -> &ExtendedPublicKey<PublicKey> {
570        &self.0
571    }
572
573    fn from_extended_pubkey(key: ExtendedPublicKey<PublicKey>) -> Self {
574        InternalIvk(key)
575    }
576}
577
578#[cfg(feature = "transparent-inputs")]
579impl IncomingViewingKey for InternalIvk {}
580
581/// An incoming viewing key at the "ephemeral" path
582/// `m/44'/<coin_type>'/<account>'/2`.
583///
584/// This allows derivation of ephemeral addresses for use within the wallet.
585#[cfg(feature = "transparent-inputs")]
586#[derive(Clone, Debug)]
587pub struct EphemeralIvk(ExtendedPublicKey<PublicKey>);
588
589#[cfg(feature = "transparent-inputs")]
590impl EphemeralIvk {
591    /// Derives a transparent address at the provided child index.
592    pub fn derive_ephemeral_address(
593        &self,
594        address_index: NonHardenedChildIndex,
595    ) -> Result<TransparentAddress, bip32::Error> {
596        let child_key = self.0.derive_child(address_index.into())?;
597        #[allow(deprecated)]
598        Ok(pubkey_to_address(child_key.public_key()))
599    }
600}
601
602/// Internal outgoing viewing key used for autoshielding.
603pub struct InternalOvk([u8; 32]);
604
605impl InternalOvk {
606    pub fn as_bytes(&self) -> [u8; 32] {
607        self.0
608    }
609}
610
611/// External outgoing viewing key used by `zcashd` for transparent-to-shielded spends to
612/// external receivers.
613pub struct ExternalOvk([u8; 32]);
614
615impl ExternalOvk {
616    pub fn as_bytes(&self) -> [u8; 32] {
617        self.0
618    }
619}
620
621#[cfg(test)]
622mod tests {
623    use bip32::ChildNumber;
624    use subtle::ConstantTimeEq;
625    use zcash_protocol::consensus::{NetworkConstants, MAIN_NETWORK};
626
627    use super::AccountPubKey;
628    use super::NonHardenedChildIndex;
629    #[allow(deprecated)]
630    use crate::keys::pubkey_to_address;
631    use crate::{
632        address::TransparentAddress,
633        keys::{AccountPrivKey, IncomingViewingKey, TransparentKeyScope},
634        test_vectors,
635    };
636
637    #[test]
638    #[allow(deprecated)]
639    fn address_derivation() {
640        let seed = [
641            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
642            24, 25, 26, 27, 28, 29, 30, 31,
643        ];
644
645        for account_index in 0..5 {
646            let account_index = zip32::AccountId::try_from(account_index).unwrap();
647            let account_sk =
648                AccountPrivKey::from_seed(&MAIN_NETWORK, &seed, account_index).unwrap();
649            let account_pubkey = account_sk.to_account_pubkey();
650
651            let external_ivk = account_pubkey.derive_external_ivk().unwrap();
652            let (address, address_index) = external_ivk.default_address();
653
654            let address_pubkey = account_pubkey
655                .derive_address_pubkey(TransparentKeyScope::EXTERNAL, address_index)
656                .unwrap();
657            assert_eq!(pubkey_to_address(&address_pubkey), address);
658
659            let expected_path = [
660                ChildNumber::new(44, true).unwrap(),
661                ChildNumber::new(MAIN_NETWORK.coin_type(), true).unwrap(),
662                ChildNumber::new(account_index.into(), true).unwrap(),
663                TransparentKeyScope::EXTERNAL.into(),
664                address_index.into(),
665            ];
666
667            // For short paths, we get an error.
668            for i in 0..3 {
669                assert_eq!(
670                    account_pubkey.derive_pubkey_at_bip32_path(
671                        &MAIN_NETWORK,
672                        account_index,
673                        &expected_path[..i]
674                    ),
675                    Err(bip32::Error::ChildNumber),
676                );
677            }
678
679            // The truncated-by-one path gives the external IVK.
680            assert_eq!(
681                account_pubkey.derive_pubkey_at_bip32_path(
682                    &MAIN_NETWORK,
683                    account_index,
684                    &expected_path[..4],
685                ),
686                Ok(*external_ivk.0.public_key()),
687            );
688
689            // The full path gives the correct pubkey.
690            assert_eq!(
691                account_pubkey.derive_pubkey_at_bip32_path(
692                    &MAIN_NETWORK,
693                    account_index,
694                    &expected_path,
695                ),
696                Ok(address_pubkey),
697            );
698        }
699    }
700
701    #[test]
702    #[allow(deprecated)]
703    fn bip_32_test_vectors() {
704        let seed = [
705            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
706            24, 25, 26, 27, 28, 29, 30, 31,
707        ];
708
709        for tv in test_vectors::bip_32() {
710            let account_sk = AccountPrivKey::from_seed(
711                &MAIN_NETWORK,
712                &seed,
713                zip32::AccountId::try_from(tv.account).unwrap(),
714            )
715            .unwrap();
716            let account_pubkey = account_sk.to_account_pubkey();
717
718            let mut key_bytes = [0u8; 65];
719            key_bytes[..32].copy_from_slice(&tv.c);
720            key_bytes[32..].copy_from_slice(&tv.pk);
721            assert_eq!(account_pubkey.serialize(), key_bytes);
722
723            let (internal_ovk, external_ovk) = account_pubkey.ovks_for_shielding();
724            assert_eq!(internal_ovk.as_bytes(), tv.internal_ovk);
725            assert_eq!(external_ovk.as_bytes(), tv.external_ovk);
726
727            // The test vectors are broken here: they should be deriving an address at the
728            // address level, but instead use the account pubkey as an address.
729            let address = TransparentAddress::PublicKeyHash(tv.address);
730            assert_eq!(pubkey_to_address(account_pubkey.0.public_key()), address);
731        }
732    }
733
734    #[test]
735    fn check_ovk_test_vectors() {
736        for tv in test_vectors::transparent_ovk() {
737            let mut key_bytes = [0u8; 65];
738            key_bytes[..32].copy_from_slice(&tv.c);
739            key_bytes[32..].copy_from_slice(&tv.pk);
740            let account_key = AccountPubKey::deserialize(&key_bytes).unwrap();
741
742            let (internal, external) = account_key.ovks_for_shielding();
743
744            assert_eq!(tv.internal_ovk, internal.as_bytes());
745            assert_eq!(tv.external_ovk, external.as_bytes());
746        }
747    }
748
749    #[test]
750    fn nonhardened_indexes_accepted() {
751        assert_eq!(0, NonHardenedChildIndex::from_index(0).unwrap().index());
752        assert_eq!(
753            0x7fffffff,
754            NonHardenedChildIndex::from_index(0x7fffffff)
755                .unwrap()
756                .index()
757        );
758    }
759
760    #[test]
761    fn hardened_indexes_rejected() {
762        assert!(NonHardenedChildIndex::from_index(0x80000000).is_none());
763        assert!(NonHardenedChildIndex::from_index(0xffffffff).is_none());
764    }
765
766    #[test]
767    fn nonhardened_index_next() {
768        assert_eq!(1, NonHardenedChildIndex::ZERO.next().unwrap().index());
769        assert!(NonHardenedChildIndex::from_index(0x7fffffff)
770            .unwrap()
771            .next()
772            .is_none());
773    }
774
775    #[test]
776    fn nonhardened_index_ct_eq() {
777        assert!(check(
778            NonHardenedChildIndex::ZERO,
779            NonHardenedChildIndex::ZERO
780        ));
781        assert!(!check(
782            NonHardenedChildIndex::ZERO,
783            NonHardenedChildIndex::ZERO.next().unwrap()
784        ));
785
786        fn check<T: ConstantTimeEq>(v1: T, v2: T) -> bool {
787            v1.ct_eq(&v2).into()
788        }
789    }
790
791    #[test]
792    fn nonhardened_index_tryfrom_keyindex() {
793        let nh: NonHardenedChildIndex = ChildNumber::new(0, false).unwrap().try_into().unwrap();
794        assert_eq!(nh.index(), 0);
795
796        assert!(NonHardenedChildIndex::try_from(ChildNumber::new(0, true).unwrap()).is_err());
797    }
798}