Making your own private key is a great feeling. I’m going to show you how, and keep it easy to follow. Most people will not go to the extreme of doing everything in this guide, but reading it will give some appreciation to how bitcoin storage works, the level of safety that is possible, and just how achievable it is with some planning and effort.
Most of it can be done without a computer, but towards the end, a computer is essential to get your extended xpub and wallet addresses. This really should be done on an air-gapped computer for maximum safety. This is how to make one. In 2020, it costs about $50 USD for all the parts.
A Bitcoin private key is 128 to 256 bits long. Meaning 128 to 256 zero’s or one’s. You can pick them at random from your head, but it probably won’t be truly random. Maybe it’ll be fine, but I wouldn’t advise trying it.
These are the steps involved:
Step 1 – Generate a large binary number. I will explain the dice method to do this
Step 2 – Convert the binary number to words from the BIP39 word list
Step 3 – Calculate the final word, which is a checksum
Step 4 – Check the resulting xpub/zpub/Zpub and addresses from two independent sources
Step 5 – Repeat steps 1 to 4 to make several private keys (for making a multi-signature wallet)
If you want to back up your key to metal (to prevent loss in a fire) you can easily etch the binary key as shown in this video with a cheap engraving pen (writing words with a steady hand is hard, vertical and horizontal lines are easy).
STEP 1 – Generate entropy. The large number
The BIP 39 list of words are seemingly random words, in alphabetical order, numbered 0 to 2047. Each word represents an 11 bit number (eleven 0’s and 1’s). In 11 digit binary, 0 is 00000000000, and 2047 is 11111111111. This is not a coincidence.
Put in a different way, the numbers 0 to 2047 in decimal, fit into 0 to 11111111111 in binary.
The first word on the list is “abandon”, and codes for the number 00000000000. Word “ability” is next on the list and codes for the number 00000000001. The last word is “zoo” and codes for 11111111111.
24 words, each representing 11 binary digits, is a convenient way to write 256 bits of random data. Note, 11 x 24 is 264 though, the last 8 bits are not random, they make up part of the checksum (256 + 8 = 264).
If you want to use a 12 word seed, you can do that, but you make a key with only 132 bits (128 bits of randomness, and 4 more bits for the checksum. The procedure is mostly the same as for the 24 word version described here.)
Make 256 bits of data with dice:
You can do this with one die but it’s slow. You can use several dice at once. Here is how I made a sample private key, with 5 dice:
- 5 dice are to be rolled together multiple times until 256 readouts are recorded.
- Dice shall be read from left to right. If too close to call, read top to bottom.
- A roll of 1, 2, or 3 records a zero.
- A roll of 4, 5 or 6 records a one.
Here are my results for this example:
11100100111 11100111000 01011111110 10101001010 00010010000
01011100111 00100010101 11011001010 00100000101 01111100101
00110001010 11101100101 10101000101 01001001101 10111010101
01011011001 00010011111 01111000110 11001110011 01000011101
10111010000 01010000000 11001000010 101
Step 2 – Convert binary numbers to words
You can look up the binary numbers and find the word from a binary look-up table. The One linked is courtesy of @bjdweck from his “Rudefox” project. You can get the first 23 words this way. To get the final word, skip to the next step (checksum).
Alternatively, and preferably, you can look up from the original source, the BIP 39 words form the Github page. These words are numbered 1 to 2047, in decimal, not binary. The 11 digit binary groups from the dice rolls need to be converted to decimal, and then you look up the word.
To convert to decimal, you can use an online calculator, but we don’t want to put these dice rolls into an internet connected computer. This is a quick video (I suggest you watch at double speed) showing and easy way to convert binary to decimal manually.
Decimal Lookup Procedure
If we had a binary of 000000000, ie zero, then we would need the first word on the list. In the Word List from Github. This word is “abandon” and it is unfortunately labelled as word “1” instead of “zero”. So any number we convert from binary to decimal, we also need to add 1 to get the right “look up number” for this table.
Decimal plus 1 & word
For word 1, the decimal is 1831 on the list. If you look that up, you’ll get the wrong word because the list starts with 1 instead of zero. So add one to 1831, and look up word 1832. The word is “top”, not “tooth”
Once you make 23 words, you’ll have 3 digits remaining, which will be used for the checksum, next:
Step 3 – The Checksum
In this example the left over digits are 101. (For a 12 word seed, there will be a remainder of 7 digits)
Take all the digits from the dice rolls (256- which includes the 11×23=253 word digits, and the final 3 remainder) and get the SHA256 hash.
Next, take the first two digits of the resulting hash (they are hexidecimal, meaning each digit represents up to the value 16… 0 1 2 3 4 5 6 7 8 9 a b c d e f) and convert them to 4 digit binary.
You will have two sets of 4-digit binaries, so 8 binary digits in total makes the non-random component of the checksum. Add them to the random component (remainder of the dice rolls- those last three digits; in this example it’s binary 101), and you have 11 binary digits (8 non-random + 3 random). That makes the 24th word.
For a 12 word seed, you only need to take the first digit of the hash, convert to a 4 digit binary, and add that to the 7 digit remainder to make an 11 digit binary.
If you just guessed the final word, you can avoid the calculation to get the checksum. You simply guess until your wallet software accepts the checksum as valid. There are multiple possible checksums. Guessing a 12th word has a 1 in 16 chance of success. Guessing a 24th word has a 1 in 256 chance. If you guess, you replace the randomness from remainder dice rolls with randomness of guessing one of the several possible valid checksum words. A 12 word seed has 128 possible checksum words, a 24 word seed has four.
Use an air-gapped Linux computer, and type this in the terminal:
echo 1110010011111100111000010111111101010100101000010010000010111001110010001010111011001010001000001010111110010100110001010111011001011010100010101001001101101110101010101101100100010011111011110001101100111001101000011101101110100000101000000011001000010101 | shasum -a 256 -0
This is “echo” followed by a space, followed by the binary number, a space, a pipe symbol, a space, “shasum”, a space, a hyphen, “a”, a space, “256”, a space, a hyphen, then”0″. That’s a zero at the end not the letter “o”.
The “-0” tells the hash function to treat the “0” and “1” characters for their binary value, not their printed ASCII characters.
- The printout of character “0” is actually 00110000. We want the value “0”.
- The printout of character “1” is actually 00110001. We want the value “1”.
This was my output of the hash:
The first two digits are 8 and 4. The binary values, using 4 digits, are 1000 and 0100. (If you need to convert one of the letters, remember that the decimals for the letters are: a=10, b=11, c=12, d=13, e=14 and f=15.)
Adding these two new binary digits to the remainder digits of 101, we get: 10110000100. In decimal, this is 1412. Add 1 to 1412, and we get 1413. Look up word 1413 = “rack”
The word “rack” is the 24th checksum word.
The terminal in Linux (and Mac) keeps a history of your commands. This is dangerous to leave in the memory. Type “history -c” to clear the private key from the history.
Step 4 – check extended public keys and addresses
This also has to be on the air-gapped computer, so we are never typing sensitive information into a computer connected to the internet.
We are going to transfer data from an internet computer to the air-gapped computer by a USB pen drive. I’ll call this the “forward direction” of data flow. This is the weakest point of security; there is a theoretical possibility of malicious software being transferred (in the forward direction) from the internet connected computer to the air-gap computer, however, we NEVER bring the USB drive back to the internet computer (reverse direction), so no information is leaked away from the air-gapped machine. Data transmission in the forward direction, in the absence of any reverse direction, is still a weakness though; nothing is perfect, but this is pretty close.
Ideally, get a brand new USB pen drive, and format it. Then copy Ian Coleman’s webpage to the drive.
Insert the drive into the air-gapped computer, and open the web page from the drive. On a Raspberry Pi Zero, it runs VERY slow, just be patient.
You’ll see you can change the word number from the default of 15, to 24, and then enter your words.
Then, check the box “Show entropy details” – you can then see the zero’s and one’s should match your dice rolls. There is also a list of the decimal numbers corresponding to the words. And you can see the checksum binary listed separately.
Make an empty text file on the USB drive.
Click the BIP84 tab, to switch over to native segwit. Then copy the “account extended public key” (starts with zpub). Shown below, highlighted:
Scroll down a bit more and copy the first address (starts with bc1q) from the website, and paste it into the text file. This is to confirm the zpub creates the correct address on our second independent check later on.
Next we want to enter the seed words into an Electrum Wallet (on the air-gapped computer of course) to check that we get the same zpub and first address.
Open Electrum. Name your wallet and click next:
Choose “standard” wallet:
Then “I already have a seed”:
Enter your seed. This is safe to do because your computer is not connected to the internet.
You are unable to click next, because Electrum doesn’t recognise the seed. It doesn’t use BIP39 seeds by default. Click “Options” then check “BIP 39”:
Now the seed should be recognised and you can click Next. Then leave the default, native-segwit, for bc1 type addresses, and zpubs.
Then you can add a password to encrypt the file on the computer. Your wallet will load, but you can’t see the address list by default.
Go to “view” and “show addresses”:
Now a tab appears which lists your addresses. Check the first address matches the one you saved earlier:
Then go to the menu, “Wallet” and “Information”:
You will see the zpub. Check it matches with what you saved in the text file.
Next we want to create a Zpub (capital Z) associated with our seed, for multisignature wallets. Electrum creates this for us, but we need to verify it is doing it correctly. I had not found a way to do this with a web tool, and was using ColdCard Wallet’s export feature which is fine, but can be laborious. Thank you to @MinimalStructur who showed me this solution – It can be done on Ian Coleman’s BIP 39 online tool, it’s just tricky to find it…
After generating the seed, scroll down and select the BIP141 tab. This allows you to manually type in the derivation path. Do so as above, which is the Electrum default. Note that you must type it like that exactly. If you forget the apostrophes you’ll get the wrong Zpub.
You also need to modify the Script Semantics. Click the field with your mouse. It’s not easy to notice, but it’s actually a drop down menu.
You then should get an identical Zpub to what Electrum shows you.
Step 5 – Repeat steps 1 to 4
Repeat as often as you need to make the number of keys you desire.
If you followed the steps in this guide, you now have several private keys. Seriously well done! Store them securely. Consider memorising in addition to storing physically. This article explains my memorising technique for mnemonic seeds.
Having multiple private keys you generated yourself is very satisfying, very secure, and allows you to build a multisignature wallet. This article explains how to make a multisignature wallet.
Make sure, after going to all this trouble, you don’t leak your addresses to a public bitcoin node, and give away your privacy. Learn why and how to run a node, and connect your own fresh multisig Electrum wallet to that node, without ever connecting to a public node. Not even once. If you do, you have to start all over, and move your bitcoin to the new wallet. Painful.
In case you are feeling generous: