From 973013cbdfb0093c9d0bc8d2f5176c981160e060 Mon Sep 17 00:00:00 2001 From: dhruvbaliyan Date: Tue, 9 Aug 2022 14:20:14 +0530 Subject: [PATCH 1/5] Removed ExtendedKeyInfo & related methods --- src/bdk.udl | 14 +------------- src/lib.rs | 40 ---------------------------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index d98cfec..acbd439 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -1,10 +1,4 @@ -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); -}; +namespace bdk {}; [Error] enum BdkError { @@ -101,12 +95,6 @@ interface Transaction { Confirmed(TransactionDetails details, BlockTime confirmation); }; -dictionary ExtendedKeyInfo { - string mnemonic; - string xprv; - string fingerprint; -}; - enum WordCount { "Words12", "Words15", diff --git a/src/lib.rs b/src/lib.rs index 95243cb..f84c08a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -351,46 +351,6 @@ impl Wallet { } } -pub struct ExtendedKeyInfo { - mnemonic: String, - xprv: String, - fingerprint: String, -} - -fn generate_extended_key( - network: Network, - word_count: WordCount, - password: Option, -) -> Result { - 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, -) -> Result { - 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 { Address::from_str(address) .map(|x| x.script_pubkey()) From 930a1f1eb4a64bf447dec2493edd9948be67f119 Mon Sep 17 00:00:00 2001 From: dhruvbaliyan Date: Tue, 9 Aug 2022 14:23:17 +0530 Subject: [PATCH 2/5] Added generate_mnemonic method --- src/bdk.udl | 5 ++++- src/lib.rs | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/bdk.udl b/src/bdk.udl index acbd439..15ef4b0 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -1,4 +1,7 @@ -namespace bdk {}; +namespace bdk { + [Throws=BdkError] + string generate_mnemonic(WordCount word_count); +}; [Error] enum BdkError { diff --git a/src/lib.rs b/src/lib.rs index f84c08a..06212e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -627,6 +627,12 @@ impl BumpFeeTxBuilder { } } +fn generate_mnemonic(word_count: WordCount) -> Result { + let mnemonic: GeneratedKey<_, BareCtx> = + Mnemonic::generate((word_count, Language::English)).unwrap(); + Ok(mnemonic.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. From 4977cb6d68830accf036b192dba3c524253e53f2 Mon Sep 17 00:00:00 2001 From: dhruvbaliyan Date: Tue, 9 Aug 2022 14:27:19 +0530 Subject: [PATCH 3/5] Added interface DerivationPath --- src/bdk.udl | 5 +++++ src/lib.rs | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/bdk.udl b/src/bdk.udl index 15ef4b0..0583962 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -245,3 +245,8 @@ interface BumpFeeTxBuilder { [Throws=BdkError] PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet); }; + +interface DerivationPath { + [Throws=BdkError] + constructor(string path); +}; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 06212e0..cc00eec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ use bdk::bitcoin::hashes::hex::ToHex; use bdk::bitcoin::secp256k1::Secp256k1; use bdk::bitcoin::util::psbt::PartiallySignedTransaction; +use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath; use bdk::bitcoin::{Address, Network, OutPoint as BdkOutPoint, Script, Txid}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; use bdk::blockchain::{ @@ -633,6 +634,20 @@ fn generate_mnemonic(word_count: WordCount) -> Result { Ok(mnemonic.to_string()) } +struct DerivationPath { + derivation_path_mutex: Mutex, +} + +impl DerivationPath { + fn new(path: String) -> Result { + BdkDerivationPath::from_str(&path) + .map(|x| DerivationPath { + derivation_path_mutex: Mutex::new(x), + }) + .map_err(|e| BdkError::Generic(e.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. From 58fea6b2050d9f10b1c5429cce4f4969c155e9e3 Mon Sep 17 00:00:00 2001 From: dhruvbaliyan Date: Tue, 9 Aug 2022 14:39:15 +0530 Subject: [PATCH 4/5] Added interfaces DescriptorSecretKey and DescriptorPublicKey --- src/bdk.udl | 23 +++++++++ src/lib.rs | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 167 insertions(+), 2 deletions(-) diff --git a/src/bdk.udl b/src/bdk.udl index 0583962..03b874b 100644 --- a/src/bdk.udl +++ b/src/bdk.udl @@ -249,4 +249,27 @@ interface BumpFeeTxBuilder { 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(); }; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index cc00eec..df468a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ use bdk::bitcoin::hashes::hex::ToHex; use bdk::bitcoin::secp256k1::Secp256k1; -use bdk::bitcoin::util::psbt::PartiallySignedTransaction; 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}; use bdk::blockchain::{ @@ -10,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; @@ -648,6 +652,144 @@ impl DerivationPath { } } +struct DescriptorSecretKey { + descriptor_secret_key_mutex: Mutex, +} + +impl DescriptorSecretKey { + fn new(network: Network, mnemonic: String, password: Option) -> Result { + 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) -> Result, 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(); + let descriptor_x_key = match descriptor_secret_key.deref() { + BdkDescriptorSecretKey::XPrv(descriptor_x_key) => Some(descriptor_x_key), + _ => None, + } + .unwrap(); + 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), + })) + } + + fn extend(&self, path: Arc) -> Arc { + let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap(); + let path = path.derivation_path_mutex.lock().unwrap().deref().clone(); + let descriptor_x_key = match descriptor_secret_key.deref() { + BdkDescriptorSecretKey::XPrv(descriptor_x_key) => Some(descriptor_x_key), + _ => None, + } + .unwrap(); + 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), + }) + } + + fn as_public(&self) -> Arc { + 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, +} + +impl DescriptorPublicKey { + fn derive(&self, path: Arc) -> Result, 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(); + let descriptor_x_key = match descriptor_public_key.deref() { + BdkDescriptorPublicKey::XPub(descriptor_x_key) => Some(descriptor_x_key), + _ => None, + } + .unwrap(); + 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), + })) + } + + fn extend(&self, path: Arc) -> Arc { + let descriptor_secret_key = self.descriptor_public_key_mutex.lock().unwrap(); + let path = path.derivation_path_mutex.lock().unwrap().deref().clone(); + let descriptor_x_key = match descriptor_secret_key.deref() { + BdkDescriptorPublicKey::XPub(descriptor_x_key) => Some(descriptor_x_key), + _ => None, + } + .unwrap(); + 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), + }) + } + + 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. From 5944756b780968588e97886d888bb854aa2053ad Mon Sep 17 00:00:00 2001 From: dhruvbaliyan Date: Tue, 9 Aug 2022 14:43:06 +0530 Subject: [PATCH 5/5] Added tests for DescriptorSecretKey and DescriptorPublicKey --- CHANGELOG.md | 23 +++++ src/lib.rs | 233 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 190 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7d727..f319934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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] diff --git a/src/lib.rs b/src/lib.rs index df468a6..d7ba6be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -676,45 +676,49 @@ impl DescriptorSecretKey { 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(); - let descriptor_x_key = match descriptor_secret_key.deref() { - BdkDescriptorSecretKey::XPrv(descriptor_x_key) => Some(descriptor_x_key), - _ => None, + 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!() + } } - .unwrap(); - 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), - })) } fn extend(&self, path: Arc) -> Arc { let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap(); let path = path.derivation_path_mutex.lock().unwrap().deref().clone(); - let descriptor_x_key = match descriptor_secret_key.deref() { - BdkDescriptorSecretKey::XPrv(descriptor_x_key) => Some(descriptor_x_key), - _ => None, + 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!() + } } - .unwrap(); - 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), - }) } fn as_public(&self) -> Arc { @@ -744,45 +748,50 @@ impl DescriptorPublicKey { 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(); - let descriptor_x_key = match descriptor_public_key.deref() { - BdkDescriptorPublicKey::XPub(descriptor_x_key) => Some(descriptor_x_key), - _ => None, + + 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!() + } } - .unwrap(); - 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), - })) } fn extend(&self, path: Arc) -> Arc { - let descriptor_secret_key = self.descriptor_public_key_mutex.lock().unwrap(); + let descriptor_public_key = self.descriptor_public_key_mutex.lock().unwrap(); let path = path.derivation_path_mutex.lock().unwrap().deref().clone(); - let descriptor_x_key = match descriptor_secret_key.deref() { - BdkDescriptorPublicKey::XPub(descriptor_x_key) => Some(descriptor_x_key), - _ => None, + 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!() + } } - .unwrap(); - 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), - }) } fn as_string(&self) -> String { @@ -797,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; @@ -857,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, BdkError> { + let path = Arc::new(DerivationPath::new(path.to_string()).unwrap()); + key.derive(path) + } + + fn extend_dsk(key: &DescriptorSecretKey, path: &str) -> Arc { + let path = Arc::new(DerivationPath::new(path.to_string()).unwrap()); + key.extend(path) + } + + fn derive_dpk( + key: &DescriptorPublicKey, + path: &str, + ) -> Result, BdkError> { + let path = Arc::new(DerivationPath::new(path.to_string()).unwrap()); + key.derive(path) + } + + fn extend_dpk(key: &DescriptorPublicKey, path: &str) -> Arc { + 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()); + } }