diff --git a/pczt/src/roles/signer/mod.rs b/pczt/src/roles/signer/mod.rs index b929e5464..a75b86dcf 100644 --- a/pczt/src/roles/signer/mod.rs +++ b/pczt/src/roles/signer/mod.rs @@ -139,6 +139,9 @@ impl Signer { /// Signs the Sapling spend at the given index with the given spend authorizing key. /// + /// Requires the spend's `proof_generation_key` field to be set (because the API does + /// not take an FVK). + /// /// It is the caller's responsibility to perform any semantic validity checks on the /// PCZT (for example, comfirming that the change amounts are correct) before calling /// this method. @@ -179,6 +182,8 @@ impl Signer { /// Signs the Orchard spend at the given index with the given spend authorizing key. /// + /// Requires the spend's `fvk` field to be set (because the API does not take an FVK). + /// /// It is the caller's responsibility to perform any semantic validity checks on the /// PCZT (for example, comfirming that the change amounts are correct) before calling /// this method. diff --git a/zcash_transparent/CHANGELOG.md b/zcash_transparent/CHANGELOG.md index 35cc8874e..46dc2a245 100644 --- a/zcash_transparent/CHANGELOG.md +++ b/zcash_transparent/CHANGELOG.md @@ -11,6 +11,9 @@ and this library adheres to Rust's notion of - `zcash_transparent::keys::AccountPubKey::derive_pubkey_at_bip32_path` now returns the correct result for valid paths instead of an error or panic. +### Added +- `zcash_transparent::pczt::Bip32Derivation::extract_bip_44_fields` + ## [0.1.0] - 2024-12-16 The entries below are relative to the `zcash_primitives` crate as of the tag diff --git a/zcash_transparent/src/keys.rs b/zcash_transparent/src/keys.rs index 7bd310448..74c6dccb3 100644 --- a/zcash_transparent/src/keys.rs +++ b/zcash_transparent/src/keys.rs @@ -1,19 +1,20 @@ //! Transparent key components. -use alloc::string::ToString; -use alloc::vec::Vec; -use bip32::{ - ChildNumber, ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey, ExtendedPublicKey, Prefix, -}; -use secp256k1::PublicKey; -use sha2::{Digest, Sha256}; +use bip32::ChildNumber; use subtle::{Choice, ConstantTimeEq}; -use zcash_protocol::consensus::{self, NetworkConstants}; -use zcash_spec::PrfExpand; -use zip32::AccountId; - -use crate::address::TransparentAddress; +#[cfg(feature = "transparent-inputs")] +use { + crate::address::TransparentAddress, + alloc::string::ToString, + alloc::vec::Vec, + bip32::{ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey, ExtendedPublicKey, Prefix}, + secp256k1::PublicKey, + sha2::{Digest, Sha256}, + zcash_protocol::consensus::{self, NetworkConstants}, + zcash_spec::PrfExpand, + zip32::AccountId, +}; /// The scope of a transparent key. /// @@ -123,8 +124,10 @@ impl From for ChildNumber { /// /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki #[derive(Clone, Debug)] +#[cfg(feature = "transparent-inputs")] pub struct AccountPrivKey(ExtendedPrivateKey); +#[cfg(feature = "transparent-inputs")] impl AccountPrivKey { /// Performs derivation of the extended private key for the BIP44 path: /// `m/44'/'/'`. @@ -221,9 +224,11 @@ impl AccountPrivKey { /// full viewing key. /// /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +#[cfg(feature = "transparent-inputs")] #[derive(Clone, Debug)] pub struct AccountPubKey(ExtendedPublicKey); +#[cfg(feature = "transparent-inputs")] impl AccountPubKey { /// Derives the BIP44 public key at the external "change level" path /// `m/44'/'/'/0`. @@ -344,6 +349,7 @@ impl AccountPubKey { } /// Derives the P2PKH transparent address corresponding to the given pubkey. +#[cfg(feature = "transparent-inputs")] #[deprecated(note = "This function will be removed from the public API in an upcoming refactor.")] pub fn pubkey_to_address(pubkey: &secp256k1::PublicKey) -> TransparentAddress { TransparentAddress::PublicKeyHash( @@ -351,6 +357,7 @@ pub fn pubkey_to_address(pubkey: &secp256k1::PublicKey) -> TransparentAddress { ) } +#[cfg(feature = "transparent-inputs")] pub(crate) mod private { use super::TransparentKeyScope; use bip32::ExtendedPublicKey; @@ -377,6 +384,7 @@ pub(crate) mod private { /// /// [BIP32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +#[cfg(feature = "transparent-inputs")] pub trait IncomingViewingKey: private::SealedChangeLevelKey + core::marker::Sized { /// Derives a transparent address at the provided child index. #[allow(deprecated)] @@ -438,9 +446,11 @@ pub trait IncomingViewingKey: private::SealedChangeLevelKey + core::marker::Size /// This allows derivation of child addresses that may be provided to external parties. /// /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +#[cfg(feature = "transparent-inputs")] #[derive(Clone, Debug)] pub struct ExternalIvk(ExtendedPublicKey); +#[cfg(feature = "transparent-inputs")] impl private::SealedChangeLevelKey for ExternalIvk { const SCOPE: TransparentKeyScope = TransparentKeyScope(0); @@ -453,6 +463,7 @@ impl private::SealedChangeLevelKey for ExternalIvk { } } +#[cfg(feature = "transparent-inputs")] impl IncomingViewingKey for ExternalIvk {} /// An incoming viewing key at the [BIP44] "internal" path @@ -462,9 +473,11 @@ impl IncomingViewingKey for ExternalIvk {} /// not be shared with external parties. /// /// [BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +#[cfg(feature = "transparent-inputs")] #[derive(Clone, Debug)] pub struct InternalIvk(ExtendedPublicKey); +#[cfg(feature = "transparent-inputs")] impl private::SealedChangeLevelKey for InternalIvk { const SCOPE: TransparentKeyScope = TransparentKeyScope(1); @@ -477,12 +490,14 @@ impl private::SealedChangeLevelKey for InternalIvk { } } +#[cfg(feature = "transparent-inputs")] impl IncomingViewingKey for InternalIvk {} /// An incoming viewing key at the "ephemeral" path /// `m/44'/'/'/2`. /// /// This allows derivation of ephemeral addresses for use within the wallet. +#[cfg(feature = "transparent-inputs")] #[derive(Clone, Debug)] pub struct EphemeralIvk(ExtendedPublicKey); diff --git a/zcash_transparent/src/lib.rs b/zcash_transparent/src/lib.rs index 7b93d735c..e42725871 100644 --- a/zcash_transparent/src/lib.rs +++ b/zcash_transparent/src/lib.rs @@ -9,12 +9,10 @@ pub mod address; pub mod builder; pub mod bundle; +pub mod keys; pub mod pczt; pub mod sighash; -#[cfg(feature = "transparent-inputs")] -pub mod keys; - #[cfg(test)] mod test_vectors; diff --git a/zcash_transparent/src/pczt.rs b/zcash_transparent/src/pczt.rs index faf053031..5f6837613 100644 --- a/zcash_transparent/src/pczt.rs +++ b/zcash_transparent/src/pczt.rs @@ -8,7 +8,11 @@ use bip32::ChildNumber; use getset::Getters; use zcash_protocol::{value::Zatoshis, TxId}; -use crate::{address::Script, sighash::SighashType}; +use crate::{ + address::Script, + keys::{NonHardenedChildIndex, TransparentKeyScope}, + sighash::SighashType, +}; mod parse; pub use parse::ParseError; @@ -230,3 +234,43 @@ pub struct Bip32Derivation { /// The sequence of indices corresponding to the HD path. derivation_path: Vec, } + +impl Bip32Derivation { + /// Extracts the BIP 44 account index, scope, and address index from this derivation + /// path. + /// + /// Returns `None` if the seed fingerprints don't match, or if this is a non-standard + /// derivation path. + pub fn extract_bip_44_fields( + &self, + seed_fp: &zip32::fingerprint::SeedFingerprint, + expected_coin_type: ChildNumber, + ) -> Option<(zip32::AccountId, TransparentKeyScope, NonHardenedChildIndex)> { + if self.seed_fingerprint == seed_fp.to_bytes() { + match &self.derivation_path[..] { + [purpose, coin_type, account_index, scope, address_index] + if purpose == &ChildNumber(44 | ChildNumber::HARDENED_FLAG) + && coin_type.is_hardened() + && coin_type == &expected_coin_type + && account_index.is_hardened() + && !scope.is_hardened() + && !address_index.is_hardened() => + { + let account_index = zip32::AccountId::try_from(account_index.index()) + .expect("account_index is hardened"); + + let scope = + TransparentKeyScope::custom(scope.index()).expect("scope is not hardened"); + + let address_index = NonHardenedChildIndex::from_index(address_index.index()) + .expect("address_index is not hardened"); + + Some((account_index, scope, address_index)) + } + _ => None, + } + } else { + None + } + } +}