Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
5 contributors

Users who have contributed to this file

@achow101 @RCasatta @jonasnick @instagibbs @fivepiece
305 lines (248 sloc) 14.5 KB

Using Bitcoin Core with Hardware Wallets

This approach is fairly manual, requires the command line, and Bitcoin Core >=0.18.0.

Note: For this guide, code lines prefixed with $ means that the command is typed in the terminal. Lines without $ are output of the commands.

Disclaimer

We are not liable for any coins that may be lost through this method. The software mentioned may have bugs. Use at your own risk.

Software

Bitcoin Core

This method of using hardware wallets uses Bitcoin Core as the wallet for monitoring the blockchain. It allows a user to use their own full node instead of relying on an SPV wallet or vendor provided software.

HWI works with Bitcoin Core as of commit c576979b78b541bf3b4a7cbeee989b55d268e3e1. It is usable with Bitcoin Core >=0.18.0.

Setup

Clone Bitcoin Core and build it. Clone HWI.

$ git clone https://github.com/bitcoin/bitcoin.git
$ cd bitcoin
$ ./autogen.sh
$ ./configure
$ make
$ src/bitcoind -daemon -addresstype=bech32 -changetype=bech32
$ cd ..
$ git clone https://github.com/bitcoin-core/HWI.git
$ cd HWI
$ python3 setup.py install

You may need some dependencies, on ubuntu install libudev-dev and libusb-1.0-0-dev

Now we need to find our hardware wallet. We do this using:

$ ./hwi.py enumerate
[{"fingerprint": "8038ecd9", "serial_number": "205A32753042", "type": "coldcard", "path": "0001:0005:00"}]

For this example, we will use the Coldcard. As we can see, the device path is 0001:0005:00. The fingerprint of the master key is 8038ecd9. Now that we have the device, we can issue commands to it. So now we want to get some keys and import them into Core. We will be fetching keys at the BIP 84 default.

$ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool 0 1000
[{"desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/0/*)#36sal9a4", "internal": false, "range": [0, 1000], "timestamp": "now", "keypool": true, "watchonly": true}]

We now create a new Bitcoin Core wallet and import the keys into Bitcoin Core. The output is formatted properly for Bitcoin Core so it can be copy and pasted.

$ ../bitcoin/src/bitcoin-cli createwallet "coldcard" true
{
  "name": "coldcard",
  "warning": ""
}
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"desc": "wpkh([8038ecd9/84'/0'/0']xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/0/*)#36sal9a4", "internal": false, "range": [0, 1000], "timestamp": "now", "keypool": true, "watchonly": true}]'

[
  {
    "success": true
  }
]

Now we repeat the getkeypool and importmulti steps but set a --internal flag and use the change keypath (m/44h/0h/0h/1) in getkeypool to generate change keys.

$ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool --internal 0 1000
[{"internal": true, "timestamp": "now", "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)#qw4uzsdd", "keypool": true, "range": {"start": 0, "end": 1000}}]
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": "now", "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)", "keypool": true, "range": [0, 1000], "watchonly": true}]'

[
  {
    "success": true
  }
]

The Bitcoin Core wallet is now setup to watch a two thousand keys (1000 normal, 1000 change) from your hardware wallet and you can use it to track your balances and create transactions. The transactions will need to be signed through HWI.

If the wallet was previously used, you will need to rescan the blockchain. You can either do this using the rescanblockchain command or editing the timestamp in the importmulti command. Here are some examples (<blockheight> refers to a block height before the wallet was created).

$ ../bitcoin/src/bitcoin-cli rescanblockchain <blockheight>
$ ../bitcoin/src/bitcoin-cli rescanblockchain 500000 # Rescan from block 500000

$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": <blockheight>, "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)#qw4uzsdd", "keypool": true, "range": [0, 1000], "watchonly": true}]'
$ ../bitcoin/src/bitcoin-cli -rpcwallet=coldcard importmulti '[{"internal": true, "timestamp": 500000, "desc": "wpkh([8038ecd9/84h/0h/0h]xpub6DR4rqx16YnCcfwFqgwvJdKiWrjDRzqxYTY44aoyHwZDSeSB5n2tqt42aYr9qPKhSKUdftPdTjhHrKKD6WGKVbuyhMvGH76VyKKZubg8o4P/1/*)#qw4uzsdd", "keypool": true, "range": [0, 1000], "watchonly": true}]' # Imports and rescans from block 500000

Usage

Usage of this primarily involves Bitcoin Core. Currently the GUI only supports generating new receive addresses (once all of the keys are imported) so this guide will only cover the command line.

Receiving

From the folder containing bitcoin and HWI, go into bitcoin. We will be doing most of the commands here.

$ cd bitcoin

To get a new address, use getnewaddress as you normally would

$ src/bitcoin-cli -rpcwallet=coldcard getnewaddress
bcrt1qu8qe24zq5e2ahh4nkl6g5ysxlpn3nyf0xt026s

This address belongs to your hardware wallet. You can check this by doing getaddressinfo:

$ src/bitcoin-cli -rpcwallet=coldcard getaddressinfo bcrt1qu8qe24zq5e2ahh4nkl6g5ysxlpn3nyf0xt026s
{
  "address": "bcrt1qu8qe24zq5e2ahh4nkl6g5ysxlpn3nyf0xt026s",
  "scriptPubKey": "0014e1c1955440a655dbdeb3b7f48a1206f86719912f",
  "ismine": false,
  "iswatchonly": true,
  "solvable": true,
  "isscript": false,
  "iswitness": true,
  "witness_version": 0,
  "witness_program": "e1c1955440a655dbdeb3b7f48a1206f86719912f",
  "pubkey": "022320f1cf72e7ba2cef6be32d7493ce3bd4c6a2575fe51ce260377adc165603d4",
  "label": "",
  "ischange": false,
  "timestamp": 1541688305,
  "hdkeypath": "m/84'/1'/0'/0/0",
  "hdseedid": "0000000000000000000000000000000000000000",
  "hdmasterkeyid": "00000000000000000000000000000000d9ec3880",
  "labels": [
    {
      "name": "",
      "purpose": "receive"
    }
  ]
}

Notice how the pubkey is the one that was specified as the very first thing being imported to your wallet.

You can give this out to people as you normally would. When coins are sent to it, you will see them in your Bitcoin Core wallet as watch-only.

Sending

To send Bitcoin, we will use walletcreatefundedpsbt. This will create a Partially Signed Bitcoin Transaction which is funded by inputs from the wallets (i.e. your watching only inputs selected with Bitcoin Core's coin selection algorithm). This PSBT can be used with HWI to produce a signed PSBT which can then be finalized and broadcast.

For example, suppose I am sending to 1 BTC to bc1q257z5t76hedc36wmmzva05890ny3kxd7xfwrgy. First I create a funded psbt with BIP 32 derivation paths to be included:

$ src/bitcoin-cli -rpcwallet=coldcard walletcreatefundedpsbt '[]' '[{"bc1q257z5t76hedc36wmmzva05890ny3kxd7xfwrgy":1}]' 0 '{"includeWatching":true}' true
{
  "psbt": "cHNidP8BAHECAAAAAU8KWkCU7H4MYBiZHmLey6FavV3L3xLfy4tVEZoubx+2AAAAAAD+////AgDh9QUAAAAAFgAUVTwqL9q+W4jp29iZ19DlfMkbGb78eNcXAAAAABYAFLHuX3WRuPs3ypeQOziNw5qFlBH8AAAAAAABAR8AZc0dAAAAABYAFOHBlVRAplXb3rO39IoSBvhnGZEvIgYCIyDxz3Lnuizva+MtdJPOO9TGoldf5RziYDd63BZWA9QYgDjs2VQAAIABAACAAAAAgAAAAAAAAAAAAAAiAgP0HMQ2K693zCXTCudBUzemDhxLmFGETOnAV7vgDz2r9RiAOOzZVAAAgAEAAIAAAACAAQAAAAAAAAAA",
  "fee": 0.00002820,
  "changepos": 1
}

Now I take the updated psbt and inspect it with decodepsbt:

$ src/bitcoin-cli decodepsbt cHNidP8BAHECAAAAAU8KWkCU7H4MYBiZHmLey6FavV3L3xLfy4tVEZoubx+2AAAAAAD+////AgDh9QUAAAAAFgAUVTwqL9q+W4jp29iZ19DlfMkbGb78eNcXAAAAABYAFLHuX3WRuPs3ypeQOziNw5qFlBH8AAAAAAABAR8AZc0dAAAAABYAFOHBlVRAplXb3rO39IoSBvhnGZEvIgYCIyDxz3Lnuizva+MtdJPOO9TGoldf5RziYDd63BZWA9QYgDjs2VQAAIABAACAAAAAgAAAAAAAAAAAAAAiAgP0HMQ2K693zCXTCudBUzemDhxLmFGETOnAV7vgDz2r9RiAOOzZVAAAgAEAAIAAAACAAQAAAAAAAAAA
{
  "tx": {
    "txid": "e51392c82e13bbfe714c73361aff14ac1a1637abf37587a562844ae5a4265adf",
    "hash": "e51392c82e13bbfe714c73361aff14ac1a1637abf37587a562844ae5a4265adf",
    "version": 2,
    "size": 113,
    "vsize": 113,
    "weight": 452,
    "locktime": 0,
    "vin": [
      {
        "txid": "b61f6f2e9a11558bcbdf12dfcb5dbd5aa1cbde621e9918600c7eec94405a0a4f",
        "vout": 0,
        "scriptSig": {
          "asm": "",
          "hex": ""
        },
        "sequence": 4294967294
      }
    ],
    "vout": [
      {
        "value": 1.00000000,
        "n": 0,
        "scriptPubKey": {
          "asm": "0 553c2a2fdabe5b88e9dbd899d7d0e57cc91b19be",
          "hex": "0014553c2a2fdabe5b88e9dbd899d7d0e57cc91b19be",
          "reqSigs": 1,
          "type": "witness_v0_keyhash",
          "addresses": [
            "bc1q257z5t76hedc36wmmzva05890ny3kxd7xfwrgy"
          ]
        }
      },
      {
        "value": 3.99997180,
        "n": 1,
        "scriptPubKey": {
          "asm": "0 b1ee5f7591b8fb37ca97903b388dc39a859411fc",
          "hex": "0014b1ee5f7591b8fb37ca97903b388dc39a859411fc",
          "reqSigs": 1,
          "type": "witness_v0_keyhash",
          "addresses": [
            "bc1qk8h97av3hran0j5hjqan3rwrn2zegy0unusy49"
          ]
        }
      }
    ]
  },
  "unknown": {
  },
  "inputs": [
    {
      "witness_utxo": {
        "amount": 5.00000000,
        "scriptPubKey": {
          "asm": "0 e1c1955440a655dbdeb3b7f48a1206f86719912f",
          "hex": "0014e1c1955440a655dbdeb3b7f48a1206f86719912f",
          "type": "witness_v0_keyhash",
          "address": "bc1qu8qe24zq5e2ahh4nkl6g5ysxlpn3nyf0wyd5k2"
        }
      },
      "bip32_derivs": [
        {
          "pubkey": "022320f1cf72e7ba2cef6be32d7493ce3bd4c6a2575fe51ce260377adc165603d4",
          "master_fingerprint": "8038ecd9",
          "path": "m/84'/1'/0'/0/0"
        }
      ]
    }
  ],
  "outputs": [
    {
    },
    {
      "bip32_derivs": [
        {
          "pubkey": "03f41cc4362baf77cc25d30ae7415337a60e1c4b9851844ce9c057bbe00f3dabf5",
          "master_fingerprint": "8038ecd9",
          "path": "m/84'/1'/0'/1/0"
        }
      ]
    }
  ],
  "fee": 0.00002820
}

Once the transaction has been inspected and everything looks good, the transaction can now be signed using HWI.

$ cd ../HWI
$ ./hwi.py -f 8038ecd9 --testnet signtx cHNidP8BAHECAAAAAU8KWkCU7H4MYBiZHmLey6FavV3L3xLfy4tVEZoubx+2AAAAAAD+////AgDh9QUAAAAAFgAUVTwqL9q+W4jp29iZ19DlfMkbGb78eNcXAAAAABYAFLHuX3WRuPs3ypeQOziNw5qFlBH8AAAAAAABAR8AZc0dAAAAABYAFOHBlVRAplXb3rO39IoSBvhnGZEvIgYCIyDxz3Lnuizva+MtdJPOO9TGoldf5RziYDd63BZWA9QYgDjs2VQAAIABAACAAAAAgAAAAAAAAAAAAAAiAgP0HMQ2K693zCXTCudBUzemDhxLmFGETOnAV7vgDz2r9RiAOOzZVAAAgAEAAIAAAACAAQAAAAAAAAAA

Follow the onscreen instructions, check everything, and approve the transaction. The result will look like:

{"psbt": "cHNidP8BAHECAAAAAU8KWkCU7H4MYBiZHmLey6FavV3L3xLfy4tVEZoubx+2AAAAAAD+////AgDh9QUAAAAAFgAUVTwqL9q+W4jp29iZ19DlfMkbGb78eNcXAAAAABYAFLHuX3WRuPs3ypeQOziNw5qFlBH8AAAAAAABAR8AZc0dAAAAABYAFOHBlVRAplXb3rO39IoSBvhnGZEvIgICIyDxz3Lnuizva+MtdJPOO9TGoldf5RziYDd63BZWA9RIMEUCIQDMECVXsrFK5XbMQn5yVCvm3zWF1kdCgepf3RSqFDDmAAIgQtty07rN4zBWMjd1qVOtkgOHBAlGaO2Se3LkiNsABYcBAQMEAQAAACIGAiMg8c9y57os72vjLXSTzjvUxqJXX+Uc4mA3etwWVgPUGIA47NlUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgID9BzENiuvd8wl0wrnQVM3pg4cS5hRhEzpwFe74A89q/UYgDjs2VQAAIABAACAAAAAgAEAAAAAAAAAAA=="}

We can now take the PSBT, finalize it, and broadcast it with Bitcoin Core

$ cd ../bitcoin
$ src/bitcoin-cli finalizepsbt cHNidP8BAHECAAAAAU8KWkCU7H4MYBiZHmLey6FavV3L3xLfy4tVEZoubx+2AAAAAAD+////AgDh9QUAAAAAFgAUVTwqL9q+W4jp29iZ19DlfMkbGb78eNcXAAAAABYAFLHuX3WRuPs3ypeQOziNw5qFlBH8AAAAAAABAR8AZc0dAAAAABYAFOHBlVRAplXb3rO39IoSBvhnGZEvIgICIyDxz3Lnuizva+MtdJPOO9TGoldf5RziYDd63BZWA9RIMEUCIQDMECVXsrFK5XbMQn5yVCvm3zWF1kdCgepf3RSqFDDmAAIgQtty07rN4zBWMjd1qVOtkgOHBAlGaO2Se3LkiNsABYcBAQMEAQAAACIGAiMg8c9y57os72vjLXSTzjvUxqJXX+Uc4mA3etwWVgPUGIA47NlUAACAAQAAgAAAAIAAAAAAAAAAAAAAIgID9BzENiuvd8wl0wrnQVM3pg4cS5hRhEzpwFe74A89q/UYgDjs2VQAAIABAACAAAAAgAEAAAAAAAAAAA==
{
  "hex": "020000000001014f0a5a4094ec7e0c6018991e62decba15abd5dcbdf12dfcb8b55119a2e6f1fb60000000000feffffff0200e1f50500000000160014553c2a2fdabe5b88e9dbd899d7d0e57cc91b19befc78d71700000000160014b1ee5f7591b8fb37ca97903b388dc39a859411fc02483045022100cc102557b2b14ae576cc427e72542be6df3585d6474281ea5fdd14aa1430e600022042db72d3bacde33056323775a953ad92038704094668ed927b72e488db0005870121022320f1cf72e7ba2cef6be32d7493ce3bd4c6a2575fe51ce260377adc165603d400000000",
  "complete": true
}
$ src/bitcoin-cli sendrawtransaction 020000000001014f0a5a4094ec7e0c6018991e62decba15abd5dcbdf12dfcb8b55119a2e6f1fb60000000000feffffff0200e1f50500000000160014553c2a2fdabe5b88e9dbd899d7d0e57cc91b19befc78d71700000000160014b1ee5f7591b8fb37ca97903b388dc39a859411fc02483045022100cc102557b2b14ae576cc427e72542be6df3585d6474281ea5fdd14aa1430e600022042db72d3bacde33056323775a953ad92038704094668ed927b72e488db0005870121022320f1cf72e7ba2cef6be32d7493ce3bd4c6a2575fe51ce260377adc165603d400000000
e51392c82e13bbfe714c73361aff14ac1a1637abf37587a562844ae5a4265adf

Refilling the keypools

When the keypools run out, they can be refilled by using the getkeypool commands as done in the beginning, but with different starting and ending indexes. For example, to refill my keypools, I would use the following getkeypool commands:

$ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool 1000 2000
$ ./hwi.py -f 8038ecd9 getkeypool --wpkh --keypool --internal 1000 2000

The output can be imported with importmulti as shown in the Setup steps.

Derivation Path BIP Compliance

The instructions above use BIP 84 to derive keys used for P2WPKH addresses (bech32 addresses). HWI follows BIPs 44, 84, and 49. By default, descriptors will be for P2PKH addresses with keys derived at m/44h/0h/0h/0 for normal receiving keys and m/44h/0h/0h/1 for change keys. Using the --wpkh option will result in P2WPKH addresses with keys derived at m/84h/0h/0h/0 for normal receiving keys and m/84h/0h/0h/1 for change keys. Using the sh_wpkh option will result in P2SH nested P2WPKH addresses with keys derived at m/49h/0h/0h/0 for normal receiving keys and m/49h/0h/0h/1 for change keys.

To actually get the correct address type when using getnewaddress from Bitcoin Core, you will need to additionally set -addresstype=p2sh-segwit and -changetype=p2sh-segwit. This can be set in the command line (as shown in the example) or in your bitcoin.conf file.

Alternative derivation paths can also be chosen using the --path option and specifying your own derivation path.

You can’t perform that action at this time.