BHWI is a sans-IO Bitcoin hardware wallet
interface. For each device it implements an interpreter that encodes commands,
decodes device responses and routes data to its recipients, while leaving all
I/O (transport, async runtime, HTTP) to the caller. That lets one Rust core
serve every target platform, from desktop and mobile to the browser (WASM) and
other languages through FFI. And because every device
sits behind one common interface, applications share a single dependency tree
instead of pulling a separate client library (and its transitive dependencies)
per hardware wallet. It ships a CLI plus a Python-HWI-compatible hwi binary.
Keeping I/O out of the core matters because driving a hardware wallet is rarely
one request/response: registering a Miniscript descriptor before signing,
answering a Ledger's Merkle-proof data requests, or routing through a Jade PIN
server all take several round trips. Rather than commit every caller to one
execution model, BHWI leaves that choice to them: each device is a small state
machine that performs no I/O itself, so it fits synchronous, asynchronous, FFI
and WASM callers alike. This matters most across an FFI boundary, where the host
language often brings its own native I/O stack: a sans-IO core lets that language
plug in its own transport directly instead of embedding a Rust runtime. The
repository ships bhwi-async for those who want a ready-made
rust async layer. See docs/VISION.md for the full rationale
behind this design.
Concretely, each device is an Interpreter: the caller calls start, feeds
device replies back through exchange until no further Transmit is returned,
then end yields the response. Throughout, the caller performs the actual I/O
for each Transmit payload the interpreter hands back.
pub trait Interpreter {
type Command;
type Transmit;
type Response;
type Error;
fn start(&mut self, command: Self::Command) -> Result<Self::Transmit, Self::Error>;
fn exchange(&mut self, data: Vec<u8>) -> Result<Option<Self::Transmit>, Self::Error>;
fn end(self) -> Result<Self::Response, Self::Error>;
}bhwi-async is one such driver: it pumps the common interpreter (BitBox02,
Coldcard, Jade, KeepKey, Ledger, Specter-DIY, and Trezor) over HID, USB serial,
TCP, UDP, or the browser and routes each Transmit to the device transport,
the Jade PIN server, or a typed host-interaction provider via its Recipient.
| crate | description |
|---|---|
bhwi |
Core sans-IO interpreters and the common command/response model. |
bhwi-async |
async/await HWI trait over the interpreters, with protocol framing. |
bhwi-async-transport |
Native device channels (HID/USB/serial/TCP) and discovery. |
bhwi-cli |
bhwi command-line tool and the hwi parity binary. |
bhwi-wasm |
WebAssembly bindings for browser callers. |
Every device implements the full HWI trait: unlock,
get_info, get_master_fingerprint, get_extended_pubkey, sign_message,
display_address, register_wallet and sign_tx. Three capability gaps
remain: backup_device is supported on BitBox02 and Coldcard but not on Jade,
KeepKey, Ledger, Specter-DIY, or Trezor; register_wallet is unsupported on
KeepKey and Trezor, which verify supported multisig descriptors at display and
signing time instead; and get_info is unsupported on Specter-DIY, whose
protocol exposes no firmware version. See docs/SPECTER.md
for the Specter-DIY setup and limits.
bhwi is a Unix-friendly command-line tool over bhwi-async:
cargo build -p bhwi-cliAll devices are built by default. Build a subset with the bitbox,
coldcard, jade, keepkey, ledger, specter and trezor features:
cargo build -p bhwi-cli --no-default-features --features jade,ledgerThe same features exist on bhwi, bhwi-async and bhwi-async-transport, and each
forwards to the crate below it. device install-udev-rules is Linux only.
| command | subcommand | purpose |
|---|---|---|
address |
get |
get an address from the device |
descriptor |
pubkeys |
get pubkey descriptors from the device |
descriptor |
keypool |
get a ranged keypool descriptor from the selected device |
descriptor |
register |
register a named wallet policy with the selected device |
device |
list |
list all available devices |
device |
backup |
start a backup on the selected device |
device |
setup |
initialize an unseeded device |
device |
wipe |
erase wallet material from the selected device |
device |
restore |
restore an unseeded device via its mnemonic flow |
device |
toggle-passphrase |
toggle mnemonic-passphrase use on the selected device |
device |
prompt-pin |
ask the selected device to show its PIN keypad |
device |
send-pin |
send the keypad positions shown on the device screen |
device |
install-udev-rules |
install udev rules for hardware wallet device access |
xpub |
get |
get an extended public key at a derivation path |
psbt |
sign |
sign a PSBT with the selected device |
message |
sign |
sign a message with the selected device |
Shared --fingerprint, --device-type, --device-path, --network,
--format, and --passphrase options work before a command group, between
the group and action, or after the action. For example:
bhwi message sign --network testnet --fingerprint f5acc2fd \
--message hello --path "m/44'/1'/0'/0"Output is chainable by default (no headers). Use --format pretty or
--format json where supported; PSBT, address, and xpub output remains plain
regardless of format.
bhwi-cli also builds a Python-HWI-compatible hwi binary. A parity suite
checks its output against Bitcoin Core HWI for the devices where parity is
claimed. See docs/HWI_PARITY.md.
- docs/VISION.md: design rationale
- docs/DEVICE_ONBOARDING.md: adding a device
- docs/HWI_PARITY.md: Python-HWI parity
- docs/NIX.md: Nix emulator runners
- Device emulation: BITBOX · COLDCARD · JADE · KEEPKEY · LEDGER · SPECTER · TREZOR
BHWI is released under the terms of the license in LICENSE. The BitBox02 integration additionally carries BITBOX_LICENSE.