Skip to content

Commit

Permalink
Add offline signing tutorial
Browse files Browse the repository at this point in the history
  • Loading branch information
BrandonOdiwuor committed Sep 13, 2023
1 parent ab42b2e commit f22625f
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 0 deletions.
1 change: 1 addition & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ The Bitcoin repo's [root README](/README.md) contains relevant information on th
- [Init Scripts (systemd/upstart/openrc)](init.md)
- [Managing Wallets](managing-wallets.md)
- [Multisig Tutorial](multisig-tutorial.md)
- [Offline Signing Tutorial](offline-signing-tutorial.md)
- [P2P bad ports definition and list](p2p-bad-ports.md)
- [PSBT support](psbt.md)
- [Reduce Memory](reduce-memory.md)
Expand Down
193 changes: 193 additions & 0 deletions doc/offline-signing-tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Offline Signing Tutorial

Welcome to this tutorial on how to sign transactions offline using PSBT (Partially Signed Bitcoin Transactions). This tutorial will guide you through the process of securely signing Bitcoin transactions using an offline wallet and an online watch-only wallet.

## Overview
In this tutorial, we have two hosts: [offline] which is totally offline and without a copy of the blockchain and [online] which is a regular online node, both running Bitcoin 25.0

In this example, we are going to create an `offline_wallet` on the [offline] host. We will then create a `watch_only_wallet` on the [online] host using descriptors imported from the `offline_wallet` and load funds to the wallet. we'll create a PSBT transaction using the `watch_only_wallet`, sign it with the `offline_wallet` then broadcast the transaction using the [online] host

### Requirements
- [jq](https://jqlang.github.io/jq/) installation - This tutorial uses jq to process JSON.

> [!NOTE]
> Tested using Signet with a connected host machine and an offline docker container (representing the offline wallet) both running Signet, should also work with Regtest and Mainnet
### Create and Prepare the `offline_wallet`

1. On the offline machine, create an offline wallet named `offline_wallet`.

```sh
[offline]$ ./src/bitcoin-cli -signet -named createwallet wallet_name="offline_wallet"
{
"name": "offline_wallet"
}
```

2. Export the offline wallet descriptors to a JSON file named `descriptors.json`.

```sh
[offline]$ ./src/bitcoin-cli -signet -rpcwallet="offline_wallet" listdescriptors | jq -r '.descriptors' >> /path/to/descriptors.json
```

> [!NOTE]
> The `descriptors.json` file will be exported to the online machine (e.g. using a USB) to create a watch-only wallet. (This is an easier way to export the descriptors compared to manual export)
### Create the online `watch_only_wallet`

1. On the online machine, create an online watch-only wallet named `watch_only_wallet` without private keys (disable_private_keys=true) and should be blank with no keys or HD seed (blank=true).
- The `watch_only_wallet` wallet will be used to track transactions received to the `offline_wallet` and for creating PSBT transactions.

```sh
[online]$ ./src/bitcoin-cli -signet -named createwallet wallet_name="watch_only_wallet" disable_private_keys=true blank=true
{
"name": "watch_only_wallet"
}
```

2. Import the `offline_wallet` descriptors to the online `watch_only_wallet` using the `descriptors.json` file created on the offline wallet

```sh
[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" importdescriptors "$(cat /path/to/descriptors.json)"
[
{
"success": true
},
{
"success": true
},
{
"success": true
},
{
"success": true
},
{
"success": true
},
{
"success": true
},
{
"success": true
},
{
"success": true
}
]
```
> [!NOTE]
> Importing the multiple descriptors from the `offline_wallet` provides the ability to generate a variety of address types by the [online] `watch_only_wallets`
### Load Funds to the `offline_wallet`
1. Generate an address for the `offline_wallet` using the `watch_only_wallet` to load funds.

```sh
[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" getnewaddress
tb1qtu5qgc6ddhmqm5yqjvhg83qgk2t4ewajg0h6yh
```

2. Visit a faucet like https://signet.bc-2.jp to load funds into the generated address.

3. Confirm the received funds (to the offline_wallet) using the online watch_only_wallet.
```sh
[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" listunspent
[
{
"txid": "0f3953dfc3eb8e753cd1633151837c5b9953992914ff32b7de08c47f1f29c762",
"vout": 1,
"address": "tb1qtu5qgc6ddhmqm5yqjvhg83qgk2t4ewajg0h6yh",
"label": "",
"scriptPubKey": "00145f2804634d6df60dd080932e83c408b2975cbbb2",
"amount": 0.01000000,
"confirmations": 4,
"spendable": true,
"solvable": true,
"desc": "wpkh([306c734f/84h/1h/0h/0/0]025932ccee7590158f7e08bb36290d135d30a0b045163da896e1cd7645ec4223a9)#xytvyr4a",
"parent_descs": [
"wpkh([306c734f/84h/1h/0h]tpubDCJnY92ib4Zu3qd6wrBXEjG436tQdA2tDiJU2iSJYjkNS1darssPWKaBfojhjUF5vMLBcxbN2r93pmFMz2zyTEZuNx9JDo9rWqoHhATW3Uz/0/*)#7mh08dkg"
],
"safe": true
}
]
```

### Create and Export a PSBT Transaction

1. Create a PSBT transaction using the online `watch_only_wallet`, sending funds to the `online_receiving_wallet` address. (`walletcreatefundedpsbt inputs[], outputs[address: amount]`)
- Export the psbt transaction to `funded_psbt.txt` for easy portability to the `offline_wallet`

```sh
[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" walletcreatefundedpsbt '[]' '[{"tb1q9k5w0nhnhyeh78snpxh0t5t7c3lxdeg3erez32": 0.009}]' | jq -r '.psbt' >> /path/to/funded_psbt.txt
[online]$ cat /path/to/funded_psbt.txt
cHNidP8BAHECAAAAAWLHKR9/xAjetzL/FCmZU5lbfINRMWPRPHWO68PfUzkPAQAAAAD9////AoA4AQAAAAAAFgAULajnzvO5M38eEwmu9dF+xH5m5RGs0g0AAAAAABYAFMaT0f/Wp2DCZzL6dkJ3GhWj4Y9vAAAAAAABAHECAAAAAY+dRPEBrGopkw4ugSzS9npzJDEIrE/bq1XXI0KbYnYrAQAAAAD+////ArKaXgAAAAAAFgAUwEc4LdoxSjbWo/2Ue+HS+QjwfiBAQg8AAAAAABYAFF8oBGNNbfYN0ICTLoPECLKXXLuyYW8CAAEBH0BCDwAAAAAAFgAUXygEY01t9g3QgJMug8QIspdcu7IiBgJZMszudZAVj34IuzYpDRNdMKCwRRY9qJbhzXZF7EIjqRgwbHNPVAAAgAEAAIAAAACAAAAAAAAAAAAAACICA7BlBnyAR4F2UkKuSX9MFhYCsn6j//z9i7lHDm1O0CU0GDBsc09UAACAAQAAgAAAAIABAAAAAAAAAAA=
```

### Decode and Analyze the PSBT Transaction

1. Decode and analyze the PSBT transaction on the `offline_wallet` using the `funded_psbt.txt` file.

```sh
[offline]$ ./src/bitcoin-cli -signet decodepsbt $(cat /path/to/funded_psbt.txt)
{
...
}
[offline]$ ./src/bitcoin-cli -signet analyzepsbt $(cat /path/to/funded_psbt.txt)
{
"inputs": [
{
"has_utxo": true,
"is_final": false,
"next": "signer",
"missing": {
"signatures": [
"5f2804634d6df60dd080932e83c408b2975cbbb2"
]
}
}
],
"estimated_vsize": 141,
"estimated_feerate": 0.00100000,
"fee": 0.00014100,
"next": "signer"
}
```

### Process and Sign the PSBT Transaction

1. Process, Sign and Finalize the PSBT transaction on the `offline_wallet`.
- Save the final PSBT hex to the `final_psbt.txt` file, to be exported and broadcasted by the `online_wallet`

```sh
[offline]$ ./src/bitcoin-cli -signet -rpcwallet="offline_wallet" walletprocesspsbt $(cat /path/to/funded_psbt.txt) | jq -r .hex >> /path/to/final_psbt.txt

[offline]$ cat ~/final_psbt.txt
0200000000010162c7291f7fc408deb732ff14299953995b7c83513163d13c758eebc3df53390f0100000000fdffffff028c4f010000000000160014dda0f427f67bfeca9f0e7252e458ee39b82c7e06a0bb0d00000000001600142da8e7cef3b9337f1e1309aef5d17ec47e66e5110247304402200d245ee92df8be0183c98fb26bcbc474307ccdb764877273644b4c4eb359138202206c4cf787f120828a812b47043902683e24d50a60216e3b2fe0f104be2806e54f0121025932ccee7590158f7e08bb36290d135d30a0b045163da896e1cd7645ec4223a900000000
```

### Broadcast the Finalized PSBT Transaction
1. Broadcast the signed and finalized PSBT transaction (on the `final_psbt.txt` file) using the online wallet

```sh
[online]$ ./src/bitcoin-cli -signet sendrawtransaction $(cat /path/to/final_psbt.txt)
c2430a0e46df472b04b0ca887bbcd5c4abf7b2ce2eb71de981444a80e2b96d52
```

### Confirm Wallet Balances

1. Confirm the updated balance of the offline wallet using the `watch_only_wallet`.

```sh
[online]$ ./src/bitcoin-cli -signet -rpcwallet="watch_only_wallet" getbalances
{
"mine": {
"trusted": 0.00085900,
"untrusted_pending": 0.00000000,
"immature": 0.00000000
},
"lastprocessedblock": {
"hash": "0000003065c0669fff27edb4a71928cb48e5a6cfcdf06f491a83fd86822d18a6",
"height": 159592
}
}
```

0 comments on commit f22625f

Please sign in to comment.