From ccbbad3e9e5b09c0bf681b527348ad9dd9e2bc4b Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Tue, 26 Jan 2021 11:48:44 -0500 Subject: [PATCH] [keys] Improve the API of `DerivableKey` A new `ExtendedKey` type has been added, which is basically an enum of `bip32::ExtendedPubKey` and `bip32::ExtendedPrivKey`, with some extra metadata regarding the `ScriptContext`. This type has some methods that make it very easy to extract its content as either an `xprv` or `xpub`. The `DerivableKey` trait has been updated so that the user now only has to implement a method (`DerivableKey::into_extended_key()`) to perform the conversion into an `ExtendedKey`. The method that was previously called `add_metadata()` has now been renamed to `into_descriptor_key()`, and it has a blanket implementation. --- CHANGELOG.md | 7 ++ src/keys/bip39.rs | 60 +++++++++--- src/keys/mod.rs | 245 +++++++++++++++++++++++++++++++++++++++------- 3 files changed, 263 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be596822..6578d309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Keys +#### Changed +- Renamed `DerivableKey::add_metadata()` to `DerivableKey::into_descriptor_key()` +#### Added +- Added an `ExtendedKey` type that is an enum of `bip32::ExtendedPubKey` and `bip32::ExtendedPrivKey` +- Added `DerivableKey::into_extended_key()` as the only method that needs to be implemented + ### Misc #### Added - Added a function to get the version of BDK at runtime diff --git a/src/keys/bip39.rs b/src/keys/bip39.rs index b3223740..8ea2aeb8 100644 --- a/src/keys/bip39.rs +++ b/src/keys/bip39.rs @@ -32,51 +32,81 @@ use bitcoin::Network; use miniscript::ScriptContext; -use bip39::{Language, Mnemonic, MnemonicType, Seed}; +pub use bip39::{Language, Mnemonic, MnemonicType, Seed}; -use super::{any_network, DerivableKey, DescriptorKey, GeneratableKey, GeneratedKey, KeyError}; +use super::{ + any_network, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, KeyError, +}; + +fn set_valid_on_any_network( + descriptor_key: DescriptorKey, +) -> DescriptorKey { + // We have to pick one network to build the xprv, but since the bip39 standard doesn't + // encode the network, the xprv we create is actually valid everywhere. So we override the + // valid networks with `any_network()`. + descriptor_key.override_valid_networks(any_network()) +} /// Type for a BIP39 mnemonic with an optional passphrase pub type MnemonicWithPassphrase = (Mnemonic, Option); #[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))] impl DerivableKey for Seed { - fn add_metadata( + fn into_extended_key(self) -> Result, KeyError> { + Ok(bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.as_bytes())?.into()) + } + + fn into_descriptor_key( self, source: Option, derivation_path: bip32::DerivationPath, ) -> Result, KeyError> { - let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.as_bytes())?; - let descriptor_key = xprv.add_metadata(source, derivation_path)?; + let descriptor_key = self + .into_extended_key()? + .into_descriptor_key(source, derivation_path)?; - // here we must choose one network to build the xpub, but since the bip39 standard doesn't - // encode the network, the xpub we create is actually valid everywhere. so we override the - // valid networks with `any_network()`. - Ok(descriptor_key.override_valid_networks(any_network())) + Ok(set_valid_on_any_network(descriptor_key)) } } #[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))] impl DerivableKey for MnemonicWithPassphrase { - fn add_metadata( + fn into_extended_key(self) -> Result, KeyError> { + let (mnemonic, passphrase) = self; + let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or("")); + + seed.into_extended_key() + } + + fn into_descriptor_key( self, source: Option, derivation_path: bip32::DerivationPath, ) -> Result, KeyError> { - let (mnemonic, passphrase) = self; - let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or("")); - seed.add_metadata(source, derivation_path) + let descriptor_key = self + .into_extended_key()? + .into_descriptor_key(source, derivation_path)?; + + Ok(set_valid_on_any_network(descriptor_key)) } } #[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))] impl DerivableKey for Mnemonic { - fn add_metadata( + fn into_extended_key(self) -> Result, KeyError> { + (self, None).into_extended_key() + } + + fn into_descriptor_key( self, source: Option, derivation_path: bip32::DerivationPath, ) -> Result, KeyError> { - (self, None).add_metadata(source, derivation_path) + let descriptor_key = self + .into_extended_key()? + .into_descriptor_key(source, derivation_path)?; + + Ok(set_valid_on_any_network(descriptor_key)) } } diff --git a/src/keys/mod.rs b/src/keys/mod.rs index 68872800..1df214cf 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -30,7 +30,7 @@ use std::marker::PhantomData; use std::ops::Deref; use std::str::FromStr; -use bitcoin::secp256k1; +use bitcoin::secp256k1::{self, Secp256k1, Signing}; use bitcoin::util::bip32; use bitcoin::{Network, PrivateKey, PublicKey}; @@ -299,6 +299,69 @@ pub trait ToDescriptorKey: Sized { fn to_descriptor_key(self) -> Result, KeyError>; } +/// Enum for extended keys that can be either `xprv` or `xpub` +/// +/// An instance of [`ExtendedKey`] can be constructed from an [`ExtendedPrivKey`](bip32::ExtendedPrivKey) +/// or an [`ExtendedPubKey`](bip32::ExtendedPubKey) by using the `From` trait. +/// +/// Defaults to the [`Legacy`](miniscript::Legacy) context. +pub enum ExtendedKey { + /// A private extended key, aka an `xprv` + Private((bip32::ExtendedPrivKey, PhantomData)), + /// A public extended key, aka an `xpub` + Public((bip32::ExtendedPubKey, PhantomData)), +} + +impl ExtendedKey { + /// Return whether or not the key contains the private data + pub fn has_secret(&self) -> bool { + match self { + ExtendedKey::Private(_) => true, + ExtendedKey::Public(_) => false, + } + } + + /// Transform the [`ExtendedKey`] into an [`ExtendedPrivKey`](bip32::ExtendedPrivKey) for the + /// given [`Network`], if the key contains the private data + pub fn into_xprv(self, network: Network) -> Option { + match self { + ExtendedKey::Private((mut xprv, _)) => { + xprv.network = network; + Some(xprv) + } + ExtendedKey::Public(_) => None, + } + } + + /// Transform the [`ExtendedKey`] into an [`ExtendedPubKey`](bip32::ExtendedPubKey) for the + /// given [`Network`] + pub fn into_xpub( + self, + network: bitcoin::Network, + secp: &Secp256k1, + ) -> bip32::ExtendedPubKey { + let mut xpub = match self { + ExtendedKey::Private((xprv, _)) => bip32::ExtendedPubKey::from_private(secp, &xprv), + ExtendedKey::Public((xpub, _)) => xpub, + }; + + xpub.network = network; + xpub + } +} + +impl From for ExtendedKey { + fn from(xpub: bip32::ExtendedPubKey) -> Self { + ExtendedKey::Public((xpub, PhantomData)) + } +} + +impl From for ExtendedKey { + fn from(xprv: bip32::ExtendedPrivKey) -> Self { + ExtendedKey::Private((xprv, PhantomData)) + } +} + /// Trait for keys that can be derived. /// /// When extra metadata are provided, a [`DerivableKey`] can be transofrmed into a @@ -310,45 +373,155 @@ pub trait ToDescriptorKey: Sized { /// generally recommended to implemented this trait instead of [`ToDescriptorKey`]. The same /// rules regarding script context and valid networks apply. /// +/// ## Examples +/// +/// Key types that can be directly converted into an [`ExtendedPrivKey`] or +/// an [`ExtendedPubKey`] can implement only the required `into_extended_key()` method. +/// +/// ``` +/// use bdk::bitcoin; +/// use bdk::bitcoin::util::bip32; +/// use bdk::keys::{DerivableKey, ExtendedKey, KeyError, ScriptContext}; +/// +/// struct MyCustomKeyType { +/// key_data: bitcoin::PrivateKey, +/// chain_code: Vec, +/// network: bitcoin::Network, +/// } +/// +/// impl DerivableKey for MyCustomKeyType { +/// fn into_extended_key(self) -> Result, KeyError> { +/// let xprv = bip32::ExtendedPrivKey { +/// network: self.network, +/// depth: 0, +/// parent_fingerprint: bip32::Fingerprint::default(), +/// private_key: self.key_data, +/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()), +/// child_number: bip32::ChildNumber::Normal { index: 0 }, +/// }; +/// +/// xprv.into_extended_key() +/// } +/// } +/// ``` +/// +/// Types that don't internally encode the [`Network`](bitcoin::Network) in which they are valid need some extra +/// steps to override the set of valid networks, otherwise only the network specified in the +/// [`ExtendedPrivKey`] or [`ExtendedPubKey`] will be considered valid. +/// +/// ``` +/// use bdk::bitcoin; +/// use bdk::bitcoin::util::bip32; +/// use bdk::keys::{ +/// any_network, DerivableKey, DescriptorKey, ExtendedKey, KeyError, ScriptContext, +/// }; +/// +/// struct MyCustomKeyType { +/// key_data: bitcoin::PrivateKey, +/// chain_code: Vec, +/// } +/// +/// impl DerivableKey for MyCustomKeyType { +/// fn into_extended_key(self) -> Result, KeyError> { +/// let xprv = bip32::ExtendedPrivKey { +/// network: bitcoin::Network::Bitcoin, // pick an arbitrary network here +/// depth: 0, +/// parent_fingerprint: bip32::Fingerprint::default(), +/// private_key: self.key_data, +/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()), +/// child_number: bip32::ChildNumber::Normal { index: 0 }, +/// }; +/// +/// xprv.into_extended_key() +/// } +/// +/// fn into_descriptor_key( +/// self, +/// source: Option, +/// derivation_path: bip32::DerivationPath, +/// ) -> Result, KeyError> { +/// let descriptor_key = self +/// .into_extended_key()? +/// .into_descriptor_key(source, derivation_path)?; +/// +/// // Override the set of valid networks here +/// Ok(descriptor_key.override_valid_networks(any_network())) +/// } +/// } +/// ``` +/// /// [`DerivationPath`]: (bip32::DerivationPath) -pub trait DerivableKey { - /// Add a extra metadata, consume `self` and turn it into a [`DescriptorKey`] - fn add_metadata( - self, - origin: Option, - derivation_path: bip32::DerivationPath, - ) -> Result, KeyError>; -} +/// [`ExtendedPrivKey`]: (bip32::ExtendedPrivKey) +/// [`ExtendedPubKey`]: (bip32::ExtendedPubKey) +pub trait DerivableKey: Sized { + /// Consume `self` and turn it into an [`ExtendedKey`] + /// + /// This can be used to get direct access to `xprv`s and `xpub`s for types that implement this trait, + /// like [`Mnemonic`](bip39::Mnemonic) when the `keys-bip39` feature is enabled. + #[cfg_attr( + feature = "keys-bip39", + doc = r##" +```rust +use bdk::bitcoin::Network; +use bdk::keys::{DerivableKey, ExtendedKey}; +use bdk::keys::bip39::{Mnemonic, Language}; -impl DerivableKey for bip32::ExtendedPubKey { - fn add_metadata( +# fn main() -> Result<(), Box> { +let xkey: ExtendedKey = + Mnemonic::from_phrase( + "jelly crash boy whisper mouse ecology tuna soccer memory million news short", + Language::English + )? + .into_extended_key()?; +let xprv = xkey.into_xprv(Network::Bitcoin).unwrap(); +# Ok(()) } +``` +"## + )] + fn into_extended_key(self) -> Result, KeyError>; + + /// Consume `self` and turn it into a [`DescriptorKey`] by adding the extra metadata, such as + /// key origin and derivation path + fn into_descriptor_key( self, origin: Option, derivation_path: bip32::DerivationPath, ) -> Result, KeyError> { - DescriptorPublicKey::XPub(DescriptorXKey { - origin, - xkey: self, - derivation_path, - is_wildcard: true, - }) - .to_descriptor_key() + match self.into_extended_key()? { + ExtendedKey::Private((xprv, _)) => DescriptorSecretKey::XPrv(DescriptorXKey { + origin, + xkey: xprv, + derivation_path, + is_wildcard: true, + }) + .to_descriptor_key(), + ExtendedKey::Public((xpub, _)) => DescriptorPublicKey::XPub(DescriptorXKey { + origin, + xkey: xpub, + derivation_path, + is_wildcard: true, + }) + .to_descriptor_key(), + } + } +} + +/// Identity conversion +impl DerivableKey for ExtendedKey { + fn into_extended_key(self) -> Result, KeyError> { + Ok(self) + } +} + +impl DerivableKey for bip32::ExtendedPubKey { + fn into_extended_key(self) -> Result, KeyError> { + Ok(self.into()) } } impl DerivableKey for bip32::ExtendedPrivKey { - fn add_metadata( - self, - origin: Option, - derivation_path: bip32::DerivationPath, - ) -> Result, KeyError> { - DescriptorSecretKey::XPrv(DescriptorXKey { - origin, - xkey: self, - derivation_path, - is_wildcard: true, - }) - .to_descriptor_key() + fn into_extended_key(self) -> Result, KeyError> { + Ok(self.into()) } } @@ -389,12 +562,16 @@ where Ctx: ScriptContext, K: DerivableKey, { - fn add_metadata( + fn into_extended_key(self) -> Result, KeyError> { + self.key.into_extended_key() + } + + fn into_descriptor_key( self, origin: Option, derivation_path: bip32::DerivationPath, ) -> Result, KeyError> { - let descriptor_key = self.key.add_metadata(origin, derivation_path)?; + let descriptor_key = self.key.into_descriptor_key(origin, derivation_path)?; Ok(descriptor_key.override_valid_networks(self.valid_networks)) } } @@ -531,7 +708,7 @@ impl GeneratableKey for PrivateKey { impl> ToDescriptorKey for (T, bip32::DerivationPath) { fn to_descriptor_key(self) -> Result, KeyError> { - self.0.add_metadata(None, self.1) + self.0.into_descriptor_key(None, self.1) } } @@ -539,7 +716,7 @@ impl> ToDescriptorKey for (T, bip32::KeySource, bip32::DerivationPath) { fn to_descriptor_key(self) -> Result, KeyError> { - self.0.add_metadata(Some(self.1), self.2) + self.0.into_descriptor_key(Some(self.1), self.2) } }