├── Makefile ├── README.md ├── README.md.sig └── offline /Makefile: -------------------------------------------------------------------------------- 1 | DEB_DIR=debs 2 | 3 | check: urlchecks sigchecks sumchecks 4 | 5 | urlchecks: README.md 6 | @for url in `sed -n 's,.*\(http[^")]*\).*,\1,p' $< | grep -v '$${IGNORED_URLS:-xxxxx}'`; do if wget -q --spider $$url; then echo $$url OK; else echo $$url fail; exit 1; fi; done 7 | 8 | sigchecks: README.md 9 | gpg --verify README.md.sig README.md 10 | 11 | sumchecks: README-sumchecks offline-sumchecks 12 | 13 | fetchdebs: README.md 14 | cd debs && wget `sed -n 's,.*\(http[^)]*\.deb\).*,\1,p' ../$<` 15 | 16 | README-sumchecks: README.md 17 | @sed -n '/^/{$!{ N; s/.*sha256sum \(.*\) --->[^`]*`\([a-f0-9]*\).*/\1 \2/p;}}' $< | while read FILE SUM; do echo -n $$FILE...; if [ "`sha256sum $$FILE`" != "$$SUM $$FILE" ]; then echo WRONG; exit 1; else echo OK; fi; done 18 | 19 | offline-sumchecks: offline 20 | @sed -n "s,^ *'\([^']*.deb\)':'\([^']*\)'.*,\1 \2,p" $< | while read FILE SUM; do if [ "`sha256sum $(DEB_DIR)/$$FILE`" != "$$SUM $(DEB_DIR)/$$FILE" ]; then echo $$FILE wrong; exit 1; else echo $$FILE OK; fi; done 21 | 22 | README.md.sig: README.md 23 | gpg -b README.md 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rusty's Remarkably Unreliable Guide To Bitcoin Storage: 2018 Edition 2 | 3 | ## About This Guide 4 | 5 | This is an opinionated guide to storing your bitcoin securely. You 6 | should read this before buying bitcoin. 7 | 8 | I wrote this because existing guides are all aimed at experts[1](#gmax-offline), 9 | assume knowledge you might not have, or skip over important things 10 | like backups, or actually spending your bitcoin. 11 | 12 | ## Check You Have The Right Guide! 13 | 14 | This guide is digitally signed by me: you can download it from 15 | [github](https://github.com/rustyrussell/bitcoin-storage-guide/tags) 16 | where you should see a green "Verified" next to the latest version. 17 | 18 | For the technically inclined you can also find my GPG fingerprint 19 | here, and my signature on this document in README.md.sig (`gpg --verify README.md.sig README.md`) 20 | 21 | * [Keybase](https://keybase.io/rusty) 22 | * [Twitter](https://mobile.twitter.com/rusty_twit/status/644559490646278144) 23 | * [My blog](https://ozlabs.org/~rusty/rusty-gpg.asc) 24 | 25 | ## About The Author 26 | 27 | I'm Rusty Russell, a Linux kernel hacker best known for writing 28 | iptables. I stumbled into bitcoin a few years ago and promptly got 29 | distracted by all the cool technology; I ended up employed by bitcoin 30 | innovation developers Blockstream because I told them bitcoin is like 31 | Linux and I know Linux (no, really!). 32 | 33 | Part of this salary deal was about $5000 of bitcoin per year, paid 34 | monthly after the first year. I decided to 35 | document what I'm doing in the hope that greater minds will provide 36 | feedback on what I did wrong. And then return my coins please... 37 | 38 | # Getting Started 39 | 40 | Bitcoins are digital cash; if you get my private key (a big, long, 41 | secret number), you can spend my bitcoins from anywhere in the world. 42 | This makes me nervous, and it should make you nervous too. 43 | 44 | Letting someone else hold my coins isn't viable either: the history of 45 | bitcoin is one of scams and hacks, and it will take decades before we 46 | have any idea which bitcoin services are reliable, and which will 47 | collapse and take the funds with them. 48 | 49 | There are four main concerns for those holding bitcoin: 50 | 51 | 1. **Failure**: All bitcoins will become worthless. 52 | This is a real possibility, but I can't help you with this one. 53 | 2. **Loss**: I will lose my private key and nobody will be able to spend the coins. 54 | 3. **Theft**: Someone will steal my private key and take my coins. 55 | 4. **Inconvenience**: It will be really hard for me to spend my coins when I want to. 56 | 57 | Loss protection, theft protection and convenience are all competing 58 | goals, so I'm going to provide a quick guide: 59 | 60 | 1. Beer money. For less than $50, I'd keep it in any online wallet 61 | you want. 62 | * Risks: they could get hacked, go bankrupt, or stop your account. 63 | * Pros: really easy to use. 64 | 65 | 2. Small investment. For less than $10000, my preference would be 66 | any service which doesn't control your keys, say using 2 of 3 67 | multi-sig, where you control 2 and they control 1. I've only got 68 | experience with the 69 | Greenbits wallet for Android[2](#greenbits-android) (you can also use Green Address's Chrome 70 | app[3](#greenaddress)). Their initial setup UX is mediocre, but they provide two 71 | factor for spending above your chosen limit, ability to save your 72 | wallet phrase in case you lose your phone, a failsafe if they 73 | ever vanish, and they don't have access to your coins. 74 | (Disclaimer: I have Blockstream stock, and they own GreenAddress). 75 | 76 | * Risks: if you use SMS verification on the same phone as the 77 | Greenbits wallet, software which hacks your phone could 78 | intercept that and spend your money. If you use a different 79 | method (eg. phone for SMS verification, laptop Chrome for the 80 | app), the hacker would have to hack both phone and laptop. 81 | * Pros: almost as easy to use. 82 | 83 | 3. Larger investment. For less than $50000, you could use a 84 | hardware wallet with a screen. The main options here are 85 | the Trezor[4](#trezor) and the Ledger[5](#ledger); it's probably possible to extract the secret key from 86 | the device, but it'd be fairly hard (thus, not worthwhile) and 87 | they'd almost certainly need physical access. 88 | 89 | * Risks: the device might be replaced (or "pre-initialized") before you get it. 90 | If someone hacks your laptop, they could silently change 91 | the address where you're sending the funds, so you want to 92 | double-check the address using another device for large 93 | transfers. They can't change the amount though. 94 | * Pros: still fairly easy to use. 95 | 96 | 4. Serious investment. For larger amounts, or the paranoid, a 97 | completely offline "safe" is a good idea. That's what this 98 | document is mainly about, and this is what I'm doing: I plan on 99 | being at Blockstream for a while, and not spending the bitcoin. 100 | 101 | * Risks: someone hacks your offline machine before you take it offline, 102 | or someone substitutes the address you're sending the funds to and 103 | you pay to the wrong place, or you lose the private keys, or you get 104 | frustrated with how long it takes to spend bitcoin and make a mistake. 105 | * Pros: most secure against theft. 106 | 107 | 5. More money than I will ever have. For those carrying millions, I 108 | don't have any useful advice; you need to find a professional who 109 | won't steal all your money. 110 | 111 | # Setting Up An Offline Safe 112 | 113 | We're going to use bitcoin-core, the bitcoin reference software, to generate a few bitcoin addresses on a machine which has 114 | no way of contacting the outside world, we're going to write down the 115 | private keys on paper, we're going to double-check them, and we're 116 | going to protect those pieces of paper. 117 | 118 | ## Create an Offline Safe 119 | 120 | You will need: 121 | 122 | * A pen 123 | * Four pieces of paper 124 | * Two USB keys (one least 2GB aka "big", one at least 4M aka "small"). 125 | * A laptop with two USB ports 126 | 127 | ### For The Extra Paranoid 128 | 129 | The extra paranoid will destroy or never reuse those the USB keys in 130 | any other machine, maim the laptop, ensure it stays offline forever or 131 | destroy it: you can simply buy a cheap $200 laptop or use an old one. 132 | 133 | ### Step 1: Download and Prepare Ubuntu 18.04 (20 minutes) 134 | 135 | Ubuntu is a simple, free operating system. It's easy to install and 136 | use, and downloaded thousands of times each day. You can get it from 137 | [http://releases.ubuntu.com/18.04/ubuntu-18.04-desktop-amd64.iso](http://releases.ubuntu.com/18.04/ubuntu-18.04-desktop-amd64.iso). 138 | You will need a recent laptop (if it's over 10 years old, it might not 139 | support 64 bit, and you'll get a message about "This kernel requires an x86-64 CPU"). 140 | 141 | You should check that you have the real Ubuntu, if you can. On Linux 142 | and MacOS this is easy, on Windows you'll need 143 | [sha256sum.exe](http://www.labtestproject.com/files/win/sha256sum/sha256sum.exe). 144 | Use the following commands to sum the file you downloaded, which should 145 | match the example below: 146 | 147 | * **MacOS**: shasum -a 256 /tmp/ubuntu-18.04-desktop-amd64.iso 148 | * **Windows**: sha256sum.exe ubuntu-18.04-desktop-amd64.iso 149 | * **Linux**: sha256sum /tmp/ubuntu-18.04-desktop-amd64.iso 150 | 151 | This should give a number 152 | 153 | `a55353d837cbf7bc006cf49eeff05ae5044e757498e30643a9199b9a25bc9a34` 154 | 155 | If the number you get is different, STOP. Something is badly wrong. 156 | 157 | Now we need put it on the big USB, so we can install it on the cheap 158 | laptop; here are instructions for 159 | [Windows](https://www.ubuntu.com/download/desktop/create-a-usb-stick-on-windows), 160 | [MacOS](https://www.ubuntu.com/download/desktop/create-a-usb-stick-on-mac-osx) 161 | and 162 | [Linux](https://www.ubuntu.com/download/desktop/create-a-usb-stick-on-ubuntu). 163 | 164 | #### Extra Paranoia (Optional) 165 | 166 | Use DVDs instead of the USB sticks, and a laptop which doesn't have a 167 | DVD burner so you know it can never write anything back. 168 | 169 | ### Step 2: Putting bitcoin on the small USB key (10 minutes) 170 | 171 | Format the small USB key and put the helper script which matches this HOWTO: 172 | 173 | * [offline](raw.githubusercontent.com/rustyrussell/bitcoin-storage-guide/v0.3.0/offline) 174 | 175 | You also need bitcoin and friends on the USB key: 176 | 177 | * [bitcoind](https://launchpad.net/~bitcoin/+archive/ubuntu/bitcoin/+files/bitcoind_0.16.0-bionic1_amd64.deb) 178 | * [libdb4.8++](https://launchpad.net/~bitcoin/+archive/ubuntu/bitcoin/+files/libdb4.8++_4.8.30-bionic3_amd64.deb) 179 | * [libboost-chrono1.65.1](https://mirrors.kernel.org/ubuntu/pool/main/b/boost1.65.1/libboost-chrono1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb) 180 | * [libboost-filesystem1.65.1](https://mirrors.kernel.org/ubuntu/pool/main/b/boost1.65.1/libboost-filesystem1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb) 181 | * [libboost-program-options1.65.1](https://mirrors.kernel.org/ubuntu/pool/main/b/boost1.65.1/libboost-program-options1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb) 182 | * [libboost-system1.65.1](https://mirrors.kernel.org/ubuntu/pool/main/b/boost1.65.1/libboost-system1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb) 183 | * [libboost-thread1.65.1](https://mirrors.kernel.org/ubuntu/pool/main/b/boost1.65.1/libboost-thread1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb) 184 | * [libevent-core](https://mirrors.kernel.org/ubuntu/pool/main/libe/libevent/libevent-core-2.1-6_2.1.8-stable-4build1_amd64.deb) 185 | * [libevent-pthreads](https://mirrors.kernel.org/ubuntu/pool/main/libe/libevent/libevent-pthreads-2.1-6_2.1.8-stable-4build1_amd64.deb) 186 | * [libminiupnpc10](https://mirrors.kernel.org/ubuntu/pool/main/m/miniupnpc/libminiupnpc10_1.9.20140610-4ubuntu2_amd64.deb) 187 | * [libssl1.1](https://mirrors.kernel.org/ubuntu/pool/main/o/openssl/libssl1.1_1.1.0g-2ubuntu4_amd64.deb) 188 | * [libzmq5](https://mirrors.kernel.org/ubuntu/pool/universe/z/zeromq3/libzmq5_4.2.5-1_amd64.deb) 189 | * [libsodium23](https://mirrors.kernel.org/ubuntu/pool/main/libs/libsodium/libsodium23_1.0.16-2_amd64.deb) 190 | * [libpgm-5.2-0](https://mirrors.kernel.org/ubuntu/pool/universe/libp/libpgm/libpgm-5.2-0_5.2.122~dfsg-2_amd64.deb) 191 | * [libnorm1](https://mirrors.kernel.org/ubuntu/pool/universe/libp/libpgm/libpgm-5.2-0_5.2.122~dfsg-2_amd64.deb) 192 | * [qrencode](https://mirrors.kernel.org/ubuntu/pool/universe/q/qrencode/qrencode_3.4.4-1build1_amd64.deb) 193 | * [libqrencode](https://mirrors.kernel.org/ubuntu/pool/universe/q/qrencode/libqrencode3_3.4.4-1build1_amd64.deb) 194 | 195 | #### Extra Paranoia (Optional) 196 | 197 | (TECHNICAL USERS ONLY) Use the "tar" command (on MacOS or Linux) to 198 | place the files directly onto the raw USB device, instead of using a 199 | filesystem. This avoids any potential filesystem exploits, and makes 200 | it harder for other files to sneak on. 201 | 202 | #### Extra Paranoia (Optional) 203 | 204 | Physically remove the wireless and bluetooth cards in your laptop, as 205 | well as the speaker. This makes it almost impossible for your private 206 | keys to leak out, even if the laptop were compromised somehow. 207 | 208 | It won't help if they laptop has been physically compromised with a 209 | secret transmitter, of course. But we have to stop somewhere! 210 | 211 | ### Step 3: Booting Ubuntu on Your Laptop (10 minutes) 212 | 213 | 1. Find the "airplane mode" icon on the keyboard. Mine is on F2; 214 | pressing "Fn" and F2 while the laptop is on should stop it 215 | transmitting anything. If you don't have one, goto step 3. 216 | 217 | 2. Turn the laptop on, and activate airplane mode. Then turn it off 218 | again. We're going to leave it in airplane mode from now on. 219 | 220 | 3. Put the big USB into a USB port and turn the laptop on. To make it 221 | start from that USB you usually need to hit F12, F2 or F1 during 222 | the boot sequence. Google is your friend here, for your particular 223 | laptop (or there may be a message on the screen). 224 | 225 | 4. You'll see a black screen with some text. You can select the "Try 226 | Ubuntu without Installing" option and hit return or simply wait. 227 | 228 | 5. You'll see a purple background with a dots blinking underneath 229 | the word Ubuntu as it boots up. Eventually you'll see a desktop. On 230 | the top right, you'll see 3 icons: left-click the top right, and 231 | you should see "Wi-Fi Not Connected". Left-click on that, then 232 | click on "Turn Off". Now you should see a little airplane on the 233 | top right of the screen. 234 | 235 | 5. Insert the small USB. 236 | 237 | 6. Start a terminal: we're going to do the rest as manual commands. Do this 238 | by clicking on the dots at the far bottom left, and typing "term". 239 | You'll see a TV icon with a `>_` in it: click on this. We're going 240 | to type into that box where it says `ubuntu@ubuntu:~$ `. 241 | 242 | 7. Let's make sure the script is the right one, and nobody has 243 | modified it. Type the following then hit enter (it's a single line): 244 | 245 | `sha256sum /media/ubuntu/*/offline` 246 | 247 | You should get the following result numbers and letters (ignore after the space). If not, STOP, something is wrong. 248 | 249 | `afc1503fdedb51adaa072719c8a54eafd59ea068311af487afea90033034059a /media/ubuntu/USBKEY/offline` 250 | 251 | 8. Hit F11 to make the terminal full screen. 252 | 253 | 9. Run the script by typing: `python3 /media/ubuntu/*/offline` 254 | 255 | ### Step 4: Generating Some Private Keys (5 minutes) 256 | 257 | 1. The script installs and runs the bitcoin program every time; it will complain 258 | if something goes wrong, and you should too. 259 | 260 | 2. Make sure nobody can see your laptop screen. No windows, no 261 | reflections. 262 | 263 | 3. Type "create" then press enter. 264 | 265 | 4. You will get an address like "342ftSRCvFHfCeFFBuz4xwbeqnDw6BGUey". 266 | Anyone can send bitcoin to this, and only the private key can 267 | spend it. You will also get a private key like 268 | "L2b68o8EwXQQfWTu7K77Y7Pz9hLqzfb5vjVviJ8NadLXMWHdGYAv". This is a 269 | standard key you could import into other wallets later if you 270 | wanted to. 271 | 272 | 5. Write down the public address(es) (3...) on one piece of paper, 273 | and write down the private key(s) (L... or K...) on three pieces 274 | of paper. It shows you where to split the key into two or three 275 | parts, too. 276 | 277 | 6. To generate more addresses and keys, you can do this as many times 278 | as you like. 279 | 280 | ### Step 5: Reboot and Check (5 minutes) 281 | 282 | Let's make sure we wrote down that private key correctly! 283 | 284 | 1. Hold down the power button until the laptop turns off. It will forget 285 | everything we've done. 286 | 287 | 2. Turn it back on, booting off the big USB key again. 288 | 289 | 3. Turn off networking, select "Try Ubuntu", put in the small USB key, 290 | open a terminal, press F11, and run `python3 /media/ubuntu/*/offline`. 291 | 292 | 4. Select "restore", and enter your private key (L... or K...). If 293 | you got it correct, it should show you the public address which 294 | matches. If you didn't, check for typos (you can use the left and 295 | right arrow keys to change your previous answer, and backspace to 296 | delete backwards). 297 | 298 | 5. Restore all the private keys you created to check them all. 299 | 300 | 6. Hold down the power button to turn the laptop off. 301 | 302 | #### Extra Paranoia (Optional) 303 | 304 | Never put those USB keys in another machine ever again, as it's 305 | possible that malicious software on your laptop could have written 306 | information about your keys to them. Keep them with the laptop, and 307 | never allow the laptop to go online ever again. 308 | 309 | ### Step 6: Store the Keys Safely 310 | 311 | The public address (3...) you can write on the fridge, record in your 312 | diary, or email to your own gmail account. The only reason to keep 313 | this secret is so that people don't know how many bitcoins you have. 314 | You can get this back as long as you have your private key, anyway. 315 | 316 | The private keys must never be disclosed. They're about 52 317 | letters-and-numbers long, and the first 1 and last 5 or 6 digits are 318 | redundant, so if someone got most of the other 45 they could figure 319 | out the rest. 320 | 321 | A simple scheme would be splitting the three copies of the private key 322 | into two pieces as shown by `offline`; keep the first parts at home, work, 323 | and your parent's house. The second parts at your friend's house, in 324 | your handbag, and in a sealed envelope with instructions wherever your 325 | will is stored (eg. with your lawyer). 326 | 327 | ## Sending Bitcoins to your Offline Safe. 328 | 329 | Simply send to that public address. 330 | 331 | For your own convenience when you come to spend it, you should record 332 | the transaction ID, the amount sent, and which output went to your 333 | public address: the first output is number 0, the second is number 1, 334 | etc (it will usually send some change to another address as well, 335 | hence two outputs). 336 | 337 | ### Extra Paranoia (Optional) 338 | 339 | Use multiple addresses, and send randomized amounts at different times 340 | on different days. Divide it up roughly using random-looking numbers, 341 | then for each one, flip a coin: if it's heads flip again. If that's 342 | heads, make it a round number of bitcoin, otherwise make it a round 343 | number of USD at the current exchange rate. This will blend in with 344 | other transactions fairly well. 345 | 346 | ## Spending Bitcoins from your Offline Safe (20 minutes) 347 | 348 | We will get your offline safe to create and sign a raw bitcoin 349 | transaction, which you can send out onto the bitcoin network for 350 | miners to include in blocks. 351 | 352 | Bitcoin is essentially a ledger of payments: an incoming transaction 353 | pays into your address, and you spend that by creating a transaction 354 | which sends coins to a new address. Your offline bitcoin progream obviously 355 | doesn't know what transactions exist, so you need to tell it what 356 | transaction paid you manually; it can be made to create a new 357 | transaction and sign it[1](#gmax-offline). There's a quick way, and a slow way. 358 | 359 | You'll need: 360 | 361 | 1. *Slow way*: the raw transaction which paid bitcoins into your 362 | address. This looks like 363 | "0100000001344630cbff61fbc362f7e1ff2f11a344c29326e4ee96e787dc0d4e5cc02fd0690 364 | 00000004a493046022100ef89701f460e8660c80808a162bbf2d676f40a331a243592c36d6b 365 | d1f81d6bdf022100d29c072f1b18e59caba6e1f0b8cadeb373fd33a25feded746832ec17988 366 | 0c23901ffffffff0100f2052a010000001976a914dd40dedd8f7e37466624c4dacc6362d8e7 367 | be23dd88ac00000000". And you'll have to type it all in without making mistakes. 368 | 369 | 2. OR *Quick way*: The amount, transaction ID and output number of the transaction 370 | which sent funds to your address. A transaction ID looks like 371 | "a9d4599e15b53f3eb531608ddb31f48c695c3d0b3538a6bda871e8b34f2f430c" 372 | and you'll have to type it all in without making mistakes. 373 | If you didn't record this when you sent it, a block explorer should 374 | be able to find it if you give it your public address. The first 375 | output is numbered "0", the second "1", etc. 376 | 377 | 3. The address you want to send the funds to. For large amounts, you 378 | should get this address in two different ways, to make sure someone 379 | else hasn't substituted it with their own address. This might mean 380 | getting it via your cell phone as well as a laptop, or even having 381 | the recipient read it out to you over the phone. 382 | 383 | 4. The fee rate you should pay to miners so they'll mine your 384 | transaction. I have no idea what this will be: 5000 satoshi per 385 | kilobyte is currently reasonable. Google for "bitcoin fee estimator" 386 | and you'll find several. 387 | 388 | 5. (Optional) Another address in your offline safe to send the 389 | change to, if you're not sending the entire amount. 390 | 391 | 6. A phone camera or QR code scanner to get the resulting transaction out. 392 | 393 | Now you have those: 394 | 395 | 1. Turn your laptop on, booting off the big USB key again. Turn 396 | off networking, select "Try Ubuntu", put in the small USB key, open 397 | a terminal, press F11, and run `python3 /media/ubuntu/*/offline`. 398 | 399 | 2. Restore the private key which received the payment. 400 | 401 | 3. Now enter the transaction you received (slow way) or the 402 | transaction ID (quick way). 403 | 404 | 4. Slow way: the program will now look through the transaction for 405 | which output paid to you, and how much it was. If you typed the 406 | transaction wrong, it probably will fail to work when you try to broadcast 407 | the final transaction (it may simply fail to decode the transaction 408 | immediately, if you're lucky). 409 | 410 | 5. Quick way: you will tell it the output number and the amount that 411 | was sent. If you get the output number or transaction ID wrong, it 412 | will fail when you try to broadcast the final transaction. If you 413 | get the amount wrong it will also fail then. 414 | 415 | 6. Now enter the fee rate, and address to pay to. Fees are required to 416 | get miners to include your transaction in a block. 417 | 418 | 7. Now enter the amount to send: to send it all (minus the fee), just 419 | hit enter. 420 | 421 | 8. If you didn't elect to send it all, enter one of your public 422 | addresses to receive the change. I don't recommend reusing 423 | addresses, since that makes it obvious which output is payment and 424 | which is change. 425 | 426 | 9. It will complain if the change amount is tiny, or the fee amount 427 | seems huge. 428 | 429 | 10. It will then describe the transaction; check this is correct. If 430 | you elected to receive change, write down the transaction id, 431 | output and amount for spending in future. 432 | 433 | 11. Press Enter and it will then show you the completed, raw transaction 434 | as a QR code. Google for "bitcoin send raw transaction" and 435 | you'll see various web sites where you can paste it in to send it 436 | to the bitcoin network. 437 | 438 | 12. If you don't want the transaction, don't send it: you can simply 439 | try again to create a new one. 440 | 441 | ### Extra Paranoia (Optional) 442 | 443 | Run your own bitcoin full node somewhere (online). Use `bitcoin-cli 444 | importaddress 342ftSRCvFHfCeFFBuz4xwbeqnDw6BGUey` (except with your 445 | own public key). You can do this once for each address, and use 446 | `bitcoin-cli getbalance` to see your bitcoins. Use `bitcoin-cli 447 | listtransactions "*" 1000 0 true` to show received and sent 448 | transactions. Use `bitcoin-cli getrawtransaction` to get a particular 449 | raw transaction. Use `bitcoin-cli estimatefee 6` to get the fee rate. 450 | 451 | If you need to use a block explorer to find transactions, connect via 452 | Tor. 453 | 454 | # Final Words On Security 455 | 456 | No security is perfect, but it's even more common for people to lose 457 | their keys altogether. Make sure your loved ones know your bitcoins 458 | exist, and where to ask for help if you get struck by lightning. 459 | 460 | Good luck! 461 | 462 | Rusty. 463 | 464 | # References and Other Resources 465 | 466 | [1] [Greg Maxwell's offline signing example with bitcoin 0.7](https://people.xiph.org/~greg/signdemo.txt) 467 | 468 | [2] [Greenbits Wallet for Android](https://play.google.com/store/apps/details?id=com.greenaddress.greenbits_android_wallet&hl=en) (NOT the older GreenAddress wallet, which has usability issues) 469 | 470 | [3] [Green Address](https://greenaddress.it/) 471 | 472 | [4] [The Trezor Hardware Wallet](https://bitcointrezor.com/) 473 | 474 | [5] [The Ledger Hardware Wallet](https://www.ledgerwallet.com/) 475 | 476 | # License 477 | 478 | 479 | Creative Commons License 480 |
This guide is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 481 | 482 | The offline script is licensed under CC0 (public domain). 483 | -------------------------------------------------------------------------------- /README.md.sig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustyrussell/bitcoin-storage-guide/9fdde120b8501fee5503ac04eae9295338c51271/README.md.sig -------------------------------------------------------------------------------- /offline: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # Simple script to save typos and other frustrations for offline wallets. 3 | # Licenced under CC0 (Public domain), by Rusty Russell 4 | import argparse 5 | import glob 6 | import json 7 | import os 8 | import readline 9 | import subprocess 10 | import sys 11 | import time 12 | 13 | DEBS={ 14 | 'bitcoind_0.16.0-bionic1_amd64.deb': 'd9ac2eada58aeeae671ce4c2c355ab7e4717bb7a2844eeb143a23a8a433ddd48', 15 | 'libboost-chrono1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb': 'ecb75ff0dbc0956585a6cddd7fd8c863593948b47c0c9b27ec734f5560283956', 16 | 'libboost-filesystem1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb': '8756b023a86222524cf6ef7884da92fd21ad53207b38e705f568a745dcea4cef', 17 | 'libboost-program-options1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb': 'dc13c690524ad6258b7897b2dd6723a3b3df29d745657a6ccbe7440d57f1b453', 18 | 'libboost-system1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb': '390e93c275504a03101de7e35d898f224dff2594ff802dcc83a936b5fca690cc', 19 | 'libboost-thread1.65.1_1.65.1+dfsg-0ubuntu5_amd64.deb': '3748600e4dbadfb84df6873d23117a3728ad1f3b71505062037d4caa346696b3', 20 | 'libdb4.8++_4.8.30-bionic3_amd64.deb': 'b3ddd5da73816484304a747b526039ec143fe70c969168981813461ba4080233', 21 | 'libevent-core-2.1-6_2.1.8-stable-4build1_amd64.deb': 'b3cffa9ac341236250ef786dd55d4cd7eac6a36c87541df1cf4038239761256b', 22 | 'libevent-pthreads-2.1-6_2.1.8-stable-4build1_amd64.deb': '67a8152b242882100487f6ea5c364e142b163777f6b4490902e374719726fdaf', 23 | 'libminiupnpc10_1.9.20140610-4ubuntu2_amd64.deb': '11902e06fda5689053b5bbf3f5861343eef4f2e725c70833bdcaa105f0e033ce', 24 | 'libpgm-5.2-0_5.2.122~dfsg-2_amd64.deb': '22db07cf0d827a1b96ae2243fc832f8b55d91543faebc0fbb374893b7b079b1b', 25 | 'libsodium23_1.0.16-2_amd64.deb': '2620628ebe077a92003464d544e273e00ac58c29d62c7a7d7178dc13ee913152', 26 | 'libssl1.1_1.1.0g-2ubuntu4_amd64.deb': '82e424d50e144320319414a1964d3a980abde4bc39fc7d8c2885819328cb5cb4', 27 | 'libzmq5_4.2.5-1_amd64.deb': 'a584e32363a975735aefec1a11edc84cc8444eca8157b590d44c229d7500ead5', 28 | 'libqrencode3_3.4.4-1build1_amd64.deb': 'e2815703e5ed29f47a8434fbc23535b7bdd938e4483c925bfbc92414f2715d56', 29 | 'qrencode_3.4.4-1build1_amd64.deb': '7b46f0f4d2a985f7130c14902b1cbfae1b26558d2609f4b38e4196d6321fe18c', 30 | 'libnorm1_1.5r6+dfsg1-6_amd64.deb': 'd16894811280faa258b132786870b9adfc264d50bf844c0ee990291114652da9', 31 | } 32 | 33 | def prefilled_input(prompt, text): 34 | def prefill_hook(): 35 | if text is not None: 36 | readline.insert_text(text) 37 | readline.redisplay() 38 | 39 | readline.set_pre_input_hook(prefill_hook) 40 | result = input(prompt) 41 | readline.set_pre_input_hook() 42 | return result 43 | 44 | def check_ubuntu(): 45 | size=1921843200 46 | cdrom='a55353d837cbf7bc006cf49eeff05ae5044e757498e30643a9199b9a25bc9a34' 47 | 48 | output=subprocess.run('dd status=none if=/dev/cdrom count={} | sha256sum'.format(int(size / 512)), 49 | shell=True, check=True, universal_newlines=True, 50 | stdout=subprocess.PIPE).stdout 51 | if output != cdrom + " -\n": 52 | sys.exit('Bad ubuntu image') 53 | 54 | def goto_debdir(): 55 | offline=glob.glob('/media/ubuntu/*/offline') 56 | if len(offline) != 1: 57 | sys.exit('More than one offline script found inserted?') 58 | os.chdir(os.path.dirname(offline[0])) 59 | 60 | def check_debs(): 61 | for d in DEBS.keys(): 62 | output=subprocess.run(['sha256sum', d], 63 | universal_newlines=True, check=True, 64 | stdout=subprocess.PIPE).stdout 65 | if output != '{} {}\n'.format(DEBS[d], d): 66 | sys.exit('Bad package {}'.format(d)) 67 | 68 | def install_debs(): 69 | cmd=['sudo', 'dpkg', '-i'] 70 | cmd.extend(DEBS.keys()) 71 | subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL) 72 | 73 | def stop_bitcoin(): 74 | subprocess.run(['bitcoin-cli', 'stop'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 75 | print("stopping bitcoind") 76 | 77 | def start_bitcoin(): 78 | subprocess.Popen('bitcoind') 79 | 80 | # Make sure it's started. 81 | end=time.clock() + 10 82 | while time.clock() < end: 83 | if subprocess.run(['bitcoin-cli', 'ping'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0: 84 | return 85 | time.sleep(0.5) 86 | 87 | # Try stopping, just in case. 88 | stop_bitcoin() 89 | raise Exception('Starting bitcoind timed out') 90 | 91 | # Helper to run bitcoin-cli 92 | def bitcoin_cli(*args): 93 | cmd=['bitcoin-cli'] 94 | cmd.extend(args) 95 | return subprocess.run(cmd, 96 | universal_newlines=True, 97 | stdout=subprocess.PIPE) 98 | 99 | # Helper to run bitcoin-cli, which shouldn't fail. Returns stdout. 100 | def bitcoin_cli_nofail(*args): 101 | cmd=['bitcoin-cli'] 102 | cmd.extend(args) 103 | return subprocess.run(cmd, 104 | check=True, 105 | universal_newlines=True, 106 | stdout=subprocess.PIPE).stdout 107 | 108 | # Helper to run bitcoin-cli, which shouldn't fail. Returns stdout, \n removed. 109 | def bitcoin_cli_simple(*args): 110 | return bitcoin_cli_nofail(*args).rstrip() 111 | 112 | # Helper to parse bitcoin-cli output as JSON. 113 | def json_from_bitcoin_cli(*args): 114 | return json.loads(bitcoin_cli_nofail(*args)) 115 | 116 | def new_key(): 117 | print('Generating address', end='...', flush=True) 118 | time.sleep(1) 119 | tmpaddr=bitcoin_cli_simple('getnewaddress', 'p2sh-segwit') 120 | tmpprivkey=bitcoin_cli_simple('dumpprivkey', tmpaddr) 121 | print('SUCCESS') 122 | 123 | print('') 124 | print('Public address: {}'.format(tmpaddr)) 125 | print('Private key (keep secret!): {}'.format(tmpprivkey)) 126 | print('Split in two: {} {}'.format(tmpprivkey[:24],tmpprivkey[24:])) 127 | print('Split in three: {} {} {}'.format(tmpprivkey[:16],tmpprivkey[16:31],tmpprivkey[31:])) 128 | print('') 129 | 130 | input('Press Enter') 131 | 132 | # See https://en.bitcoin.it/wiki/Proper_Money_Handling_(JSON-RPC) 133 | def bitcoin_to_satoshi(value): 134 | return int(round(value * 1e8)) 135 | 136 | def satoshi_to_bitcoin(amount): 137 | return float(amount / 1e8) 138 | 139 | def get_p2sh_addresses(): 140 | # We want the P2SH addresses. 141 | P2SH_prefix='3' 142 | return [addr for addr in json_from_bitcoin_cli('getaddressesbyaccount', '') if addr.startswith(P2SH_prefix)] 143 | 144 | def check_key(): 145 | global addr 146 | global privkey 147 | addr=None 148 | privkey=prefilled_input('Enter your private key (UPPER and lower case matters!): ', 149 | privkey) 150 | 151 | # There's no good way to figure out the address, except diff before 152 | # and after. Blech! 153 | before_addresses=set(get_p2sh_addresses()) 154 | if bitcoin_cli('importprivkey', privkey.replace(" ", "")).returncode == 0: 155 | print('Key is VALID!') 156 | after_addresses=set(get_p2sh_addresses()) 157 | 158 | diff=after_addresses - before_addresses 159 | if len(diff) == 1: 160 | addr=diff.pop() 161 | print('Public address is {}'.format(addr)) 162 | elif len(diff) == 0: 163 | print('Re-import of existing key? One of:') 164 | for k in after_addresses: 165 | print(' ' + k) 166 | addr=input('Please enter the address: ') 167 | if addr not in after_addresses: 168 | print('{} is not one of those. Try again'.format(addr)) 169 | addr=None 170 | else: 171 | raise Exception('More than one address created from that privkey?') 172 | else: 173 | print('Invalid private key (starts with L or K)') 174 | 175 | def spend_tx(): 176 | global addr 177 | global privkey 178 | global tx 179 | global vout 180 | global input_amount 181 | global fee_rate 182 | global dest_addr 183 | global change_addr 184 | 185 | print('Generating public keyscript for {}'.format(addr), end='...', flush=True) 186 | scriptpubkey = json_from_bitcoin_cli('validateaddress', addr)['scriptPubKey'] 187 | print('SUCCESS') 188 | tx=prefilled_input('Enter transaction ID (64 characters) or raw transaction: ', 189 | tx) 190 | 191 | if len(tx) == 64: 192 | # Make sure it's hex! 193 | try: 194 | int(tx,16) 195 | except ValueError: 196 | print('Bad transaction ID. Try again') 197 | return 198 | 199 | txid=tx 200 | vout=int(prefilled_input('Enter output number which paid to {} (first is 0, second is 1, etc): '.format(addr), vout)) 201 | input_amount=float(prefilled_input('Enter amount it paid (in bitcoins): ', input_amount)) 202 | else: 203 | decode=bitcoin_cli('decoderawtransaction', tx) 204 | if decode.returncode != 0: 205 | print('Decoding transaction FAILED. Typo? Try again') 206 | return 207 | 208 | txjson=json.loads(decode.stdout) 209 | txid=txjson['txid'] 210 | 211 | print('Transaction ID: {}'.format(txid)) 212 | vout=None 213 | for v in txjson['vout']: 214 | if v['scriptPubKey']['hex'] == scriptpubkey: 215 | print('Output {} pays {} to us'.format(v['n'], v['value'])) 216 | if vout is not None: 217 | print('More than one output pays to us. Failing.') 218 | return 219 | vout=v['n'] 220 | input_amount=v['value'] 221 | 222 | if vout is None: 223 | print('No output pays to us. Failing.') 224 | return 225 | 226 | input_amount_satoshis=bitcoin_to_satoshi(input_amount) 227 | 228 | fee_rate=int(prefilled_input('Fee per kilobyte (in satoshis, eg 100000): ', fee_rate)) 229 | dest_addr=prefilled_input('Address to send to: ', dest_addr) 230 | 231 | dest_amount=input('Amount to send (hit ENTER to send as much as possible): ') 232 | if dest_amount != '': 233 | dest_amount_satoshis=bitcoin_to_satoshi(float(dest_amount)) 234 | change_addr=prefilled_input('Address for change: ', change_addr) 235 | 236 | # Tx with two outputs is about 170 vbytes. 237 | fee_satoshis=int(fee_rate * 170 / 1000) 238 | change_amount_satoshis=input_amount_satoshis - dest_amount_satoshis - fee_satoshis 239 | 240 | # Sanity check for tiny or negative change (or weird fee). 241 | if change_amount_satoshis < fee_satoshis: 242 | print("Change amount {:.8f} too small (fee is {:.8f})".format(satoshi_to_bitcoin(change_amount_satoshis), satoshi_to_bitcoin(fee_satoshis))) 243 | return 244 | else: 245 | # Tx with one output is about 135 vbytes. 246 | fee_satoshis=int(fee_rate * 135 / 1000) 247 | dest_amount_satoshis=input_amount_satoshis - fee_satoshis 248 | change_addr=None 249 | 250 | # Sanity check fee. 251 | if fee_satoshis > input_amount_satoshis: 252 | print('Fee {:.8f} is greater than input amount {:.8f}' 253 | .format(satoshi_to_bitcoin(fee_satoshis), 254 | satoshi_to_bitcoin(input_amount_satoshis))) 255 | return 256 | 257 | if change_addr is None: 258 | changestr='' 259 | else: 260 | changestr=' (change to {})'.format(change_addr) 261 | 262 | print('') 263 | print('Spending {:.8f} to send {:.8f} to {}{}'.format(satoshi_to_bitcoin(input_amount_satoshis), satoshi_to_bitcoin(dest_amount_satoshis), dest_addr, changestr)) 264 | 265 | print('Paying miners a fee of {:.8f} bitcoins ({:5f}%)'.format(satoshi_to_bitcoin(fee_satoshis), fee_satoshis * 100 / dest_amount_satoshis)) 266 | 267 | if fee_satoshis > bitcoin_to_satoshi(0.001): 268 | print('WARNING: Fee of {} bitcoins seems really large.' 269 | .format(satoshi_to_bitcoin(fee_satoshis))) 270 | print('This fee goes straight to the miners.') 271 | # Simply refuse to pay more than 5% fee ever. 272 | if fee_satoshis * 20 >= dest_amount_satoshis: 273 | print('Refusing to let you do that. Sorry.') 274 | return 275 | 276 | # Create transaction. 277 | print('Creating raw transaction', end='...', flush=True) 278 | if change_addr is not None: 279 | # Random order of dest, change 280 | if os.urandom(1)[0] >= 128: 281 | outputs='{{"{}":{},"{}":{}}}'.format(dest_addr, satoshi_to_bitcoin(dest_amount_satoshis), change_addr, satoshi_to_bitcoin(change_amount_satoshis)) 282 | our_output=1 283 | else: 284 | outputs='{{"{}":{},"{}":{}}}'.format(change_addr, satoshi_to_bitcoin(change_amount_satoshis), dest_addr, satoshi_to_bitcoin(dest_amount_satoshis)) 285 | our_output=0 286 | else: 287 | outputs='{{"{}":{}}}'.format(dest_addr, satoshi_to_bitcoin(dest_amount_satoshis)) 288 | 289 | raw_tx=bitcoin_cli('createrawtransaction', 290 | '[{{"txid":"{}","vout":{}}}]'.format(txid, vout), 291 | outputs) 292 | if raw_tx.returncode != 0: 293 | print('FAILED.') 294 | print('Malformed transaction or addresses? Try again.') 295 | return 296 | 297 | print('SUCCESS') 298 | 299 | redeemscript=json_from_bitcoin_cli('validateaddress', addr)['hex'] 300 | print('Signing raw transaction', end='...', flush=True) 301 | signed_tx=json_from_bitcoin_cli('signrawtransaction', raw_tx.stdout.rstrip(), 302 | '[{{"txid":"{}","vout":{},"scriptPubKey":"{}","redeemScript":"{}","amount":{}}}]'.format(txid, vout, scriptpubkey, redeemscript,input_amount)) 303 | print('SUCCESS') 304 | 305 | if change_addr is not None: 306 | output_txid=json_from_bitcoin_cli('decoderawtransaction', raw_tx.stdout.rstrip())['txid'] 307 | print("Transaction (change) to spend next time: {} output #{} amount {}" 308 | .format(output_txid, our_output, satoshi_to_bitcoin(change_amount_satoshis))) 309 | 310 | print('Payment to broadcast: {}'.format(signed_tx['hex'])) 311 | 312 | input('Press Enter to show qrcode') 313 | subprocess.run(['qrencode', '-i', '-t', 'UTF8', signed_tx['hex']]) 314 | 315 | # Because sys.tracebacklimit=0 doesn't work in python3. From 316 | # http://stackoverflow.com/questions/27674602/hide-traceback-unless-a-debug-flag-is-set 317 | def exceptionHandler(exception_type, exception, traceback): 318 | print('{}: {}'.format(exception_type.__name__, exception)) 319 | 320 | parser = argparse.ArgumentParser(description='Offline bitcoin helper script') 321 | parser.add_argument('--verbose', action='store_true', help='Verbose debug mode') 322 | args=parser.parse_args() 323 | 324 | # No tracebacks for normal users please. 325 | if args.verbose is False: 326 | sys.excepthook = exceptionHandler 327 | 328 | # These are globals. Typos are common, so we use previous answers to 329 | # pre-populate input fields. 330 | addr=None 331 | privkey=None 332 | tx=None 333 | vout=None 334 | input_amount=None 335 | fee_rate=None 336 | dest_addr=None 337 | change_addr=None 338 | 339 | print("Rusty's Remarkable Unreliable Bitcoin Storage Script") 340 | print('----------------------------------------------------'); 341 | goto_debdir() 342 | print('Checking bitcoin and friends', end='...', flush=True) 343 | check_debs() 344 | print('OK') 345 | print('Installing bitcoin and friends', end='...', flush=True) 346 | install_debs() 347 | print('OK') 348 | 349 | print('Starting bitcoin', end='...', flush=True) 350 | start_bitcoin() 351 | print('OK') 352 | 353 | try: 354 | while True: 355 | print('') 356 | print('create - Create a new private key and public address.') 357 | print('restore - Restore a private key and check it is valid.') 358 | if addr is not None: 359 | print('spend - spend a transaction with a private key.') 360 | print('(To quit, just power off the entire machine)') 361 | choice=input('What do you want to do? ') 362 | if choice == 'create': 363 | new_key() 364 | elif choice == 'restore': 365 | check_key() 366 | elif choice == 'spend': 367 | if addr is None: 368 | print("Use 'restore' to enter the private key first") 369 | else: 370 | spend_tx() 371 | else: 372 | print("I don't understand '{}'".format(choice)) 373 | finally: 374 | stop_bitcoin() 375 | sys.exit(1) 376 | --------------------------------------------------------------------------------