diff --git a/bdk-ffi/src/descriptor.rs b/bdk-ffi/src/descriptor.rs index 8d08ee1..e33df68 100644 --- a/bdk-ffi/src/descriptor.rs +++ b/bdk-ffi/src/descriptor.rs @@ -193,3 +193,142 @@ impl Descriptor { self.extended_descriptor.to_string() } } + +// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs. +// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk` +// crate. +#[cfg(test)] +mod test { + use crate::database::DatabaseConfig; + use crate::*; + use assert_matches::assert_matches; + use bdk::descriptor::DescriptorError::Key; + use bdk::keys::KeyError::InvalidNetwork; + + fn get_descriptor_secret_key() -> DescriptorSecretKey { + let mnemonic = Mnemonic::from_string("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()).unwrap(); + DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None) + } + + #[test] + fn test_descriptor_templates() { + let master: Arc = Arc::new(get_descriptor_secret_key()); + println!("Master: {:?}", master.as_string()); + // tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h + let handmade_public_44 = master + .derive(Arc::new( + DerivationPath::new("m/44h/1h/0h".to_string()).unwrap(), + )) + .unwrap() + .as_public(); + println!("Public 44: {}", handmade_public_44.as_string()); + // Public 44: [d1d04177/44'/1'/0']tpubDCoPjomfTqh1e7o1WgGpQtARWtkueXQAepTeNpWiitS3Sdv8RKJ1yvTrGHcwjDXp2SKyMrTEca4LoN7gEUiGCWboyWe2rz99Kf4jK4m2Zmx/* + let handmade_public_49 = master + .derive(Arc::new( + DerivationPath::new("m/49h/1h/0h".to_string()).unwrap(), + )) + .unwrap() + .as_public(); + println!("Public 49: {}", handmade_public_49.as_string()); + // Public 49: [d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/* + let handmade_public_84 = master + .derive(Arc::new( + DerivationPath::new("m/84h/1h/0h".to_string()).unwrap(), + )) + .unwrap() + .as_public(); + println!("Public 84: {}", handmade_public_84.as_string()); + // Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/* + let template_private_44 = + Descriptor::new_bip44(master.clone(), KeychainKind::External, Network::Testnet); + let template_private_49 = + Descriptor::new_bip49(master.clone(), KeychainKind::External, Network::Testnet); + let template_private_84 = + Descriptor::new_bip84(master, KeychainKind::External, Network::Testnet); + // the extended public keys are the same when creating them manually as they are with the templates + println!("Template 49: {}", template_private_49.as_string()); + println!("Template 44: {}", template_private_44.as_string()); + println!("Template 84: {}", template_private_84.as_string()); + // for the public versions of the templates these are incorrect, bug report and fix in bitcoindevkit/bdk#817 and bitcoindevkit/bdk#818 + let template_public_44 = Descriptor::new_bip44_public( + handmade_public_44, + "d1d04177".to_string(), + KeychainKind::External, + Network::Testnet, + ); + let template_public_49 = Descriptor::new_bip49_public( + handmade_public_49, + "d1d04177".to_string(), + KeychainKind::External, + Network::Testnet, + ); + let template_public_84 = Descriptor::new_bip84_public( + handmade_public_84, + "d1d04177".to_string(), + KeychainKind::External, + Network::Testnet, + ); + println!("Template public 49: {}", template_public_49.as_string()); + println!("Template public 44: {}", template_public_44.as_string()); + println!("Template public 84: {}", template_public_84.as_string()); + // when using a public key, both as_string and as_string_private return the same string + assert_eq!( + template_public_44.as_string_private(), + template_public_44.as_string() + ); + assert_eq!( + template_public_49.as_string_private(), + template_public_49.as_string() + ); + assert_eq!( + template_public_84.as_string_private(), + template_public_84.as_string() + ); + // when using as_string on a private key, we get the same result as when using it on a public key + assert_eq!( + template_private_44.as_string(), + template_public_44.as_string() + ); + assert_eq!( + template_private_49.as_string(), + template_public_49.as_string() + ); + assert_eq!( + template_private_84.as_string(), + template_public_84.as_string() + ); + } + #[test] + fn test_descriptor_from_string() { + let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet); + let descriptor2 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Bitcoin); + // Creating a Descriptor using an extended key that doesn't match the network provided will throw and InvalidNetwork Error + assert!(descriptor1.is_ok()); + assert_matches!( + descriptor2.unwrap_err(), + bdk::Error::Descriptor(Key(InvalidNetwork)) + ) + } + #[test] + fn test_wallet_from_descriptor() { + let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap(); + let wallet1 = Wallet::new( + Arc::new(Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap()), + None, + Network::Testnet, + DatabaseConfig::Memory + ); + let wallet2 = Wallet::new( + Arc::new(descriptor1), + None, + Network::Bitcoin, + DatabaseConfig::Memory, + ); + // Creating a wallet using a Descriptor with an extended key that doesn't match the network provided in the wallet constructor will throw and InvalidNetwork Error + assert!(wallet1.is_ok()); + assert_matches!( + wallet2.unwrap_err(), + bdk::Error::Descriptor(Key(InvalidNetwork)) + ) + } +} diff --git a/bdk-ffi/src/keys.rs b/bdk-ffi/src/keys.rs index 50723d7..0d7513d 100644 --- a/bdk-ffi/src/keys.rs +++ b/bdk-ffi/src/keys.rs @@ -242,3 +242,135 @@ impl DescriptorPublicKey { self.descriptor_public_key_mutex.lock().unwrap().to_string() } } + +// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs. +// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk` +// crate. +#[cfg(test)] +mod test { + use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic}; + use crate::BdkError; + use bdk::bitcoin::hashes::hex::ToHex; + use bdk::bitcoin::Network; + use std::sync::Arc; + + fn get_descriptor_secret_key() -> DescriptorSecretKey { + let mnemonic = Mnemonic::from_string("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()).unwrap(); + DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None) + } + + 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, + ) -> Result, BdkError> { + 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, + ) -> Result, BdkError> { + 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").unwrap(); + 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").unwrap(); + assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*"); + let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch"; + let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap(); + let result = extended_key.derive(Arc::new(DerivationPath::new("m/0".to_string()).unwrap())); + dbg!(&result); + assert!(result.is_err()); + } + + #[test] + fn test_from_str_descriptor_secret_key() { + let key1 = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch"; + let key2 = "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/1/1/*"; + let private_descriptor_key1 = DescriptorSecretKey::from_string(key1.to_string()).unwrap(); + let private_descriptor_key2 = DescriptorSecretKey::from_string(key2.to_string()).unwrap(); + dbg!(private_descriptor_key1); + dbg!(private_descriptor_key2); + // Should error out because you can't produce a DescriptorSecretKey from an xpub + let key0 = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi"; + assert!(DescriptorSecretKey::from_string(key0.to_string()).is_err()); + } + + #[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").unwrap(); + 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()); + } + + #[test] + fn test_retrieve_master_secret_key() { + let master_dpk = get_descriptor_secret_key(); + let master_private_key = master_dpk.secret_bytes().to_hex(); + assert_eq!( + master_private_key, + "e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936" + ) + } +} diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 5363120..3f2f976 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -264,384 +264,3 @@ pub struct TxBuilderResult { } 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. -// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk` -// crate. -#[cfg(test)] -mod test { - use crate::database::DatabaseConfig; - use crate::*; - use assert_matches::assert_matches; - use bdk::bitcoin::hashes::hex::ToHex; - use bdk::bitcoin::Address; - use bdk::descriptor::DescriptorError::Key; - use bdk::keys::KeyError::InvalidNetwork; - use bdk::wallet::get_funded_wallet; - use std::str::FromStr; - use std::sync::Mutex; - - #[test] - fn test_drain_wallet() { - let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; - let (funded_wallet, _, _) = get_funded_wallet(test_wpkh); - let test_wallet = Wallet { - wallet_mutex: Mutex::new(funded_wallet), - }; - let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string(); - let drain_to_script = crate::Address::new(drain_to_address) - .unwrap() - .script_pubkey(); - let tx_builder = TxBuilder::new() - .drain_wallet() - .drain_to(drain_to_script.clone()); - assert!(tx_builder.drain_wallet); - assert_eq!(tx_builder.drain_to, Some(drain_to_script.script.clone())); - - let tx_builder_result = tx_builder.finish(&test_wallet).unwrap(); - let psbt = tx_builder_result.psbt.internal.lock().unwrap().clone(); - let tx_details = tx_builder_result.transaction_details; - - // confirm one input with 50,000 sats - assert_eq!(psbt.inputs.len(), 1); - let input_value = psbt - .inputs - .get(0) - .cloned() - .unwrap() - .non_witness_utxo - .unwrap() - .output - .get(0) - .unwrap() - .value; - assert_eq!(input_value, 50_000_u64); - - // confirm one output to correct address with all sats - fee - assert_eq!(psbt.outputs.len(), 1); - let output_address = Address::from_script( - &psbt - .unsigned_tx - .output - .get(0) - .cloned() - .unwrap() - .script_pubkey, - Network::Testnet, - ) - .unwrap(); - assert_eq!( - output_address, - Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap() - ); - let output_value = psbt.unsigned_tx.output.get(0).cloned().unwrap().value; - assert_eq!(output_value, 49_890_u64); // input - fee - - assert_eq!( - tx_details.txid, - "312f1733badab22dc26b8dcbc83ba5629fb7b493af802e8abe07d865e49629c5" - ); - assert_eq!(tx_details.received, 0); - assert_eq!(tx_details.sent, 50000); - assert!(tx_details.fee.is_some()); - assert_eq!(tx_details.fee.unwrap(), 110); - assert!(tx_details.confirmation_time.is_none()); - } - - fn get_descriptor_secret_key() -> DescriptorSecretKey { - let mnemonic = Mnemonic::from_string("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()).unwrap(); - DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None) - } - - 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, - ) -> Result, BdkError> { - 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, - ) -> Result, BdkError> { - 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").unwrap(); - 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").unwrap(); - assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*"); - - let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch"; - let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap(); - let result = extended_key.derive(Arc::new(DerivationPath::new("m/0".to_string()).unwrap())); - dbg!(&result); - assert!(result.is_err()); - } - - #[test] - fn test_from_str_descriptor_secret_key() { - let key1 = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch"; - let key2 = "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/1/1/*"; - - let private_descriptor_key1 = DescriptorSecretKey::from_string(key1.to_string()).unwrap(); - let private_descriptor_key2 = DescriptorSecretKey::from_string(key2.to_string()).unwrap(); - - dbg!(private_descriptor_key1); - dbg!(private_descriptor_key2); - - // Should error out because you can't produce a DescriptorSecretKey from an xpub - let key0 = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi"; - assert!(DescriptorSecretKey::from_string(key0.to_string()).is_err()); - } - - #[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").unwrap(); - 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()); - } - - #[test] - fn test_retrieve_master_secret_key() { - let master_dpk = get_descriptor_secret_key(); - let master_private_key = master_dpk.secret_bytes().to_hex(); - assert_eq!( - master_private_key, - "e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936" - ) - } - - #[test] - fn test_psbt_fee() { - let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; - let (funded_wallet, _, _) = get_funded_wallet(test_wpkh); - let test_wallet = Wallet { - wallet_mutex: Mutex::new(funded_wallet), - }; - let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string(); - let drain_to_script = crate::Address::new(drain_to_address) - .unwrap() - .script_pubkey(); - - let tx_builder = TxBuilder::new() - .fee_rate(2.0) - .drain_wallet() - .drain_to(drain_to_script.clone()); - //dbg!(&tx_builder); - assert!(tx_builder.drain_wallet); - assert_eq!(tx_builder.drain_to, Some(drain_to_script.script.clone())); - - let tx_builder_result = tx_builder.finish(&test_wallet).unwrap(); - - assert!(tx_builder_result.psbt.fee_rate().is_some()); - assert_eq!( - tx_builder_result.psbt.fee_rate().unwrap().as_sat_per_vb(), - 2.682927 - ); - - assert!(tx_builder_result.psbt.fee_amount().is_some()); - assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220); - } - - #[test] - fn test_descriptor_templates() { - let master: Arc = Arc::new(get_descriptor_secret_key()); - println!("Master: {:?}", master.as_string()); - // tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h - - let handmade_public_44 = master - .derive(Arc::new( - DerivationPath::new("m/44h/1h/0h".to_string()).unwrap(), - )) - .unwrap() - .as_public(); - println!("Public 44: {}", handmade_public_44.as_string()); - // Public 44: [d1d04177/44'/1'/0']tpubDCoPjomfTqh1e7o1WgGpQtARWtkueXQAepTeNpWiitS3Sdv8RKJ1yvTrGHcwjDXp2SKyMrTEca4LoN7gEUiGCWboyWe2rz99Kf4jK4m2Zmx/* - - let handmade_public_49 = master - .derive(Arc::new( - DerivationPath::new("m/49h/1h/0h".to_string()).unwrap(), - )) - .unwrap() - .as_public(); - println!("Public 49: {}", handmade_public_49.as_string()); - // Public 49: [d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/* - - let handmade_public_84 = master - .derive(Arc::new( - DerivationPath::new("m/84h/1h/0h".to_string()).unwrap(), - )) - .unwrap() - .as_public(); - println!("Public 84: {}", handmade_public_84.as_string()); - // Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/* - - let template_private_44 = - Descriptor::new_bip44(master.clone(), KeychainKind::External, Network::Testnet); - let template_private_49 = - Descriptor::new_bip49(master.clone(), KeychainKind::External, Network::Testnet); - let template_private_84 = - Descriptor::new_bip84(master, KeychainKind::External, Network::Testnet); - - // the extended public keys are the same when creating them manually as they are with the templates - println!("Template 49: {}", template_private_49.as_string()); - println!("Template 44: {}", template_private_44.as_string()); - println!("Template 84: {}", template_private_84.as_string()); - - // for the public versions of the templates these are incorrect, bug report and fix in bitcoindevkit/bdk#817 and bitcoindevkit/bdk#818 - let template_public_44 = Descriptor::new_bip44_public( - handmade_public_44, - "d1d04177".to_string(), - KeychainKind::External, - Network::Testnet, - ); - let template_public_49 = Descriptor::new_bip49_public( - handmade_public_49, - "d1d04177".to_string(), - KeychainKind::External, - Network::Testnet, - ); - let template_public_84 = Descriptor::new_bip84_public( - handmade_public_84, - "d1d04177".to_string(), - KeychainKind::External, - Network::Testnet, - ); - - println!("Template public 49: {}", template_public_49.as_string()); - println!("Template public 44: {}", template_public_44.as_string()); - println!("Template public 84: {}", template_public_84.as_string()); - - // when using a public key, both as_string and as_string_private return the same string - assert_eq!( - template_public_44.as_string_private(), - template_public_44.as_string() - ); - assert_eq!( - template_public_49.as_string_private(), - template_public_49.as_string() - ); - assert_eq!( - template_public_84.as_string_private(), - template_public_84.as_string() - ); - - // when using as_string on a private key, we get the same result as when using it on a public key - assert_eq!( - template_private_44.as_string(), - template_public_44.as_string() - ); - assert_eq!( - template_private_49.as_string(), - template_public_49.as_string() - ); - assert_eq!( - template_private_84.as_string(), - template_public_84.as_string() - ); - } - - #[test] - fn test_descriptor_from_string() { - let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet); - let descriptor2 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Bitcoin); - - // Creating a Descriptor using an extended key that doesn't match the network provided will throw and InvalidNetwork Error - assert!(descriptor1.is_ok()); - assert_matches!( - descriptor2.unwrap_err(), - bdk::Error::Descriptor(Key(InvalidNetwork)) - ) - } - - #[test] - fn test_wallet_from_descriptor() { - let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap(); - - let wallet1 = Wallet::new( - Arc::new(Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap()), - None, - Network::Testnet, - DatabaseConfig::Memory - ); - - let wallet2 = Wallet::new( - Arc::new(descriptor1), - None, - Network::Bitcoin, - DatabaseConfig::Memory, - ); - - // Creating a wallet using a Descriptor with an extended key that doesn't match the network provided in the wallet constructor will throw and InvalidNetwork Error - assert!(wallet1.is_ok()); - assert_matches!( - wallet2.unwrap_err(), - bdk::Error::Descriptor(Key(InvalidNetwork)) - ) - } -} diff --git a/bdk-ffi/src/psbt.rs b/bdk-ffi/src/psbt.rs index c5dd0a8..272fae2 100644 --- a/bdk-ffi/src/psbt.rs +++ b/bdk-ffi/src/psbt.rs @@ -72,3 +72,45 @@ impl PartiallySignedTransaction { self.internal.lock().unwrap().fee_rate().map(Arc::new) } } + +// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs. +// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk` +// crate. +#[cfg(test)] +mod test { + use crate::wallet::{TxBuilder, Wallet}; + use bdk::wallet::get_funded_wallet; + use std::sync::Mutex; + + #[test] + fn test_psbt_fee() { + let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; + let (funded_wallet, _, _) = get_funded_wallet(test_wpkh); + let test_wallet = Wallet { + wallet_mutex: Mutex::new(funded_wallet), + }; + let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string(); + let drain_to_script = crate::Address::new(drain_to_address) + .unwrap() + .script_pubkey(); + + let tx_builder = TxBuilder::new() + .fee_rate(2.0) + .drain_wallet() + .drain_to(drain_to_script.clone()); + //dbg!(&tx_builder); + assert!(tx_builder.drain_wallet); + assert_eq!(tx_builder.drain_to, Some(drain_to_script.script.clone())); + + let tx_builder_result = tx_builder.finish(&test_wallet).unwrap(); + + assert!(tx_builder_result.psbt.fee_rate().is_some()); + assert_eq!( + tx_builder_result.psbt.fee_rate().unwrap().as_sat_per_vb(), + 2.682927 + ); + + assert!(tx_builder_result.psbt.fee_amount().is_some()); + assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220); + } +} diff --git a/bdk-ffi/src/wallet.rs b/bdk-ffi/src/wallet.rs index 2459aaf..29daf4b 100644 --- a/bdk-ffi/src/wallet.rs +++ b/bdk-ffi/src/wallet.rs @@ -444,3 +444,82 @@ impl BumpFeeTxBuilder { .map(Arc::new) } } + +// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs. +// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk` +// crate. +#[cfg(test)] +mod test { + use crate::wallet::{TxBuilder, Wallet}; + use bdk::bitcoin::{Address, Network}; + use bdk::wallet::get_funded_wallet; + use std::str::FromStr; + use std::sync::Mutex; + + #[test] + fn test_drain_wallet() { + let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; + let (funded_wallet, _, _) = get_funded_wallet(test_wpkh); + let test_wallet = Wallet { + wallet_mutex: Mutex::new(funded_wallet), + }; + let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string(); + let drain_to_script = crate::Address::new(drain_to_address) + .unwrap() + .script_pubkey(); + let tx_builder = TxBuilder::new() + .drain_wallet() + .drain_to(drain_to_script.clone()); + assert!(tx_builder.drain_wallet); + assert_eq!(tx_builder.drain_to, Some(drain_to_script.script.clone())); + + let tx_builder_result = tx_builder.finish(&test_wallet).unwrap(); + let psbt = tx_builder_result.psbt.internal.lock().unwrap().clone(); + let tx_details = tx_builder_result.transaction_details; + + // confirm one input with 50,000 sats + assert_eq!(psbt.inputs.len(), 1); + let input_value = psbt + .inputs + .get(0) + .cloned() + .unwrap() + .non_witness_utxo + .unwrap() + .output + .get(0) + .unwrap() + .value; + assert_eq!(input_value, 50_000_u64); + + // confirm one output to correct address with all sats - fee + assert_eq!(psbt.outputs.len(), 1); + let output_address = Address::from_script( + &psbt + .unsigned_tx + .output + .get(0) + .cloned() + .unwrap() + .script_pubkey, + Network::Testnet, + ) + .unwrap(); + assert_eq!( + output_address, + Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap() + ); + let output_value = psbt.unsigned_tx.output.get(0).cloned().unwrap().value; + assert_eq!(output_value, 49_890_u64); // input - fee + + assert_eq!( + tx_details.txid, + "312f1733badab22dc26b8dcbc83ba5629fb7b493af802e8abe07d865e49629c5" + ); + assert_eq!(tx_details.received, 0); + assert_eq!(tx_details.sent, 50000); + assert!(tx_details.fee.is_some()); + assert_eq!(tx_details.fee.unwrap(), 110); + assert!(tx_details.confirmation_time.is_none()); + } +}