diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml index 013d43e9..46dd206e 100644 --- a/.github/workflows/code_coverage.yml +++ b/.github/workflows/code_coverage.yml @@ -16,6 +16,10 @@ jobs: uses: actions/checkout@v6 with: persist-credentials: false + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libusb-1.0-0-dev pkg-config - name: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index 727f042b..37e50e20 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -22,6 +22,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libusb-1.0-0-dev pkg-config - name: Setup Rust Toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -45,7 +49,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 - + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y libudev-dev libusb-1.0-0-dev pkg-config - name: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 79da994d..50c7e21a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details. ## [Unreleased] +- Added `hwi` feature with a hardware wallet `hwi` command to list connected devices, register a wallet policy, display a receive address, and sign PSBTs. Supports Ledger, Coldcard, BitBox02, Jade and Specter. + ## [4.0.0] - Added persistance to existing async payjoin integration diff --git a/Cargo.lock b/Cargo.lock index 17bbc65e..25ebbad3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,8 +60,22 @@ dependencies = [ "aead 0.4.3", "aes 0.7.5", "cipher 0.3.0", - "ctr", - "ghash", + "ctr 0.7.0", + "ghash 0.4.4", + "subtle", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.1", "subtle", ] @@ -86,6 +100,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "1.0.0" @@ -142,6 +165,12 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.8" @@ -163,6 +192,34 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-hwi" +version = "0.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30263bda4fbe2613e08989a2526d44ee0c13f19a3badd0d8d872cc233d6611" +dependencies = [ + "async-trait", + "bitbox-api", + "bitcoin", + "coldcard", + "crossbeam", + "futures", + "hex", + "hidapi", + "ledger-apdu", + "ledger-transport-hidapi", + "ledger_bitcoin_client", + "regex", + "reqwest 0.11.27", + "serde", + "serde_bytes", + "serde_cbor", + "serialport", + "tokio", + "tokio-serial", + "tracing", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -171,7 +228,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -209,6 +266,24 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + [[package]] name = "base58ck" version = "0.1.101" @@ -247,6 +322,7 @@ name = "bdk-cli" version = "4.0.0" dependencies = [ "assert_cmd", + "async-hwi", "bdk_bitcoind_rpc", "bdk_electrum", "bdk_esplora", @@ -455,6 +531,33 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bitbox-api" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d5d5ce87126e79759ea758e3ed4c1113a84fd4188a61c23d425f05c9e3c791" +dependencies = [ + "async-trait", + "base32", + "bitcoin", + "byteorder", + "chrono", + "getrandom 0.2.17", + "hex", + "hidapi", + "noise-protocol", + "noise-rust-crypto", + "num-bigint", + "prost 0.13.5", + "prost-build", + "semver", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "zeroize", +] + [[package]] name = "bitcoin" version = "0.32.8" @@ -569,7 +672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87a803a4b54e44635206b53329c78c0029d0c70926288ac2f07f4bb1267546cb" dependencies = [ "aead 0.4.3", - "aes-gcm", + "aes-gcm 0.9.2", "bitcoin-hpke", "byteorder", "chacha20poly1305 0.8.0", @@ -608,6 +711,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446819536d8121575eeb7e89efdbadb3f055e87e4bb66c6679a6d5cc2f4b64fd" +dependencies = [ + "hex-conservative 0.1.2", +] + [[package]] name = "bitcoin_hashes" version = "0.14.101" @@ -704,6 +816,15 @@ version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -861,6 +982,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -885,7 +1017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half", + "half 2.7.1", ] [[package]] @@ -945,10 +1077,10 @@ version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -977,7 +1109,7 @@ checksum = "9f7c1b60bae2c3d45228dfb096046aa51ef6c300de70b658d7a13fcb0c4f832e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -989,6 +1121,21 @@ dependencies = [ "cc", ] +[[package]] +name = "coldcard" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1178252cca421753b1024d240d43e502b019f51ca8022652587cdad4347d58b7" +dependencies = [ + "aes 0.8.4", + "base58", + "bitcoin_hashes 0.13.1", + "ctr 0.9.2", + "hidapi", + "k256", + "rand 0.8.6", +] + [[package]] name = "colorchoice" version = "1.0.5" @@ -1005,12 +1152,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1063,6 +1226,56 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.22" @@ -1075,6 +1288,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -1126,6 +1351,41 @@ dependencies = [ "cipher 0.3.0", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "defmt" version = "1.1.1" @@ -1145,7 +1405,7 @@ dependencies = [ "defmt-parser", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1157,6 +1417,16 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1185,6 +1455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -1218,7 +1489,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1231,12 +1502,32 @@ dependencies = [ "tokio", ] +[[package]] +name = "doc-comment" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" + [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "either" version = "1.16.0" @@ -1254,7 +1545,7 @@ dependencies = [ "electrum-client 0.20.0", "log", "minreq", - "nix", + "nix 0.25.1", "which", "zip", ] @@ -1288,6 +1579,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_filter" version = "2.0.0" @@ -1361,6 +1680,22 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "filetime" version = "0.2.29" @@ -1377,6 +1712,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.9" @@ -1396,6 +1737,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1482,7 +1829,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1522,6 +1869,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1570,9 +1918,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug", - "polyval", + "polyval 0.5.3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval 0.6.2", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + [[package]] name = "half" version = "2.7.1" @@ -1617,7 +2011,13 @@ dependencies = [ [[package]] name = "heck" -version = "0.5.0" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" @@ -1627,6 +2027,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + [[package]] name = "hex-conservative" version = "0.2.2" @@ -1651,6 +2057,19 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "hidapi" +version = "2.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c78dadfc12f865bc3fcac3897e64533b930737ceb9ef245c8277de98d0b010e9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "pkg-config", + "windows-sys 0.61.2", +] + [[package]] name = "hkdf" version = "0.11.0" @@ -1698,6 +2117,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.2" @@ -1708,6 +2138,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1715,7 +2156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.2", ] [[package]] @@ -1726,8 +2167,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.2", + "http-body 1.0.1", "pin-project-lite", ] @@ -1737,6 +2178,36 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.10.1" @@ -1747,8 +2218,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http", - "http-body", + "http 1.4.2", + "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", @@ -1757,18 +2228,32 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http", - "hyper", + "http 1.4.2", + "hyper 1.10.1", "hyper-util", "rustls 0.23.41", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower-service", "webpki-roots 1.0.8", ] @@ -1781,7 +2266,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.10.1", "hyper-util", "native-tls", "tokio", @@ -1799,19 +2284,43 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.2", + "http-body 1.0.1", + "hyper 1.10.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.4", "tokio", "tower-service", "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.2.0" @@ -1934,6 +2443,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -1946,6 +2465,24 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1974,7 +2511,7 @@ checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2004,7 +2541,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.118", ] [[package]] @@ -2023,7 +2560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2059,12 +2596,74 @@ dependencies = [ "serde_json", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.9", + "signature", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "ledger-apdu" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe435806c197dfeaa5efcded5e623c4b8230fd28fdf1e91e7a86e40ef2acbf90" +dependencies = [ + "arrayref", + "no-std-compat", + "snafu", +] + +[[package]] +name = "ledger-transport" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1117f2143d92c157197785bf57711d7b02f2cfa101e162f8ca7900fb7f976321" +dependencies = [ + "async-trait", + "ledger-apdu", +] + +[[package]] +name = "ledger-transport-hidapi" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27139d540e4271fa55b67b8cb94c6f100931042dcc663db1c2395fa3ffb8599" +dependencies = [ + "byteorder", + "cfg-if", + "hex", + "hidapi", + "ledger-transport", + "libc", + "log", + "thiserror 1.0.69", +] + +[[package]] +name = "ledger_bitcoin_client" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c8007335257021d1d294df3714fe4648e47fd2467bb43e43566e2d4d04ce61a" +dependencies = [ + "async-trait", + "bitcoin", + "miniscript", +] + [[package]] name = "libc" version = "0.2.186" @@ -2097,6 +2696,26 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libudev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "lightning" version = "0.2.4" @@ -2133,7 +2752,7 @@ checksum = "d4c717494cdc2c8bb85bee7113031248f5f6c64f8802b33c1c9e2d98e594aa71" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2184,6 +2803,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.8.3" @@ -2199,6 +2827,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniscript" version = "12.3.7" @@ -2241,10 +2875,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] +[[package]] +name = "mio-serial" +version = "5.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4ba3f20276f21b7cad3f1b54c97489cf096a3894fd627cc6951cb3abdd4c60" +dependencies = [ + "log", + "mio", + "nix 0.31.3", + "serialport", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "native-tls" version = "0.2.18" @@ -2276,6 +2930,59 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "noise-protocol" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f226274a278d3d465c891db37e9a300e754f5344a33a62a2a8cd1cdadffb93a" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "noise-rust-crypto" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c6159f60beb3bbbcdc266bc789bfc6c37fdad7d7ca7152d3e049ef5af633f0" +dependencies = [ + "aes-gcm 0.10.3", + "blake2", + "chacha20poly1305 0.10.1", + "noise-protocol", + "sha2 0.10.9", + "x25519-dalek", + "zeroize", +] + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -2291,12 +2998,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2346,7 +3072,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2418,7 +3144,7 @@ dependencies = [ "bitcoin-hpke", "bitcoin-ohttp", "bitcoin_uri", - "http", + "http 1.4.2", "reqwest 0.12.28", "serde", "serde_json", @@ -2451,6 +3177,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3637c05577168127568a64e9dc5a6887da720efef07b3d9472d45f63ab191166" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2463,6 +3199,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -2503,6 +3249,18 @@ dependencies = [ "universal-hash 0.4.0", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -2581,6 +3339,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2590,6 +3358,83 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck 0.4.1", + "itertools 0.10.5", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost 0.11.9", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + [[package]] name = "quinn" version = "0.11.11" @@ -2603,7 +3448,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.41", - "socket2", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -2642,7 +3487,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.4", "tracing", "windows-sys 0.61.2", ] @@ -2776,12 +3621,53 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.25.4", + "winreg", +] + [[package]] name = "reqwest" version = "0.12.28" @@ -2791,11 +3677,11 @@ dependencies = [ "base64 0.22.1", "bytes", "futures-core", - "http", - "http-body", + "http 1.4.2", + "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.10.1", + "hyper-rustls 0.27.9", "hyper-tls", "hyper-util", "js-sys", @@ -2809,10 +3695,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower", "tower-http", "tower-service", @@ -2832,11 +3718,11 @@ dependencies = [ "base64 0.22.1", "bytes", "futures-core", - "http", - "http-body", + "http 1.4.2", + "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.10.1", + "hyper-rustls 0.27.9", "hyper-util", "js-sys", "log", @@ -2846,9 +3732,9 @@ dependencies = [ "rustls 0.23.41", "rustls-pki-types", "rustls-platform-verifier", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower", "tower-http", "tower-service", @@ -2858,6 +3744,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2967,6 +3863,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pki-types" version = "1.15.0" @@ -2983,7 +3888,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", @@ -3072,6 +3977,20 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.29.1" @@ -3100,7 +4019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.13.0", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3132,6 +4051,26 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half 1.8.3", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3149,7 +4088,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3186,6 +4125,25 @@ dependencies = [ "serde", ] +[[package]] +name = "serialport" +version = "4.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4d91116f97173694f1642263b2ff837f80d933aa837e2314969f6728f661df3" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "core-foundation 0.10.1", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2", + "nix 0.26.4", + "scopeguard", + "unescaper", + "windows-sys 0.52.0", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3252,6 +4210,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -3286,6 +4254,38 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.4" @@ -3296,6 +4296,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3314,6 +4324,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.118" @@ -3325,6 +4346,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3342,7 +4369,28 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -3416,7 +4464,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3427,7 +4475,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3495,7 +4543,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] @@ -3508,7 +4556,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3521,6 +4569,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -3531,6 +4589,35 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-serial" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd00f5f8b1e01c3e5afccd9e42ed80c2ad2df6d007877f29f8592c62e69cd116" +dependencies = [ + "cfg-if", + "futures-core", + "futures-sink", + "log", + "mio-serial", + "serialport", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.11" @@ -3588,7 +4675,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -3603,8 +4690,8 @@ dependencies = [ "bitflags 2.13.0", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.2", + "http-body 1.0.1", "pin-project-lite", "tower", "tower-layer", @@ -3643,7 +4730,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -3693,6 +4780,15 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "unescaper" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7285e83a80ce76f5e7bce79fa41f68d78ba62d1003cf27bf748ab24413808cf4" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -3868,7 +4964,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -3968,19 +5064,81 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3989,7 +5147,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -4001,34 +5159,67 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4041,24 +5232,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4071,6 +5286,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wit-bindgen" version = "0.57.1" @@ -4083,6 +5308,17 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "xattr" version = "1.6.1" @@ -4112,7 +5348,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -4133,7 +5369,7 @@ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4153,7 +5389,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", "synstructure", ] @@ -4174,7 +5410,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -4207,7 +5443,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b6e2c2d4..c252ad16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ reqwest = { version = "0.13.2", default-features = false, features = ["rustls"], url = { version = "2.5.8", optional = true } bdk_message_signer = { version = "0.2.0", optional = true } bitcoin-payment-instructions = { version = "0.7.0", optional = true} +async-hwi = { version = "0.0.32", features = ["specter", "coldcard", "ledger", "jade", "bitbox", "service"], optional = true} [features] default = ["repl", "sqlite"] @@ -73,6 +74,9 @@ compiler = [] # Experimental silent payment sending capabilities silent-payments = ["bdk_sp"] +# Use a hardware wallet (HWI) to display addresses, register wallets and sign PSBTs +hwi = ["async-hwi"] + [dev-dependencies] predicates = "3.0" tempfile = "3.8" diff --git a/README.md b/README.md index 94278f3c..9b81ace1 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ bdk-cli can be compiled with different features to suit your experimental needs. - `message_signer`: BIP322 message signing/verification. - `silent-payments`: Experimental BIP-352 silent payment sending - `dns_payment`: BIP-353 DNS payment instructions + - `hwi`: Hardware wallet support (Ledger, Coldcard, BitBox02, Jade, Specter) to list devices, register a wallet policy, display a receive address, and sign PSBTs. The `default` feature set is `repl` and `sqlite`. With the `default` features, `bdk-cli` can be used as an **air-gapped** wallet, and can do everything that doesn't require a network connection. @@ -206,6 +207,36 @@ View all payjoin sessions (active and completed) and also see their status: cargo run -- wallet --wallet payjoin_history ``` +## Hardware wallet (HWI) + +Compile with the `hwi` feature to interact with a connected hardware wallet (Ledger, Coldcard, BitBox02, Jade or Specter). On Linux you need the USB/HID system libraries first: + +```shell +sudo apt-get install -y libudev-dev libusb-1.0-0-dev pkg-config +``` +List every connected device (fingerprint and model): + +```shell +cargo run --features hwi -- hwi devices +``` +Register a wallet policy on the device. + +```shell +cargo run --features hwi -- wallet --wallet my_wallet hwi register +``` + +Display a receive address on the device: + +```shell +cargo run --features hwi -- wallet --wallet my_wallet hwi address +``` + +Sign a PSBT with the device: + +```shell +cargo run --features hwi -- wallet --wallet my_wallet hwi sign +``` + ## Justfile We have added the `just` command runner to help you with common commands (during development) and running regtest `bitcoind` if you are using the `rpc` feature. diff --git a/src/commands.rs b/src/commands.rs index 975f33be..3155a1c9 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -59,6 +59,9 @@ use crate::handlers::dns::{CreateDnsTxCommand, ResolveDnsRecipientCommand}; #[cfg(any(feature = "electrum", feature = "esplora", feature = "rpc"))] use crate::utils::parse_proxy_auth; +#[cfg(feature = "hwi")] +use crate::handlers::hwi::{HwiCommand, WalletHwiCommand}; + /// The BDK Command Line Wallet App /// /// bdk-cli is a lightweight command line bitcoin wallet, powered by BDK. @@ -206,6 +209,12 @@ pub enum CliSubCommand { /// Resolves BIP-353 DNS payment instructions for a human-readable name. #[cfg(feature = "dns_payment")] ResolveDnsRecipient(ResolveDnsRecipientCommand), + /// Hardware wallet (HWI) operations. + /// + /// List connected devices, register a wallet policy, display a receive + /// address for verification, or sign a PSBT with a hardware wallet. + #[cfg(feature = "hwi")] + Hwi(HwiCommand), } /// Wallet operation subcommands. @@ -213,6 +222,12 @@ pub enum CliSubCommand { pub enum WalletSubCommand { /// Save wallet configuration to `config.toml`. Config(SaveConfigCommand), + /// Hardware wallet (HWI) operations for this wallet. + /// + /// Register the wallet's policy on a device, display a receive + /// address, or sign a PSBT. + #[cfg(feature = "hwi")] + Hwi(WalletHwiCommand), #[cfg(any( feature = "electrum", feature = "esplora", diff --git a/src/error.rs b/src/error.rs index bee8f191..d6364340 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,6 +43,10 @@ pub enum BDKCliError { #[error("Key error: {0}")] KeyError(#[from] bdk_wallet::keys::KeyError), + #[cfg(feature = "hwi")] + #[error("Hardware wallet error: {0}")] + HwiError(#[from] async_hwi::Error), + #[error("LocalChain error: {0}")] LocalChainError(#[from] bdk_wallet::chain::local_chain::ApplyHeaderError), diff --git a/src/handlers/hwi.rs b/src/handlers/hwi.rs new file mode 100644 index 00000000..657c3238 --- /dev/null +++ b/src/handlers/hwi.rs @@ -0,0 +1,410 @@ +//! Hardware wallet (HWI) command handlers. +//! +//! HWI operations are split by their data dependency: +//! +//! - Device-only utilities live under the top-level `hwi` command +//! ([`HwiCommand`]): `hwi xpub` reads a device extended public key and +//! `hwi devices` lists connected devices. Neither needs a wallet — `hwi xpub` +//! is the bootstrap step used to build a descriptor. +//! - Wallet-scoped operations live under `wallet hwi …` +//! ([`WalletHwiCommand`]): `register`, `address` and `sign`. These take the +//! wallet policy from the loaded wallet's descriptors, so no descriptor needs +//! to be passed on the command line. + +use async_hwi::AddressScript; +use bdk_wallet::KeychainKind; +use clap::{Parser, Subcommand}; +use serde_json::json; +use std::str::FromStr; + +use crate::error::BDKCliError as Error; +use crate::handlers::{AppContext, AsyncAppCommand, Init, OfflineOperations}; +use crate::utils::hwi::{HwiWallet, enumerate_hwi_devices, first_hwi_device}; + +use bdk_wallet::bitcoin::{ + Psbt, + bip32::{ChildNumber, DerivationPath}, + hex::DisplayHex, +}; +use bdk_wallet::miniscript::{Descriptor, DescriptorPublicKey, descriptor::ShInner}; + +/// Device-only HWI utilities that do not require a wallet. +#[derive(Debug, Parser, Clone, PartialEq, Eq)] +pub struct HwiCommand { + #[command(subcommand)] + pub subcommand: HwiSubCommand, +} + +/// Subcommands for the top-level `hwi` command. +#[derive(Debug, Subcommand, Clone, PartialEq, Eq)] +#[command(rename_all = "snake")] +pub enum HwiSubCommand { + /// Read an extended public key from the device at a derivation path. + /// + /// Needed to build a wallet descriptor for the device. + Xpub { + /// Derivation path, e.g. `m/84'/1'/0'`. + #[arg(default_value = "m/84'/1'/0'")] + path: String, + + /// Output descriptor script type. + #[arg(long = "type", value_parser = ["wpkh", "tr", "pkh"])] + script_type: Option, + }, + /// List all connected hardware wallet devices. + Devices, +} + +impl AsyncAppCommand> for HwiCommand { + type Output = serde_json::Value; + + async fn execute(&self, ctx: &mut AppContext) -> Result { + let network = ctx.network; + // Device-only commands operate without a wallet policy. + let hwi_wallet = HwiWallet::default(); + + match &self.subcommand { + HwiSubCommand::Xpub { path, script_type } => { + let derivation = DerivationPath::from_str(path) + .map_err(|e| Error::Generic(format!("Invalid derivation path: {e}")))?; + let device = first_hwi_device(network, &hwi_wallet).await?; + let xpub = device.get_extended_pubkey(&derivation).await?; + let fingerprint = device.get_master_fingerprint().await?.to_string(); + let origin = path.trim_start_matches('m').trim_start_matches('/'); + let key_expression = format!("[{fingerprint}/{origin}]{xpub}"); + + let mut out = json!({ + "path": path, + "xpub": xpub.to_string(), + "master_fingerprint": fingerprint, + "key_expression": key_expression, + }); + + match script_type + .clone() + .or_else(|| infer_script_type(path).map(str::to_string)) + { + Some(stype) => { + out["script_type"] = json!(stype); + out["external_descriptor"] = + json!(build_descriptor(&stype, &key_expression, 0)?); + out["internal_descriptor"] = + json!(build_descriptor(&stype, &key_expression, 1)?); + } + None => { + out["note"] = json!( + "Non-standard or multisig path; pass --type to emit descriptors, or use key_expression" + ); + } + } + Ok(out) + } + + HwiSubCommand::Devices => { + let devices = enumerate_hwi_devices(network, &hwi_wallet).await?; + let mut listed = Vec::with_capacity(devices.len()); + for device in &devices { + let fingerprint = device.get_master_fingerprint().await?.to_string(); + listed.push(json!({ + "fingerprint": fingerprint, + "model": device.device_kind().to_string(), + })); + } + Ok(json!({ "count": listed.len(), "devices": listed })) + } + } + } +} + +/// Wallet-scoped HWI operations (`wallet hwi …`). +#[derive(Debug, Parser, Clone, PartialEq, Eq)] +pub struct WalletHwiCommand { + /// Registration HMAC returned by `register`, required to reuse a registered + /// policy on a Ledger for `address`/`sign`. + #[arg(env = "HWI_HMAC", long)] + pub hmac: Option, + #[command(subcommand)] + pub subcommand: WalletHwiSubCommand, +} + +/// Subcommands for `wallet hwi`. +#[derive(Debug, Subcommand, Clone, PartialEq, Eq)] +#[command(rename_all = "snake")] +pub enum WalletHwiSubCommand { + /// Register the wallet's policy on the device, returning its HMAC. + Register, + /// Display a receive address on the device. + Address { + /// Address index to derive. + #[arg(long, default_value_t = 0)] + index: u32, + /// Derive from the change (internal) keychain instead of external. + #[arg(long)] + change: bool, + }, + /// Sign a PSBT with the device. + /// + /// The device produces partial signatures; the returned PSBT still needs to + /// be finalized before it can be broadcasted. + Sign { + /// The base64-encoded PSBT to sign. + psbt: String, + }, +} + +impl WalletHwiCommand { + /// Run a wallet-scoped HWI operation. The wallet policy is derived from the + /// loaded wallet's public descriptors and `wallet_name` is used as the + /// device registration name. + pub async fn run( + &self, + ctx: &mut AppContext>, + wallet_name: &str, + ) -> Result { + let network = ctx.network; + + // Derive the multipath policy from the wallet's public descriptors. + let ext_desc = ctx + .state + .wallet + .public_descriptor(KeychainKind::External) + .clone(); + let int_desc = ctx + .state + .wallet + .public_descriptor(KeychainKind::Internal) + .clone(); + + let kind = WalletKind::classify(&ext_desc); + + // Only multisig/miniscript wallets get a registered device policy; + // single-sig wallets are signed straight from the PSBT, so attaching a + // policy (which BitBox/Ledger reject for single-sig) is skipped. + + let ext = ext_desc.to_string(); + let int = int_desc.to_string(); + let int = if int == ext { None } else { Some(int) }; + let policy = build_device_policy(&ext, int.as_deref())?; + + let hwi_wallet = HwiWallet { + name: Some(wallet_name), + policy: matches!(kind, WalletKind::Policy).then_some(policy.as_str()), + hmac: self.hmac.as_deref(), + }; + + match &self.subcommand { + WalletHwiSubCommand::Register => { + tracing::debug!("Registering wallet '{wallet_name}' with policy '{policy}'"); + let device = first_hwi_device(network, &hwi_wallet).await?; + match device.register_wallet(wallet_name, &policy).await { + Ok(hmac) => Ok(json!({ + "success": true, + "hmac": hmac.map(|h| h.to_lower_hex_string()), + })), + // Some devices don't implement registration. + Err(async_hwi::Error::UnimplementedMethod) => Ok(json!({ + "success": true, + "hmac": null, + "message": "This device does not support wallet registration", + })), + Err(e) => Err(Error::HwiError(e)), + } + } + WalletHwiSubCommand::Address { index, change } => { + let keychain = if *change { + KeychainKind::Internal + } else { + KeychainKind::External + }; + let address = ctx + .state + .wallet + .peek_address(keychain, *index) + .address + .to_string(); + + // Attempt to derive and display the address on the device and on the cli. + // If it fails to derive from the device, it falls back on the wallet to generate and display + let script = match kind { + WalletKind::TaprootSingleSig => { + let desc = if *change { &int_desc } else { &ext_desc }; + taproot_address_path(desc, *index).map(AddressScript::P2TR) + } + _ => Some(AddressScript::Miniscript { + index: *index, + change: *change, + }), + }; + + let device = first_hwi_device(network, &hwi_wallet).await?; + let displayed = match script { + Some(script) => match device.display_address(&script).await { + Ok(()) => ( + true, + "Verify this address matches the one shown on your device".to_string(), + ), + // The device attempted but declined (e.g. unsupported + // for this script type, or no registered policy). + Err(e) => ( + false, + format!("Device did not display the address ({e}); showing the locally derived address"), + ), + }, + None => ( + false, + "Could not build a device address request; showing the locally derived address".to_string(), + ), + }; + + Ok(json!({ + "index": index, + "change": change, + "address": address, + "displayed_on_device": displayed, + })) + } + + WalletHwiSubCommand::Sign { psbt } => { + let mut psbt = Psbt::from_str(psbt) + .map_err(|e| Error::Generic(format!("Failed to parse PSBT: {e}")))?; + let device = first_hwi_device(network, &hwi_wallet).await?; + device.sign_tx(&mut psbt).await?; + Ok(json!({ "psbt": psbt.to_string() })) + } + } + } +} + +/// Strip a trailing descriptor checksum. +fn strip_checksum(descriptor: &str) -> &str { + descriptor.split('#').next().unwrap_or(descriptor) +} + +/// How a wallet's descriptor maps onto a hardware device's capabilities. +#[derive(Clone, Copy, PartialEq, Eq)] +enum WalletKind { + /// BIP-86 taproot single-sig — shown on device via the simple P2TR path. + TaprootSingleSig, + /// Other single-sig (wpkh/pkh/sh-wpkh) — signable, but no on-device address + /// display path in async-hwi. + SingleSig, + /// Multisig/miniscript — requires policy registration. + Policy, +} + +impl WalletKind { + fn classify(desc: &Descriptor) -> Self { + match desc { + Descriptor::Tr(tr) if tr.tap_tree().is_none() => WalletKind::TaprootSingleSig, + Descriptor::Wpkh(_) | Descriptor::Pkh(_) => WalletKind::SingleSig, + Descriptor::Sh(sh) if matches!(sh.as_inner(), ShInner::Wpkh(_)) => { + WalletKind::SingleSig + } + _ => WalletKind::Policy, + } + } +} + +/// Build the full BIP-32 path (from the master key) to the taproot single-sig +/// address at `index`, e.g. `86'/1'/0'/0/index`. Returns `None` unless the +/// descriptor is a keyspend-only taproot over an xpub key. +fn taproot_address_path( + desc: &Descriptor, + index: u32, +) -> Option { + let Descriptor::Tr(tr) = desc else { + return None; + }; + if tr.tap_tree().is_some() { + return None; + } + let DescriptorPublicKey::XPub(xkey) = tr.internal_key() else { + return None; + }; + // Full path from the master key: [origin]/[xpub derivation]/index. + let base = match &xkey.origin { + Some((_, origin_path)) => origin_path.extend(&xkey.derivation_path), + None => xkey.derivation_path.clone(), + }; + let index = ChildNumber::from_normal_idx(index).ok()?; + Some(base.extend([index])) +} + +/// Infer the single-sig descriptor script type from a derivation path's. +fn infer_script_type(path: &str) -> Option<&'static str> { + let purpose = path + .split('/') + .filter(|s| !s.is_empty() && *s != "m") + .find_map(|s| s.trim_end_matches(['\'', 'h']).parse::().ok())?; + match purpose { + 44 => Some("pkh"), + 49 => Some("sh-wpkh"), + 84 => Some("wpkh"), + 86 => Some("tr"), + _ => None, + } +} + +/// Wrap a key expression into a checksummed single-sig descriptor for the given +/// keychain branch (0 = external, 1 = internal). +fn build_descriptor(script_type: &str, key_expr: &str, branch: u32) -> Result { + let raw = match script_type { + "tr" => format!("tr({key_expr}/{branch}/*)"), + "pkh" => format!("pkh({key_expr}/{branch}/*)"), + "sh-wpkh" => format!("sh(wpkh({key_expr}/{branch}/*))"), + _ => format!("wpkh({key_expr}/{branch}/*)"), + }; + let descriptor = Descriptor::::from_str(&raw) + .map_err(|e| Error::Generic(format!("Failed to build descriptor: {e}")))?; + Ok(descriptor.to_string()) +} + +/// Build the wallet policy +/// +/// - Rejects private-key. +/// - Passes an already-multipath descriptor (`/**` or `/<..>/*`) through +/// unchanged. +/// - Combines a standard `/0/*` external and `/1/*` internal pair into a single +/// `/<0;1>/*` multipath descriptor. +fn build_device_policy(ext: &str, int: Option<&str>) -> Result { + let ext = strip_checksum(ext).trim().to_string(); + + if ext.contains("xprv") || ext.contains("tprv") { + return Err(Error::Generic( + "Hardware wallet policy must use public keys (xpub), not private keys".to_string(), + )); + } + + // Already a multipath descriptor: use as-is. + if ext.contains("/**") || ext.contains('<') { + return Ok(ext); + } + + match int { + Some(int) => { + let int = strip_checksum(int).trim(); + if int.contains("xprv") || int.contains("tprv") { + return Err(Error::Generic( + "Hardware wallet policy must use public keys (xpub), not private keys" + .to_string(), + )); + } + // `/0/*` and `/1/*` only ever appear as a key's wildcard branch. + if ext.replace("/0/*", "/1/*") != int { + return Err(Error::Generic( + "Cannot derive a multipath policy: the wallet's external and internal \ + descriptors are not a standard `/0/*` and `/1/*` pair." + .to_string(), + )); + } + Ok(ext.replace("/0/*", "/<0;1>/*")) + } + None => { + tracing::warn!( + "Wallet has no separate internal (change) descriptor; the device policy will \ + only cover receive addresses." + ); + Ok(ext) + } + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index a2c4ad5b..7d3d7f8c 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -2,6 +2,8 @@ pub mod config; pub mod descriptor; #[cfg(feature = "dns_payment")] pub mod dns; +#[cfg(feature = "hwi")] +pub mod hwi; pub mod key; pub mod offline; pub mod online; @@ -114,7 +116,8 @@ pub trait AppCommand { feature = "esplora", feature = "rpc", feature = "cbf", - feature = "dns_payment" + feature = "dns_payment", + feature = "hwi" ))] pub trait AsyncAppCommand { type Output: FormatOutput; diff --git a/src/handlers/offline.rs b/src/handlers/offline.rs index fe4097e4..617ef2f9 100644 --- a/src/handlers/offline.rs +++ b/src/handlers/offline.rs @@ -35,6 +35,13 @@ use { bdk_message_signer::{MessageProof, MessageSigner}, }; +/// When built with hardware wallet support, `create_tx` produces a +/// PSBT that offline signers and hardware wallets can consume. +#[cfg(feature = "hwi")] +const DEFAULT_OFFLINE_SIGNER: bool = true; +#[cfg(not(feature = "hwi"))] +const DEFAULT_OFFLINE_SIGNER: bool = false; + impl OfflineWalletSubCommand { pub fn execute(&self, ctx: &mut AppContext>) -> Result<(), Error> { match self { @@ -205,7 +212,7 @@ pub struct CreateTxCommand { pub enable_rbf: bool, /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `PSBT_GLOBAL_XPUB` and more details to let the signer identify the change output. - #[arg(long = "offline_signer")] + #[arg(long = "offline_signer", default_value_t = DEFAULT_OFFLINE_SIGNER)] pub offline_signer: bool, /// Selects which utxos *must* be spent. diff --git a/src/handlers/repl.rs b/src/handlers/repl.rs index b15b653f..b93916e9 100644 --- a/src/handlers/repl.rs +++ b/src/handlers/repl.rs @@ -86,6 +86,12 @@ pub(crate) async fn respond( .map_err(|e| e.to_string())?; Some(()) } + #[cfg(feature = "hwi")] + WalletSubCommand::Hwi(_) => { + writeln!(std::io::stdout(), "hwi is not available in REPL mode") + .map_err(|e| e.to_string())?; + Some(()) + } }, ReplSubCommand::Descriptor(cmd) => { diff --git a/src/main.rs b/src/main.rs index 06e3ea24..ad72381a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ use log::{debug, warn}; use crate::commands::{CliOpts, CliSubCommand, WalletSubCommand}; use crate::error::BDKCliError as Error; -#[cfg(feature = "dns_payment")] +#[cfg(any(feature = "dns_payment", feature = "hwi"))] use crate::handlers::AsyncAppCommand; use crate::handlers::{AppCommand, AppContext}; use crate::utils::output::FormatOutput; @@ -115,6 +115,21 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> { config_cmd.execute(&mut ctx)?.write_out(std::io::stdout())?; } + #[cfg(feature = "hwi")] + WalletSubCommand::Hwi(cmd) => { + let runtime = WalletRuntime::load(&home_dir, &wallet_name)?; + let mut wallet = runtime.build_wallet(false)?; + + let mut ctx = AppContext::new_offline_wallet( + runtime.network, + runtime.home_dir.clone(), + &mut wallet, + ); + + cmd.run(&mut ctx, &wallet_name) + .await? + .write_out(std::io::stdout())?; + } }, CliSubCommand::Key { subcommand } => { @@ -218,6 +233,11 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> { let mut ctx = AppContext::new(cli_opts.network, home_dir); cmd.execute(&mut ctx).await?.write_out(std::io::stdout())?; } + #[cfg(feature = "hwi")] + CliSubCommand::Hwi(cmd) => { + let mut ctx = AppContext::new(cli_opts.network, home_dir); + cmd.execute(&mut ctx).await?.write_out(std::io::stdout())?; + } } Ok(()) diff --git a/src/utils/hwi.rs b/src/utils/hwi.rs new file mode 100644 index 00000000..94b1568b --- /dev/null +++ b/src/utils/hwi.rs @@ -0,0 +1,203 @@ +//! Hardware wallet (HWI) device discovery. +//! +//! Enumerates every supported hardware wallet that is currently reachable and +//! returns them as trait objects. Supported devices: Specter, Jade, Ledger, +//! Coldcard and BitBox02 (plus the Specter and Ledger simulators). The logic +//! mirrors the reference implementation shipped with the `async-hwi` crate so +//! that new devices supported upstream become available here with minimal +//! changes. + +use async_hwi::bitbox::api::BitBox as BitBoxApi; +use async_hwi::bitbox::api::runtime::TokioRuntime; +use async_hwi::{ + HWI, + bitbox::{BitBox02, NoiseConfigNoCache, PairingBitbox02WithLocalCache, api::runtime}, + coldcard, + jade::{self, Jade}, + ledger::{HidApi, Ledger, LedgerSimulator, TransportHID}, + specter::{Specter, SpecterSimulator}, +}; +use bdk_wallet::bitcoin::{Network, hex::FromHex}; +use tracing::warn; + +use crate::error::BDKCliError as Error; + +/// Optional wallet context required by some devices (Ledger/Coldcard/BitBox) +/// to register and operate on a specific multisig/miniscript policy. +#[derive(Default, Clone)] +pub struct HwiWallet<'a> { + pub name: Option<&'a str>, + pub policy: Option<&'a str>, + pub hmac: Option<&'a str>, +} + +/// Enumerate every hardware wallet currently connected. +/// +/// Returns one boxed [`HWI`] trait object per reachable device. The list can be +/// empty if nothing is plugged in. Enumeration never fails hard for a single +/// device kind: failures are logged and the other device kinds are still +/// probed. +pub async fn enumerate_hwi_devices( + network: Network, + wallet: &HwiWallet<'_>, +) -> Result>, Error> { + let mut devices: Vec> = Vec::new(); + + // Specter simulator (TCP) then physical Specter devices (serial). + if let Ok(device) = SpecterSimulator::try_connect().await { + devices.push(device.into()); + } + match Specter::enumerate().await { + Ok(specters) => devices.extend(specters.into_iter().map(Into::into)), + Err(e) => warn!("Specter enumeration failed: {e:?}"), + } + + // Jade (serial). + match Jade::enumerate().await { + Ok(jades) => { + for device in jades { + let device = device.with_network(network); + match device.get_info().await { + Ok(info) => { + if info.jade_state == jade::api::JadeState::Locked + && let Err(e) = device.auth().await + { + warn!("Jade authentication failed: {e:?}"); + continue; + } + devices.push(device.into()); + } + Err(e) => warn!("Jade get_info failed: {e:?}"), + } + } + } + Err(e) => warn!("Jade enumeration failed: {e:?}"), + } + + // Ledger simulator (Speculos). + if let Ok(device) = LedgerSimulator::try_connect().await { + devices.push(device.into()); + } + + // BitBox02 simulator (TCP, defaults to 127.0.0.1:15423). async-hwi only + // connects a physical BitBox over USB, so the simulator is reached directly + // through the re-exported bitbox-api. A connection error just means the + // simulator isn't running, so we ignore it and keep probing other devices. + { + if let Ok(bitbox) = + BitBoxApi::::from_simulator(None, Box::new(NoiseConfigNoCache {})).await + { + match bitbox.unlock_and_pair().await { + Ok(pairing) => match pairing.wait_confirm().await { + Ok(paired) => { + // A freshly started simulator is unseeded, so key operations fail. + // Applicable to only simulators + if let Ok(info) = paired.device_info().await + && !info.initialized + && let Err(e) = paired.restore_from_mnemonic().await + { + warn!("BitBox simulator seeding failed: {e:?}"); + } + let mut bb02 = BitBox02::from(paired).with_network(network); + if let Some(policy) = wallet.policy { + bb02 = bb02.with_policy(policy).map_err(map_device_err)?; + } + devices.push(bb02.into()); + } + Err(e) => warn!("BitBox simulator pairing confirmation failed: {e:?}"), + }, + Err(e) => warn!("BitBox simulator unlock/pair failed: {e:?}"), + } + } + } + + // USB (HID) devices: BitBox02 and Coldcard. + let api = Box::new(HidApi::new().map_err(|e| Error::Generic(e.to_string()))?); + + for device_info in api.device_list() { + // BitBox02 + if async_hwi::bitbox::is_bitbox02(device_info) + && let Ok(handle) = device_info.open_device(&api) + && let Ok(pairing) = + PairingBitbox02WithLocalCache::::connect(handle, None).await + && let Ok((device, _)) = pairing.wait_confirm().await + { + let mut bb02 = BitBox02::from(device).with_network(network); + if let Some(policy) = wallet.policy { + bb02 = bb02.with_policy(policy).map_err(map_device_err)?; + } + devices.push(bb02.into()); + } + + // Coldcard + if device_info.vendor_id() == coldcard::api::COINKITE_VID + && device_info.product_id() == coldcard::api::CKCC_PID + && let Some(sn) = device_info.serial_number() + { + match coldcard::api::Coldcard::open(&api, sn, None) { + Ok((cc, _)) => { + let mut hw = coldcard::Coldcard::from(cc); + if let Some(name) = wallet.name { + hw = hw.with_wallet_name(name.to_string()); + } + devices.push(hw.into()); + } + Err(e) => warn!("Failed to open Coldcard (SN {sn}): {e:?}"), + } + } + } + + // Ledger (HID). + for detected in Ledger::::enumerate(&api) { + if let Ok(mut device) = Ledger::::connect(&api, detected) { + // A registered wallet policy (and its HMAC) is required before a + // Ledger will operate on anything other than default single-sig. + if let (Some(name), Some(policy)) = (wallet.name, wallet.policy) { + let hmac = match wallet.hmac { + Some(s) => { + let bytes = Vec::from_hex(s) + .map_err(|e| Error::Generic(format!("Invalid HMAC hex: {e}")))?; + let mut h = [0u8; 32]; + if bytes.len() != h.len() { + return Err(Error::Generic( + "HMAC must be 32 bytes (64 hex chars)".to_string(), + )); + } + h.copy_from_slice(&bytes); + Some(h) + } + None => None, + }; + device = device + .with_wallet(name, policy, hmac) + .map_err(map_device_err)?; + } + devices.push(device.into()); + } + } + + Ok(devices) +} + +/// Enumerate devices and return the first one, erroring if none are found. +/// +/// Used by the single-device subcommands (register/address/sign). If more than +/// one device is connected a warning is logged and the first is used. +pub async fn first_hwi_device( + network: Network, + wallet: &HwiWallet<'_>, +) -> Result, Error> { + let devices = enumerate_hwi_devices(network, wallet).await?; + let count = devices.len(); + if count > 1 { + warn!("{count} hardware wallets detected; using the first one."); + } + devices + .into_iter() + .next() + .ok_or_else(|| Error::Generic("No hardware wallet detected".to_string())) +} + +fn map_device_err(e: E) -> Error { + Error::Generic(e.to_string()) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 084cab52..15138df5 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,5 +2,7 @@ pub mod common; pub mod descriptors; pub mod output; pub use common::*; +#[cfg(feature = "hwi")] +pub mod hwi; pub mod runtime; pub mod types;