Merge bitcoindevkit/bdk-ffi#154: Add child key pair generation api

5944756b780968588e97886d888bb854aa2053ad Added tests for DescriptorSecretKey and DescriptorPublicKey (dhruvbaliyan)
58fea6b2050d9f10b1c5429cce4f4969c155e9e3 Added interfaces DescriptorSecretKey and DescriptorPublicKey (dhruvbaliyan)
4977cb6d68830accf036b192dba3c524253e53f2 Added interface DerivationPath (dhruvbaliyan)
930a1f1eb4a64bf447dec2493edd9948be67f119 Added generate_mnemonic method (dhruvbaliyan)
973013cbdfb0093c9d0bc8d2f5176c981160e060 Removed ExtendedKeyInfo & related methods (dhruvbaliyan)

Pull request description:

  Would like to know if anything can be improved. Completes #87

Top commit has no ACKs.

Tree-SHA512: a480535c8965015d860336c717ec3c394778ac08194b0336eeba4209f3e3eff2072873a190dd8c9e4fac1e2f712c7040c838dc1c1a757d53c28866f118c99c17
This commit is contained in:
Steve Myers 2022-08-18 12:13:31 -05:00
commit 7da28658a5
No known key found for this signature in database
GPG Key ID: 8105A46B22C2D051
3 changed files with 318 additions and 52 deletions

View File

@ -5,6 +5,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
- APIs Added [#154]
- `generate_mnemonic()`, returns string mnemonic
- `interface DescriptorSecretKey`
- `new(Network, string_mnenoinc, password)`, contructs DescriptorSecretKey
- `derive(DerivationPath)`, derives and returns child DescriptorSecretKey
- `extend(DerivationPath)`, extends and returns DescriptorSecretKey
- `as_public()`, returns DescriptorSecretKey as DescriptorPublicKey
- `as_string()`, returns DescriptorSecretKey as String
- `interface DescriptorPublicKey`
- `derive(DerivationPath)` derives and returns child DescriptorPublicKey
- `extend(DerivationPath)` extends and returns DescriptorPublicKey
- `as_string()` returns DescriptorPublicKey as String
- Interfaces Added [#154]
- `DescriptorSecretKey`
- `DescriptorPublicKey`
- `DerivationPath`
- Dictionary Removed [#154]
- `ExtendedKeyInfo {mnenonic, xprv, fingerprint}`
- APIs Removed [#154]
- `generate_extended_key`, returned ExtendedKeyInfo
- `restore_extended_key`, returned ExtendedKeyInfo
[#154]: https://github.com/bitcoindevkit/bdk-ffi/pull/154
## [v0.8.0]
- Update BDK to version 0.20.0 [#169]

View File

@ -1,9 +1,6 @@
namespace bdk {
[Throws=BdkError]
ExtendedKeyInfo generate_extended_key(Network network, WordCount word_count, string? password);
[Throws=BdkError]
ExtendedKeyInfo restore_extended_key(Network network, string mnemonic, string? password);
string generate_mnemonic(WordCount word_count);
};
[Error]
@ -101,12 +98,6 @@ interface Transaction {
Confirmed(TransactionDetails details, BlockTime confirmation);
};
dictionary ExtendedKeyInfo {
string mnemonic;
string xprv;
string fingerprint;
};
enum WordCount {
"Words12",
"Words15",
@ -254,3 +245,31 @@ interface BumpFeeTxBuilder {
[Throws=BdkError]
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet);
};
interface DerivationPath {
[Throws=BdkError]
constructor(string path);
};
interface DescriptorSecretKey {
[Throws=BdkError]
constructor(Network network, string mnemonic, string? password);
[Throws=BdkError]
DescriptorSecretKey derive(DerivationPath path);
DescriptorSecretKey extend(DerivationPath path);
DescriptorPublicKey as_public();
string as_string();
};
interface DescriptorPublicKey {
[Throws=BdkError]
DescriptorPublicKey derive(DerivationPath path);
DescriptorPublicKey extend(DerivationPath path);
string as_string();
};

View File

@ -1,5 +1,6 @@
use bdk::bitcoin::hashes::hex::ToHex;
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
use bdk::bitcoin::{Address, Network, OutPoint as BdkOutPoint, Script, Txid};
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
@ -9,8 +10,12 @@ use bdk::blockchain::{
use bdk::blockchain::{Blockchain as BdkBlockchain, Progress as BdkProgress};
use bdk::database::any::{AnyDatabase, SledDbConfiguration, SqliteDbConfiguration};
use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
use bdk::descriptor::DescriptorXKey;
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey};
use bdk::keys::{
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
};
use bdk::miniscript::BareCtx;
use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::wallet::AddressIndex as BdkAddressIndex;
@ -351,46 +356,6 @@ impl Wallet {
}
}
pub struct ExtendedKeyInfo {
mnemonic: String,
xprv: String,
fingerprint: String,
}
fn generate_extended_key(
network: Network,
word_count: WordCount,
password: Option<String>,
) -> Result<ExtendedKeyInfo, Error> {
let mnemonic: GeneratedKey<_, BareCtx> =
Mnemonic::generate((word_count, Language::English)).unwrap();
let mnemonic = mnemonic.into_key();
let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?;
let xprv = xkey.into_xprv(network).unwrap();
let fingerprint = xprv.fingerprint(&Secp256k1::new());
Ok(ExtendedKeyInfo {
mnemonic: mnemonic.to_string(),
xprv: xprv.to_string(),
fingerprint: fingerprint.to_string(),
})
}
fn restore_extended_key(
network: Network,
mnemonic: String,
password: Option<String>,
) -> Result<ExtendedKeyInfo, Error> {
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?;
let xprv = xkey.into_xprv(network).unwrap();
let fingerprint = xprv.fingerprint(&Secp256k1::new());
Ok(ExtendedKeyInfo {
mnemonic: mnemonic.to_string(),
xprv: xprv.to_string(),
fingerprint: fingerprint.to_string(),
})
}
fn to_script_pubkey(address: &str) -> Result<Script, BdkError> {
Address::from_str(address)
.map(|x| x.script_pubkey())
@ -667,6 +632,173 @@ impl BumpFeeTxBuilder {
}
}
fn generate_mnemonic(word_count: WordCount) -> Result<String, BdkError> {
let mnemonic: GeneratedKey<_, BareCtx> =
Mnemonic::generate((word_count, Language::English)).unwrap();
Ok(mnemonic.to_string())
}
struct DerivationPath {
derivation_path_mutex: Mutex<BdkDerivationPath>,
}
impl DerivationPath {
fn new(path: String) -> Result<Self, BdkError> {
BdkDerivationPath::from_str(&path)
.map(|x| DerivationPath {
derivation_path_mutex: Mutex::new(x),
})
.map_err(|e| BdkError::Generic(e.to_string()))
}
}
struct DescriptorSecretKey {
descriptor_secret_key_mutex: Mutex<BdkDescriptorSecretKey>,
}
impl DescriptorSecretKey {
fn new(network: Network, mnemonic: String, password: Option<String>) -> Result<Self, BdkError> {
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic)
.map_err(|e| BdkError::Generic(e.to_string()))?;
let xkey: ExtendedKey = (mnemonic, password).into_extended_key()?;
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: None,
xkey: xkey.into_xprv(network).unwrap(),
derivation_path: BdkDerivationPath::master(),
wildcard: bdk::descriptor::Wildcard::Unhardened,
});
Ok(Self {
descriptor_secret_key_mutex: Mutex::new(descriptor_secret_key),
})
}
fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
let secp = Secp256k1::new();
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
match descriptor_secret_key.deref() {
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derived_xprv = descriptor_x_key.xkey.derive_priv(&secp, &path)?;
let key_source = match descriptor_x_key.origin.clone() {
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
None => (descriptor_x_key.xkey.fingerprint(&secp), path),
};
let derived_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: Some(key_source),
xkey: derived_xprv,
derivation_path: BdkDerivationPath::default(),
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
descriptor_secret_key_mutex: Mutex::new(derived_descriptor_secret_key),
}))
}
BdkDescriptorSecretKey::SinglePriv(_) => {
unreachable!()
}
}
}
fn extend(&self, path: Arc<DerivationPath>) -> Arc<Self> {
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
match descriptor_secret_key.deref() {
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let extended_path = descriptor_x_key.derivation_path.extend(path);
let extended_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: descriptor_x_key.origin.clone(),
xkey: descriptor_x_key.xkey,
derivation_path: extended_path,
wildcard: descriptor_x_key.wildcard,
});
Arc::new(Self {
descriptor_secret_key_mutex: Mutex::new(extended_descriptor_secret_key),
})
}
BdkDescriptorSecretKey::SinglePriv(_) => {
unreachable!()
}
}
}
fn as_public(&self) -> Arc<DescriptorPublicKey> {
let secp = Secp256k1::new();
let descriptor_public_key = self
.descriptor_secret_key_mutex
.lock()
.unwrap()
.as_public(&secp)
.unwrap();
Arc::new(DescriptorPublicKey {
descriptor_public_key_mutex: Mutex::new(descriptor_public_key),
})
}
fn as_string(&self) -> String {
self.descriptor_secret_key_mutex.lock().unwrap().to_string()
}
}
struct DescriptorPublicKey {
descriptor_public_key_mutex: Mutex<BdkDescriptorPublicKey>,
}
impl DescriptorPublicKey {
fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
let secp = Secp256k1::new();
let descriptor_public_key = self.descriptor_public_key_mutex.lock().unwrap();
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
match descriptor_public_key.deref() {
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derived_xpub = descriptor_x_key.xkey.derive_pub(&secp, &path)?;
let key_source = match descriptor_x_key.origin.clone() {
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
None => (descriptor_x_key.xkey.fingerprint(), path),
};
let derived_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
origin: Some(key_source),
xkey: derived_xpub,
derivation_path: BdkDerivationPath::default(),
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
descriptor_public_key_mutex: Mutex::new(derived_descriptor_public_key),
}))
}
BdkDescriptorPublicKey::SinglePub(_) => {
unreachable!()
}
}
}
fn extend(&self, path: Arc<DerivationPath>) -> Arc<Self> {
let descriptor_public_key = self.descriptor_public_key_mutex.lock().unwrap();
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
match descriptor_public_key.deref() {
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let extended_path = descriptor_x_key.derivation_path.extend(path);
let extended_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
origin: descriptor_x_key.origin.clone(),
xkey: descriptor_x_key.xkey,
derivation_path: extended_path,
wildcard: descriptor_x_key.wildcard,
});
Arc::new(Self {
descriptor_public_key_mutex: Mutex::new(extended_descriptor_public_key),
})
}
BdkDescriptorPublicKey::SinglePub(_) => {
unreachable!()
}
}
}
fn as_string(&self) -> String {
self.descriptor_public_key_mutex.lock().unwrap().to_string()
}
}
uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
@ -674,7 +806,7 @@ uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
// crate.
#[cfg(test)]
mod test {
use crate::{TxBuilder, Wallet};
use crate::*;
use bdk::bitcoin::Address;
use bdk::bitcoin::Network::Testnet;
use bdk::wallet::get_funded_wallet;
@ -734,4 +866,96 @@ mod test {
let output_value = psbt.unsigned_tx.output.get(0).cloned().unwrap().value;
assert_eq!(output_value, 49_890_u64); // input - fee
}
fn get_descriptor_secret_key() -> DescriptorSecretKey {
let mnemonic =
"chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect".to_string();
DescriptorSecretKey::new(Network::Testnet, mnemonic, None).unwrap()
}
fn derive_dsk(
key: &DescriptorSecretKey,
path: &str,
) -> Result<Arc<DescriptorSecretKey>, BdkError> {
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
key.derive(path)
}
fn extend_dsk(key: &DescriptorSecretKey, path: &str) -> Arc<DescriptorSecretKey> {
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
key.extend(path)
}
fn derive_dpk(
key: &DescriptorPublicKey,
path: &str,
) -> Result<Arc<DescriptorPublicKey>, BdkError> {
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
key.derive(path)
}
fn extend_dpk(key: &DescriptorPublicKey, path: &str) -> Arc<DescriptorPublicKey> {
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
key.extend(path)
}
#[test]
fn test_generate_descriptor_secret_key() {
let master_dsk = get_descriptor_secret_key();
assert_eq!(master_dsk.as_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
assert_eq!(master_dsk.as_public().as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
}
#[test]
fn test_derive_self() {
let master_dsk = get_descriptor_secret_key();
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m").unwrap();
assert_eq!(derived_dsk.as_string(), "[d1d04177]tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m").unwrap();
assert_eq!(derived_dpk.as_string(), "[d1d04177]tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
}
#[test]
fn test_derive_descriptors_keys() {
let master_dsk = get_descriptor_secret_key();
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m/0").unwrap();
assert_eq!(derived_dpk.as_string(), "[d1d04177/0]tpubD9oaCiP1MPmQdndm7DCD3D3QU34pWd6BbKSRedoZF1UJcNhEk3PJwkALNYkhxeTKL29oGNR7psqvT1KZydCGqUDEKXN6dVQJY2R8ooLPy8m/*");
}
#[test]
fn test_extend_descriptor_keys() {
let master_dsk = get_descriptor_secret_key();
let extended_dsk: &DescriptorSecretKey = &extend_dsk(&master_dsk, "m/0");
assert_eq!(extended_dsk.as_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/0/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let extended_dpk: &DescriptorPublicKey = &extend_dpk(master_dpk, "m/0");
assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*");
}
#[test]
fn test_derive_and_extend_descriptor_secret_key() {
let master_dsk = get_descriptor_secret_key();
// derive DescriptorSecretKey with path "m/0" from master
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
// extend derived_dsk with path "m/0"
let extended_dsk: &DescriptorSecretKey = &extend_dsk(derived_dsk, "m/0");
assert_eq!(extended_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/0/*");
}
#[test]
fn test_derive_hardened_path_using_public() {
let master_dpk = get_descriptor_secret_key().as_public();
let derived_dpk = &derive_dpk(&master_dpk, "m/84h/1h/0h");
assert!(derived_dpk.is_err());
}
}