├── 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 |
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 |
--------------------------------------------------------------------------------