]> Untitled Git - bdk-cli/commitdiff
adding hardware signers
authorRichard Ulrich <richi@ulrichard.ch>
Fri, 6 Jan 2023 11:37:07 +0000 (12:37 +0100)
committerRichard Ulrich <richard.ulrich@seba.swiss>
Thu, 16 Feb 2023 09:05:44 +0000 (10:05 +0100)
Cargo.lock
Cargo.toml
src/commands.rs
src/handlers.rs
src/utils.rs

index 761b4acc2fdb08e8ff1c01a56a6f3440f1f9a5c0..f4af6d250b00cf9d4f20dc1ae9169598838023b4 100644 (file)
@@ -121,6 +121,7 @@ dependencies = [
  "esplora-client",
  "futures",
  "getrandom",
+ "hwi",
  "js-sys",
  "log",
  "miniscript",
@@ -954,6 +955,21 @@ dependencies = [
  "quick-error",
 ]
 
+[[package]]
+name = "hwi"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cefe6626545c823e8cae0d3ad06bdface90571a7fde82ca1c1d17347388a5c0"
+dependencies = [
+ "base64 0.13.1",
+ "bitcoin",
+ "miniscript",
+ "once_cell",
+ "pyo3",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "hyper"
 version = "0.14.24"
@@ -1012,6 +1028,29 @@ dependencies = [
  "hashbrown 0.12.3",
 ]
 
+[[package]]
+name = "indoc"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8"
+dependencies = [
+ "indoc-impl",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "indoc-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unindent",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -1252,9 +1291,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.17.1"
+version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
 
 [[package]]
 name = "opaque-debug"
@@ -1349,6 +1388,25 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "paste"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
+dependencies = [
+ "paste-impl",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "paste-impl"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
+dependencies = [
+ "proc-macro-hack",
+]
+
 [[package]]
 name = "pbkdf2"
 version = "0.11.0"
@@ -1415,6 +1473,12 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.20+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.51"
@@ -1424,6 +1488,54 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "pyo3"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d41d50a7271e08c7c8a54cd24af5d62f73ee3a6f6a314215281ebdec421d5752"
+dependencies = [
+ "cfg-if",
+ "indoc",
+ "libc",
+ "parking_lot",
+ "paste",
+ "pyo3-build-config",
+ "pyo3-macros",
+ "unindent",
+]
+
+[[package]]
+name = "pyo3-build-config"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "779239fc40b8e18bc8416d3a37d280ca9b9fb04bda54b98037bb6748595c2410"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "pyo3-macros"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b247e8c664be87998d8628e86f282c25066165f1f8dda66100c48202fdb93a"
+dependencies = [
+ "pyo3-macros-backend",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pyo3-macros-backend"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a8c2812c412e00e641d99eeb79dd478317d981d938aa60325dfa7157b607095"
+dependencies = [
+ "proc-macro2",
+ "pyo3-build-config",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "quick-error"
 version = "1.2.3"
@@ -2213,6 +2325,12 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
+[[package]]
+name = "unindent"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
+
 [[package]]
 name = "untrusted"
 version = "0.7.1"
index 6e9693ea5450d52fbe6ec9caa8aa314cc0da8ec7..95e1494ce90fffebdf4c8578165e428b00cafeb0 100644 (file)
@@ -71,6 +71,9 @@ esplora-reqwest = ["esplora", "bdk/use-esplora-reqwest", "bdk/reqwest-default-tl
 # Use this to consensus verify transactions at sync time
 verify = ["bdk/verify"]
 
+# Use hardware wallets to sign transactions
+hardware-signer = ["bdk/hardware-signer"]
+
 # Extra utility tools
 # Compile policies
 compiler = ["bdk/compiler"]
@@ -93,4 +96,4 @@ regtest-electrum = ["regtest-node", "electrum", "electrsd/electrs_0_8_10"]
 # This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
 # for libraries to explicitly include the "getrandom/js" feature, so we only do it when
 # necessary for running our CI. See: https://docs.rs/getrandom/0.2.8/getrandom/#webassembly-support
-dev-getrandom-wasm = ["getrandom/js"]
\ No newline at end of file
+dev-getrandom-wasm = ["getrandom/js"]
index 6b64c87f9f9062a86961680e2ff618ee98fd2344..c8d7c0b1ad78d36f893a1d56df7b0cc3c503acc5 100644 (file)
@@ -565,6 +565,9 @@ pub enum KeySubCommand {
         #[clap(name = "PATH", short = 'p', long = "path")]
         path: DerivationPath,
     },
+    #[cfg(feature = "hardware-signer")]
+    /// List the public descriptors of the available hardware wallets
+    Hardware {},
 }
 
 /// Subcommands available in REPL mode.
index b7897c0eb16bc45b77386cc3af9cbfac84889252..fb69df25cbc48e55a18cab4283f2d00743f77170 100644 (file)
@@ -50,6 +50,11 @@ use bdk::descriptor::Segwitv0;
 use bdk::descriptor::{Descriptor, Legacy, Miniscript};
 #[cfg(all(feature = "reserves", feature = "electrum"))]
 use bdk::electrum_client::{Client, ElectrumApi};
+#[cfg(feature = "hardware-signer")]
+use bdk::hwi::{
+    interface::HWIClient,
+    types::{HWIChain, HWIDescriptor},
+};
 use bdk::keys::bip39::{Language, Mnemonic, WordCount};
 use bdk::keys::DescriptorKey::Secret;
 use bdk::keys::KeyError::{InvalidNetwork, Message};
@@ -57,6 +62,8 @@ use bdk::keys::{DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, Genera
 use bdk::miniscript::miniscript;
 #[cfg(feature = "compiler")]
 use bdk::miniscript::policy::Concrete;
+#[cfg(feature = "hardware-signer")]
+use bdk::wallet::signer::SignerError;
 use bdk::SignOptions;
 #[cfg(all(feature = "reserves", feature = "electrum"))]
 use bdk::{bitcoin::Address, blockchain::Capability};
@@ -487,6 +494,26 @@ pub(crate) fn handle_key_subcommand(
                 Err(Error::Key(Message("Invalid key variant".to_string())))
             }
         }
+        #[cfg(feature = "hardware-signer")]
+        KeySubCommand::Hardware {} => {
+            let chain = match network {
+                Network::Bitcoin => HWIChain::Main,
+                Network::Testnet => HWIChain::Test,
+                Network::Regtest => HWIChain::Regtest,
+                Network::Signet => HWIChain::Signet,
+            };
+            let devices = HWIClient::enumerate().map_err(|e| SignerError::from(e))?;
+            let descriptors = devices.iter().map(|device_| {
+                let device = device_.clone()
+                    .map_err(|e| SignerError::from(e))?;
+                let client = HWIClient::get_client(&device, true, chain.clone())
+                    .map_err(|e| SignerError::from(e))?;
+                let descriptors: HWIDescriptor<String> = client.get_descriptors(None)
+                    .map_err(|e| SignerError::from(e))?;
+                Ok(json!({"device": device.model, "receiving": descriptors.receive[0].to_string(), "change": descriptors.internal[0]})) 
+            }).collect::<Result<Vec<_>, Error>>()?;
+            Ok(json!(descriptors))
+        }
     }
 }
 
index fa4e6c1ed00b6a0b60cb8e539af479533040b614..e29fed5a7e39b2e06b5ae2317a2d0e7dbbc42773 100644 (file)
@@ -43,8 +43,18 @@ use bdk::database::any::SledDbConfiguration;
 #[cfg(feature = "sqlite-db")]
 use bdk::database::any::SqliteDbConfiguration;
 use bdk::database::{AnyDatabase, AnyDatabaseConfig, BatchDatabase, ConfigurableDatabase};
+#[cfg(feature = "hardware-signer")]
+use bdk::hwi::{interface::HWIClient, types::HWIChain};
+#[cfg(feature = "hardware-signer")]
+use bdk::wallet::hardwaresigner::HWISigner;
+#[cfg(feature = "hardware-signer")]
+use bdk::wallet::signer::{SignerError, SignerOrdering};
 use bdk::wallet::wallet_name_from_descriptor;
+#[cfg(feature = "hardware-signer")]
+use bdk::KeychainKind;
 use bdk::{Error, Wallet};
+#[cfg(feature = "hardware-signer")]
+use std::sync::Arc;
 
 /// Create a randomized wallet name from the descriptor checksum.
 /// If wallet options already includes a name, use that instead.
@@ -497,5 +507,41 @@ where
     let descriptor = wallet_opts.descriptor.as_str();
     let change_descriptor = wallet_opts.change_descriptor.as_deref();
     let wallet = Wallet::new(descriptor, change_descriptor, network, database)?;
+
+    #[cfg(feature = "hardware-signer")]
+    let wallet = add_hardware_signers(wallet, network)?;
+
+    Ok(wallet)
+}
+
+/// Add hardware wallets as signers to the wallet
+#[cfg(feature = "hardware-signer")]
+fn add_hardware_signers<D>(wallet: Wallet<D>, network: Network) -> Result<Wallet<D>, Error>
+where
+    D: BatchDatabase,
+{
+    let mut wallet = wallet;
+    let chain = match network {
+        Network::Bitcoin => HWIChain::Main,
+        Network::Testnet => HWIChain::Test,
+        Network::Regtest => HWIChain::Regtest,
+        Network::Signet => HWIChain::Signet,
+    };
+    let devices = HWIClient::enumerate().map_err(|e| SignerError::from(e))?;
+    for device in devices {
+        let device = device.map_err(|e| SignerError::from(e))?;
+        // Creating a custom signer from the device
+        let custom_signer =
+            HWISigner::from_device(&device, chain.clone()).map_err(|e| SignerError::from(e))?;
+
+        // Adding the hardware signer to the BDK wallet
+        wallet.add_signer(
+            KeychainKind::External,
+            SignerOrdering(200),
+            Arc::new(custom_signer),
+        );
+        println!("Added {} as a signer to the wallet.", device.model);
+    }
+
     Ok(wallet)
 }