|
@@ -1,16 +1,16 @@
|
|
## Table of Contents
|
|
## Table of Contents
|
|
* [Introduction](#a_i)
|
|
* [Introduction](#a_i)
|
|
* [Obtaining the binary seed](#a_rs)
|
|
* [Obtaining the binary seed](#a_rs)
|
|
- * [Convert the seed to binary (legacy uncompressed addresses)](#a_ss)
|
|
|
|
- * [Scramble the seed and save to binary (non-legacy and altcoin addresses and passwords)](#a_cs)
|
|
|
|
|
|
+* [Convert the seed to binary (legacy uncompressed addresses)](#a_ss)
|
|
|
|
+* [Scramble the seed and save to binary (non-legacy and altcoin addresses and passwords)](#a_cs)
|
|
* [Generating the keys](#a_gk)
|
|
* [Generating the keys](#a_gk)
|
|
- * [Checking the result (optional, address example)](#a_cr)
|
|
|
|
|
|
+* [Checking the result (optional, address example)](#a_cr)
|
|
* [Converting the hex value to a password (password example)](#a_hpw)
|
|
* [Converting the hex value to a password (password example)](#a_hpw)
|
|
* [Hex to WIF by hand (address example)](#a_hw)
|
|
* [Hex to WIF by hand (address example)](#a_hw)
|
|
- * [Base-conversion utility](#a_bcu)
|
|
|
|
|
|
+* [Base-conversion utility](#a_bcu)
|
|
* [Converting an MMGen mnemonic to hexadecimal format](#a_mh)
|
|
* [Converting an MMGen mnemonic to hexadecimal format](#a_mh)
|
|
|
|
|
|
-#### <a name='a_i'>Introduction</a>
|
|
|
|
|
|
+#### <a id="a_i">Introduction</a>
|
|
|
|
|
|
If you’re considering using MMGen and are a Bitcoiner with a normal, healthy
|
|
If you’re considering using MMGen and are a Bitcoiner with a normal, healthy
|
|
degree of paranoia, then the following question will probably come to mind:
|
|
degree of paranoia, then the following question will probably come to mind:
|
|
@@ -27,7 +27,7 @@ combination of circumstances ever occurs.
|
|
In addition to private keys, this tutorial can also be used to recover passwords
|
|
In addition to private keys, this tutorial can also be used to recover passwords
|
|
generated with the `mmgen-passgen` command.
|
|
generated with the `mmgen-passgen` command.
|
|
|
|
|
|
-#### <a name='a_rs'>Obtaining the binary seed</a>
|
|
|
|
|
|
+#### <a id="a_rs">Obtaining the binary seed</a>
|
|
|
|
|
|
To keep things simple, we’ll assume you have a copy of your seed in hexadecimal
|
|
To keep things simple, we’ll assume you have a copy of your seed in hexadecimal
|
|
(mmhex) format. If your backup’s in mnemonic format, skip to the section
|
|
(mmhex) format. If your backup’s in mnemonic format, skip to the section
|
|
@@ -39,20 +39,24 @@ Okay, so let’s say you have a 128-bit seed with Seed ID `FE3C6545` and funds i
|
|
the first three legacy uncompressed (`L`) addresses of this seed. Here are the
|
|
the first three legacy uncompressed (`L`) addresses of this seed. Here are the
|
|
addresses:
|
|
addresses:
|
|
|
|
|
|
- FE3C6545 {
|
|
|
|
- 1 1JVi3qcNcjMM7cTR7y9ihKUG1yDLpKRJfL
|
|
|
|
- 2 15EfKymfe3v7mqCaL174hTWSgBLFAHvtaR
|
|
|
|
- 3 1CUDd6nPHdP5pT7nN8k2AA5WdKRaKPjmea
|
|
|
|
- }
|
|
|
|
|
|
+```text
|
|
|
|
+FE3C6545 {
|
|
|
|
+ 1 1JVi3qcNcjMM7cTR7y9ihKUG1yDLpKRJfL
|
|
|
|
+ 2 15EfKymfe3v7mqCaL174hTWSgBLFAHvtaR
|
|
|
|
+ 3 1CUDd6nPHdP5pT7nN8k2AA5WdKRaKPjmea
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
|
|
Since you might have your funds in Segwit (`S`) addresses, we’ll consider that
|
|
Since you might have your funds in Segwit (`S`) addresses, we’ll consider that
|
|
case too:
|
|
case too:
|
|
|
|
|
|
- FE3C6545 SEGWIT {
|
|
|
|
- 1 3LpkKqtGkcCukRrgEFWyCajSApioiEWeTw
|
|
|
|
- 2 3FYZQyWqBJcCjaSjCV9ZVj3gKyB9u8AYCX
|
|
|
|
- 3 37wM8hwt69qwH7hZHAMn6RVdc8vMuM1CwJ
|
|
|
|
- }
|
|
|
|
|
|
+```text
|
|
|
|
+FE3C6545 SEGWIT {
|
|
|
|
+ 1 3LpkKqtGkcCukRrgEFWyCajSApioiEWeTw
|
|
|
|
+ 2 3FYZQyWqBJcCjaSjCV9ZVj3gKyB9u8AYCX
|
|
|
|
+ 3 37wM8hwt69qwH7hZHAMn6RVdc8vMuM1CwJ
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
|
|
Keys for compressed (`C`), Bech32 (`B`) and altcoin addresses, as well as
|
|
Keys for compressed (`C`), Bech32 (`B`) and altcoin addresses, as well as
|
|
passwords, are generated in a way analogous to Segwit keys, so for them you’ll
|
|
passwords, are generated in a way analogous to Segwit keys, so for them you’ll
|
|
@@ -61,7 +65,9 @@ proceed as with the Segwit case.
|
|
Here’s the seed itself in `mmhex` format, which you’ve stored in some safe place
|
|
Here’s the seed itself in `mmhex` format, which you’ve stored in some safe place
|
|
(on paper in a safe-deposit box, for example):
|
|
(on paper in a safe-deposit box, for example):
|
|
|
|
|
|
- afc3fe 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e
|
|
|
|
|
|
+```text
|
|
|
|
+afc3fe 456d 7f5f 1c4b fe3b c916 b875 60ae 6a3e
|
|
|
|
+```
|
|
|
|
|
|
Now your task is to generate keys for the addresses so you can spend your coins.
|
|
Now your task is to generate keys for the addresses so you can spend your coins.
|
|
This task is divided into two parts:
|
|
This task is divided into two parts:
|
|
@@ -73,16 +79,18 @@ into Bitcoin Core or some other wallet.
|
|
We’ll solve this task using standard command-line utilities available on any
|
|
We’ll solve this task using standard command-line utilities available on any
|
|
Linux or other Unix-like system.
|
|
Linux or other Unix-like system.
|
|
|
|
|
|
-#### <a name='a_ss'>Convert the seed to binary (legacy uncompressed addresses)</a>
|
|
|
|
|
|
+#### <a id="a_ss">Convert the seed to binary (legacy uncompressed addresses)</a>
|
|
|
|
|
|
For the legacy addresses, we begin by converting the seed to binary form and
|
|
For the legacy addresses, we begin by converting the seed to binary form and
|
|
storing it in a file. For that we use `xxd`, a handy tool for converting binary
|
|
storing it in a file. For that we use `xxd`, a handy tool for converting binary
|
|
to hex and vice versa. Don’t forget to omit the checksum from the seed and
|
|
to hex and vice versa. Don’t forget to omit the checksum from the seed and
|
|
remove the spaces:
|
|
remove the spaces:
|
|
|
|
|
|
- $ echo 456d7f5f1c4bfe3bc916b87560ae6a3e | xxd -r -p > myseed.bin
|
|
|
|
|
|
+```bash
|
|
|
|
+$ echo 456d7f5f1c4bfe3bc916b87560ae6a3e | xxd -r -p > myseed.bin
|
|
|
|
+```
|
|
|
|
|
|
-#### <a name='a_cs'>Scramble the seed and save to binary (non-legacy and altcoin addresses and passwords)</a>
|
|
|
|
|
|
+#### <a id="a_cs">Scramble the seed and save to binary (non-legacy and altcoin addresses and passwords)</a>
|
|
|
|
|
|
Other address types and passwords are generated by first “scrambling” the
|
|
Other address types and passwords are generated by first “scrambling” the
|
|
seed with a unique identifier, or “scramble string”, using the HMAC-SHA256
|
|
seed with a unique identifier, or “scramble string”, using the HMAC-SHA256
|
|
@@ -126,60 +134,79 @@ Once we’ve determined the correct string, we scramble our seed with it as
|
|
follows using the `openssl` utility available by default on any Unix-based
|
|
follows using the `openssl` utility available by default on any Unix-based
|
|
system:
|
|
system:
|
|
|
|
|
|
- # E.g. for LTC Segwit addresses:
|
|
|
|
- $ scramble_str='ltc:segwit'
|
|
|
|
|
|
+```bash
|
|
|
|
+# e.g. for LTC Segwit addresses:
|
|
|
|
+$ scramble_str='ltc:segwit'
|
|
|
|
|
|
- # E.g. for default-format passwords for Alice’s email account at fubar.io:
|
|
|
|
- $ scramble_str='b58:20:alice@fubar.io'
|
|
|
|
|
|
+# e.g. for default-format passwords for Alice’s email account at fubar.io:
|
|
|
|
+$ scramble_str='b58:20:alice@fubar.io'
|
|
|
|
|
|
- $ echo -n "$scramble_str" | openssl dgst -r -sha256 -mac hmac -macopt hexkey:456d7f5f1c4bfe3bc916b87560ae6a3e | xxd -r -p > scrambled-round0.bin
|
|
|
|
|
|
+$ echo -n "$scramble_str" | openssl dgst -r -sha256 -mac hmac -macopt hexkey:456d7f5f1c4bfe3bc916b87560ae6a3e | xxd -r -p > scrambled-round0.bin
|
|
|
|
+```
|
|
|
|
|
|
Now add the ten rounds of sha256:
|
|
Now add the ten rounds of sha256:
|
|
|
|
|
|
- $ for i in 0 1 2 3 4 5 6 7 8 9; do
|
|
|
|
- openssl dgst -sha256 -binary scrambled-round${i}.bin > scrambled-round$((i+1)).bin
|
|
|
|
- done
|
|
|
|
- $ mv scrambled-round10.bin myseed.bin
|
|
|
|
|
|
+```bash
|
|
|
|
+$ for i in 0 1 2 3 4 5 6 7 8 9; do
|
|
|
|
+ openssl dgst -sha256 -binary scrambled-round${i}.bin > scrambled-round$((i+1)).bin
|
|
|
|
+ done
|
|
|
|
|
|
-#### <a name='a_gk'>Generating the keys</a>
|
|
|
|
|
|
+$ mv scrambled-round10.bin myseed.bin
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+#### <a id="a_gk">Generating the keys</a>
|
|
|
|
|
|
The MMGen key-generating algorithm uses a chain of SHA-512 hashes with double
|
|
The MMGen key-generating algorithm uses a chain of SHA-512 hashes with double
|
|
SHA-256 branches to generate the keys from which each address is derived. To
|
|
SHA-256 branches to generate the keys from which each address is derived. To
|
|
obtain the chain’s first link, we make a single SHA-512 hash of the seed and
|
|
obtain the chain’s first link, we make a single SHA-512 hash of the seed and
|
|
save it in binary form:
|
|
save it in binary form:
|
|
|
|
|
|
- $ sha512sum myseed.bin | xxd -r -p > link1.bin
|
|
|
|
|
|
+```bash
|
|
|
|
+$ sha512sum myseed.bin | xxd -r -p > link1.bin
|
|
|
|
+```
|
|
|
|
|
|
A double SHA-256 hash of the first link gives us the key of our first address:
|
|
A double SHA-256 hash of the first link gives us the key of our first address:
|
|
|
|
|
|
- $ sha256sum link1.bin | xxd -r -p | sha256sum
|
|
|
|
- 05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
|
|
|
|
|
+```bash
|
|
|
|
+$ sha256sum link1.bin | xxd -r -p | sha256sum
|
|
|
|
+05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+Or, for the Segwit example:
|
|
|
|
+
|
|
|
|
+```text
|
|
|
|
+b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0
|
|
|
|
+```
|
|
|
|
|
|
- # or, for the Segwit example:
|
|
|
|
- b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0
|
|
|
|
|
|
+Or, for the password example:
|
|
|
|
|
|
- # or, for the password example:
|
|
|
|
- bd60b8ba034bbb40498667ee600bc0cc0b99eb19164e8d412a48f16da4e00d6b
|
|
|
|
|
|
+```text
|
|
|
|
+bd60b8ba034bbb40498667ee600bc0cc0b99eb19164e8d412a48f16da4e00d6b
|
|
|
|
+```
|
|
|
|
|
|
-#### <a name='a_cr'>Checking the result (optional, address example)</a>
|
|
|
|
|
|
+#### <a id="a_cr">Checking the result (optional, address example)</a>
|
|
|
|
|
|
With `mmgen-tool`, we can easily generate the WIF key and address from this
|
|
With `mmgen-tool`, we can easily generate the WIF key and address from this
|
|
hexadecimal key and see that it’s correct:
|
|
hexadecimal key and see that it’s correct:
|
|
|
|
|
|
- $ mmgen-tool hex2wif 05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
|
|
|
- 5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi
|
|
|
|
|
|
+```bash
|
|
|
|
+$ mmgen-tool hex2wif 05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
|
|
|
+5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi
|
|
|
|
|
|
- $ mmgen-tool wif2addr 5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi
|
|
|
|
- 1JVi3qcNcjMM7cTR7y9ihKUG1yDLpKRJfL # matches FE3C6545:L:1 above
|
|
|
|
|
|
+$ mmgen-tool wif2addr 5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi
|
|
|
|
+1JVi3qcNcjMM7cTR7y9ihKUG1yDLpKRJfL # matches FE3C6545:L:1 above
|
|
|
|
+```
|
|
|
|
|
|
Or, for the Segwit example:
|
|
Or, for the Segwit example:
|
|
|
|
|
|
- $ mmgen-tool hex2wif b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0 compressed=1
|
|
|
|
- L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F
|
|
|
|
|
|
+```bash
|
|
|
|
+$ mmgen-tool hex2wif b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0 compressed=1
|
|
|
|
+L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F
|
|
|
|
|
|
- # for a compressed (`C`) address, leave out the `segwit=1` argument
|
|
|
|
- $ mmgen-tool wif2addr L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F segwit=1
|
|
|
|
- 3LpkKqtGkcCukRrgEFWyCajSApioiEWeTw # matches FE3C6545:S:1 above
|
|
|
|
|
|
+# for a compressed (`C`) address, leave out the `segwit=1` argument
|
|
|
|
+$ mmgen-tool wif2addr L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F segwit=1
|
|
|
|
+3LpkKqtGkcCukRrgEFWyCajSApioiEWeTw # matches FE3C6545:S:1 above
|
|
|
|
+```
|
|
|
|
|
|
But since we’re trying to do this without the MMGen software, we need to find
|
|
But since we’re trying to do this without the MMGen software, we need to find
|
|
some other way to do the hex-to-WIF conversion. We could use one of many
|
|
some other way to do the hex-to-WIF conversion. We could use one of many
|
|
@@ -192,78 +219,103 @@ passwords). To get the next key, we generate the next link in the chain from
|
|
the first link and take its double SHA-256 hash, just as we did for the first
|
|
the first link and take its double SHA-256 hash, just as we did for the first
|
|
one:
|
|
one:
|
|
|
|
|
|
- $ sha512sum link1.bin | xxd -r -p > link2.bin
|
|
|
|
- $ sha256sum link2.bin | xxd -r -p | sha256sum
|
|
|
|
- 5db8fe3c8b52ccc98deab5afae780b6fbe56629e7ee1c6ed826fc2d6a81fb144 # uncompressed example
|
|
|
|
- 42f1b998f0f9b7b27b5d0b92ffa8c1c6b96d7202789c41b6e6a6a402e318a04d # Segwit example
|
|
|
|
- 9b59cec2e5d4f2a74f0d4eb2400efcf854f5a893bef0e9bf1ee83f72ca1118c3 # password example
|
|
|
|
|
|
+```bash
|
|
|
|
+$ sha512sum link1.bin | xxd -r -p > link2.bin
|
|
|
|
+
|
|
|
|
+$ sha256sum link2.bin | xxd -r -p | sha256sum
|
|
|
|
+5db8fe3c8b52ccc98deab5afae780b6fbe56629e7ee1c6ed826fc2d6a81fb144 # uncompressed example
|
|
|
|
+42f1b998f0f9b7b27b5d0b92ffa8c1c6b96d7202789c41b6e6a6a402e318a04d # Segwit example
|
|
|
|
+9b59cec2e5d4f2a74f0d4eb2400efcf854f5a893bef0e9bf1ee83f72ca1118c3 # password example
|
|
|
|
+```
|
|
|
|
|
|
And so on and so forth, until we’ve generated all the keys we need: three, in our case.
|
|
And so on and so forth, until we’ve generated all the keys we need: three, in our case.
|
|
|
|
|
|
If we’re generating keys for Ethereum and Monero, our work is done: the raw
|
|
If we’re generating keys for Ethereum and Monero, our work is done: the raw
|
|
hexadecimal keys are all we need. Otherwise, read on.
|
|
hexadecimal keys are all we need. Otherwise, read on.
|
|
|
|
|
|
-#### <a name='a_hpw'>Converting the hex value to a password (password example)</a>
|
|
|
|
|
|
+#### <a id="a_hpw">Converting the hex value to a password (password example)</a>
|
|
|
|
|
|
If it’s passwords we’re generating, we must now convert our hex key to the
|
|
If it’s passwords we’re generating, we must now convert our hex key to the
|
|
desired password format, base58 in our case. For this we can use the homemade
|
|
desired password format, base58 in our case. For this we can use the homemade
|
|
`hex2b58.py` [Base-conversion utility](#a_bcu) described below:
|
|
`hex2b58.py` [Base-conversion utility](#a_bcu) described below:
|
|
|
|
|
|
- # bd60b8... is the double sha256 of our link1.bin from above
|
|
|
|
- $ ./hex2b58.py bd60b8ba034bbb40498667ee600bc0cc0b99eb19164e8d412a48f16da4e00d6b
|
|
|
|
- DkFbZk2fDKQ7C55ASHQjhwcCdTsCiRq4ZLMMD5WQVAvv
|
|
|
|
|
|
+```bash
|
|
|
|
+# bd60b8... is the double sha256 of our link1.bin from above
|
|
|
|
+$ ./hex2b58.py bd60b8ba034bbb40498667ee600bc0cc0b99eb19164e8d412a48f16da4e00d6b
|
|
|
|
+DkFbZk2fDKQ7C55ASHQjhwcCdTsCiRq4ZLMMD5WQVAvv
|
|
|
|
+```
|
|
|
|
|
|
The password is just the last 20 characters of the output:
|
|
The password is just the last 20 characters of the output:
|
|
|
|
|
|
- dTsCiRq4ZLMMD5WQVAvv
|
|
|
|
|
|
+```text
|
|
|
|
+dTsCiRq4ZLMMD5WQVAvv
|
|
|
|
+```
|
|
|
|
|
|
-#### <a name='a_hw'>Hex to WIF by hand (address example)</a>
|
|
|
|
|
|
+#### <a id="a_hw">Hex to WIF by hand (address example)</a>
|
|
|
|
|
|
Since we’ve chosen to convert our hex keys to WIF format manually, we have a bit
|
|
Since we’ve chosen to convert our hex keys to WIF format manually, we have a bit
|
|
of work ahead of us. Let’s begin with our just-generated key #1 from seed
|
|
of work ahead of us. Let’s begin with our just-generated key #1 from seed
|
|
`FE3C6545`:
|
|
`FE3C6545`:
|
|
|
|
|
|
- 05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36 (uncompressed example)
|
|
|
|
- b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0 (Segwit example)
|
|
|
|
|
|
+```bash
|
|
|
|
+# uncompressed example
|
|
|
|
+05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
|
|
|
+
|
|
|
|
+# Segwit example
|
|
|
|
+b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0
|
|
|
|
+```
|
|
|
|
|
|
WIF format prepends hex `80` to the beginning of the key. If the key is
|
|
WIF format prepends hex `80` to the beginning of the key. If the key is
|
|
associated with a compressed public key, it also appends `01`:
|
|
associated with a compressed public key, it also appends `01`:
|
|
|
|
|
|
- # uncompressed example:
|
|
|
|
- 8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
|
|
|
|
|
+```bash
|
|
|
|
+# uncompressed example:
|
|
|
|
+8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
|
|
|
|
|
- # Segwit example (Segwit uses compressed public keys):
|
|
|
|
- 80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a001
|
|
|
|
|
|
+# Segwit example (Segwit uses compressed public keys):
|
|
|
|
+80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a001
|
|
|
|
+```
|
|
|
|
|
|
The Base58Check format invented by Satoshi for Bitcoin addresses and keys
|
|
The Base58Check format invented by Satoshi for Bitcoin addresses and keys
|
|
contains a checksum, which we now generate by taking the first four bytes (eight
|
|
contains a checksum, which we now generate by taking the first four bytes (eight
|
|
characters) of the double SHA256 of the above result:
|
|
characters) of the double SHA256 of the above result:
|
|
|
|
|
|
|
|
|
|
- # uncompressed example:
|
|
|
|
- $ echo 8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36 | xxd -r -p | sha256sum | xxd -r -p | sha256sum | cut -c 1-8
|
|
|
|
- 7b818629
|
|
|
|
|
|
+```bash
|
|
|
|
+# uncompressed example:
|
|
|
|
+$ echo 8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36 | xxd -r -p | sha256sum | xxd -r -p | sha256sum | cut -c 1-8
|
|
|
|
+7b818629
|
|
|
|
|
|
- # Segwit example:
|
|
|
|
- $ echo 80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a001 | xxd -r -p | sha256sum | xxd -r -p | sha256sum | cut -c 1-8
|
|
|
|
- 89bba812
|
|
|
|
|
|
+# Segwit example:
|
|
|
|
+$ echo 80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a001 | xxd -r -p | sha256sum | xxd -r -p | sha256sum | cut -c 1-8
|
|
|
|
+89bba812
|
|
|
|
+```
|
|
|
|
|
|
The checksum gets appended to the end, giving us the following final result:
|
|
The checksum gets appended to the end, giving us the following final result:
|
|
|
|
|
|
- 8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629 (uncompressed example)
|
|
|
|
- 80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812 (Segwit example)
|
|
|
|
|
|
+```bash
|
|
|
|
+# uncompressed example
|
|
|
|
+8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629
|
|
|
|
+
|
|
|
|
+# Segwit example
|
|
|
|
+80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812
|
|
|
|
+```
|
|
|
|
|
|
The last step is to convert all this into Base 58. Satoshi created Base-58
|
|
The last step is to convert all this into Base 58. Satoshi created Base-58
|
|
encoding for convenient and error-free writing down and dictating of Bitcoin
|
|
encoding for convenient and error-free writing down and dictating of Bitcoin
|
|
keys and addresses. He began with a Base-62 alphabet consisting of the ten
|
|
keys and addresses. He began with a Base-62 alphabet consisting of the ten
|
|
digits plus the upper and lower case Latin letters (10 + 26 + 26 = 62):
|
|
digits plus the upper and lower case Latin letters (10 + 26 + 26 = 62):
|
|
|
|
|
|
- 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijlkmnopqrstuvwxyz
|
|
|
|
|
|
+```text
|
|
|
|
+0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijlkmnopqrstuvwxyz
|
|
|
|
+```
|
|
|
|
|
|
Since ‘0’ (zero) is easily confused with capital ‘O’ visually, and capital ‘I’
|
|
Since ‘0’ (zero) is easily confused with capital ‘O’ visually, and capital ‘I’
|
|
with lowercase ‘l’, he dropped those characters, leaving the following 58:
|
|
with lowercase ‘l’, he dropped those characters, leaving the following 58:
|
|
|
|
|
|
- 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
|
|
|
|
|
|
+```text
|
|
|
|
+123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz
|
|
|
|
+```
|
|
|
|
|
|
With ‘0’ gone, ‘1’ now represents decimal zero, ‘2’ represents decimal one, and
|
|
With ‘0’ gone, ‘1’ now represents decimal zero, ‘2’ represents decimal one, and
|
|
so forth all the way up to ‘z’, representing decimal fifty-seven.
|
|
so forth all the way up to ‘z’, representing decimal fifty-seven.
|
|
@@ -272,19 +324,21 @@ Now all that remains is to convert our hexadecimal key to decimal and then Base
|
|
58 using this alphabet. This can be done in just four lines of code you can try
|
|
58 using this alphabet. This can be done in just four lines of code you can try
|
|
out at the Python prompt:
|
|
out at the Python prompt:
|
|
|
|
|
|
- # uncompressed example:
|
|
|
|
- $ python3
|
|
|
|
- >>> b58a = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
|
|
- >>> num = int('8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629',16)
|
|
|
|
- >>> result = [b58a[num // 58**e % 58] for e in range(60)]
|
|
|
|
- >>> print(''.join(reversed(result)).lstrip('1'))
|
|
|
|
- 5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi # matches key for FE3C6545:L:1 above
|
|
|
|
-
|
|
|
|
- # Segwit example has the following differences:
|
|
|
|
- ...
|
|
|
|
- >>> num = int('80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812',16)
|
|
|
|
- ...
|
|
|
|
- L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F # matches key for FE3C6545:S:1 above
|
|
|
|
|
|
+```python
|
|
|
|
+# uncompressed example:
|
|
|
|
+$ python3
|
|
|
|
+>>> b58a = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
|
|
+>>> num = int('8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629',16)
|
|
|
|
+>>> result = [b58a[num // 58**e % 58] for e in range(60)]
|
|
|
|
+>>> print(''.join(reversed(result)).lstrip('1'))
|
|
|
|
+5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi # matches key for FE3C6545:L:1 above
|
|
|
|
+
|
|
|
|
+# Segwit example has the following differences:
|
|
|
|
+...
|
|
|
|
+>>> num = int('80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812',16)
|
|
|
|
+...
|
|
|
|
+L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F # matches key for FE3C6545:S:1 above
|
|
|
|
+```
|
|
|
|
|
|
Explanation: the variable `b58a` holds the Base 58 alphabet; `num` holds the key
|
|
Explanation: the variable `b58a` holds the Base 58 alphabet; `num` holds the key
|
|
in decimal, converted from hexadecimal by Python’s `int()` function; the third
|
|
in decimal, converted from hexadecimal by Python’s `int()` function; the third
|
|
@@ -295,40 +349,48 @@ the leading zeroes (‘1’s).
|
|
Programmers unfamiliar with Python might find the following base conversion code
|
|
Programmers unfamiliar with Python might find the following base conversion code
|
|
clearer:
|
|
clearer:
|
|
|
|
|
|
- def numtob58(n):
|
|
|
|
- result = []
|
|
|
|
- while n:
|
|
|
|
- result = result + [b58a[n % 58]] # divide n by 58 and take the remainder
|
|
|
|
- n = n // 58
|
|
|
|
- return result
|
|
|
|
|
|
+```python
|
|
|
|
+def numtob58(n):
|
|
|
|
+ result = []
|
|
|
|
+ while n:
|
|
|
|
+ result = result + [b58a[n % 58]] # divide n by 58 and take the remainder
|
|
|
|
+ n = n // 58
|
|
|
|
+ return result
|
|
|
|
|
|
- result = numtob58(num)
|
|
|
|
|
|
+result = numtob58(num)
|
|
|
|
+```
|
|
|
|
|
|
-#### <a name='a_bcu'>Base-conversion utility</a>
|
|
|
|
|
|
+#### <a id="a_bcu">Base-conversion utility</a>
|
|
|
|
|
|
Adapting our code a bit and putting it in a file gives us have a handy
|
|
Adapting our code a bit and putting it in a file gives us have a handy
|
|
conversion utility we can use for any key:
|
|
conversion utility we can use for any key:
|
|
|
|
|
|
- $ cat hex2b58.py
|
|
|
|
- #!/usr/bin/env python3
|
|
|
|
- import sys
|
|
|
|
- b58a = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
|
|
- num = int(sys.argv[1],16)
|
|
|
|
- result = [b58a[num // 58**e % 58] for e in range(60)]
|
|
|
|
- print(''.join(reversed(result)).lstrip('1'))
|
|
|
|
|
|
+```python
|
|
|
|
+$ cat hex2b58.py
|
|
|
|
+#!/usr/bin/env python3
|
|
|
|
+import sys
|
|
|
|
+b58a = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|
|
|
+num = int(sys.argv[1],16)
|
|
|
|
+result = [b58a[num // 58**e % 58] for e in range(60)]
|
|
|
|
+print(''.join(reversed(result)).lstrip('1'))
|
|
|
|
+```
|
|
|
|
|
|
- $ ./hex2b58.py 8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629
|
|
|
|
- 5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi
|
|
|
|
|
|
+```text
|
|
|
|
+$ ./hex2b58.py 8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629
|
|
|
|
+5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi
|
|
|
|
|
|
- $ ./hex2b58.py 80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812
|
|
|
|
- L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F
|
|
|
|
|
|
+$ ./hex2b58.py 80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812
|
|
|
|
+L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F
|
|
|
|
+```
|
|
|
|
|
|
-#### <a name='a_mh'>Converting an MMGen mnemonic to hexadecimal format</a>
|
|
|
|
|
|
+#### <a id="a_mh">Converting an MMGen mnemonic to hexadecimal format</a>
|
|
|
|
|
|
Our familiar base-10 system uses a series of ten symbols known as digits to
|
|
Our familiar base-10 system uses a series of ten symbols known as digits to
|
|
represent numbers from zero to nine:
|
|
represent numbers from zero to nine:
|
|
|
|
|
|
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
|
|
|
|
|
+```text
|
|
|
|
+0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
|
|
|
+```
|
|
|
|
|
|
If a number has more than one digit, its value is the sum of its digits
|
|
If a number has more than one digit, its value is the sum of its digits
|
|
multiplied by increasing powers of ten, beginning with the rightmost, least
|
|
multiplied by increasing powers of ten, beginning with the rightmost, least
|
|
@@ -336,27 +398,35 @@ significant digit (the “ones column”).
|
|
|
|
|
|
Thus the number 1234, for example, can be represented as follows:
|
|
Thus the number 1234, for example, can be represented as follows:
|
|
|
|
|
|
- 4 x 1 +
|
|
|
|
- 3 x 10 +
|
|
|
|
- 2 x 100 +
|
|
|
|
- 1 x 1000
|
|
|
|
|
|
+```text
|
|
|
|
+4 x 1 +
|
|
|
|
+3 x 10 +
|
|
|
|
+2 x 100 +
|
|
|
|
+1 x 1000
|
|
|
|
+```
|
|
|
|
|
|
Or in exponential notation:
|
|
Or in exponential notation:
|
|
|
|
|
|
- 4 x 10⁰ +
|
|
|
|
- 3 x 10¹ +
|
|
|
|
- 2 x 10² +
|
|
|
|
- 1 x 10³
|
|
|
|
|
|
+```text
|
|
|
|
+4 x 10⁰ +
|
|
|
|
+3 x 10¹ +
|
|
|
|
+2 x 10² +
|
|
|
|
+1 x 10³
|
|
|
|
+```
|
|
|
|
|
|
An MMGen seed mnemonic is a number too, only the “digits” it’s comprised of come
|
|
An MMGen seed mnemonic is a number too, only the “digits” it’s comprised of come
|
|
from an alphabetically sorted series of 1626 words, the [Electrum wordlist][03],
|
|
from an alphabetically sorted series of 1626 words, the [Electrum wordlist][03],
|
|
which begins like this:
|
|
which begins like this:
|
|
|
|
|
|
- able (0), about (1), above (2), abuse (3), accept (4) ...
|
|
|
|
|
|
+```text
|
|
|
|
+able (0), about (1), above (2), abuse (3), accept (4) ...
|
|
|
|
+```
|
|
|
|
|
|
and ends like this:
|
|
and ends like this:
|
|
|
|
|
|
- yet (1621), young (1622), yours (1623), yourself (1624), youth (1625)
|
|
|
|
|
|
+```text
|
|
|
|
+yet (1621), young (1622), yours (1623), yourself (1624), youth (1625)
|
|
|
|
+```
|
|
|
|
|
|
(Type `mmgen-tool mn_printlist enum=true` to see the full enumerated list)
|
|
(Type `mmgen-tool mn_printlist enum=true` to see the full enumerated list)
|
|
|
|
|
|
@@ -365,60 +435,70 @@ just like the ten digits of our familiar base-10 system.
|
|
|
|
|
|
Here’s the mnemonic of our seed `FE3C6545`:
|
|
Here’s the mnemonic of our seed `FE3C6545`:
|
|
|
|
|
|
- dude foot desperate tie stood themselves trip descend cease suicide apple busy
|
|
|
|
|
|
+```text
|
|
|
|
+dude foot desperate tie stood themselves trip descend cease suicide apple busy
|
|
|
|
+```
|
|
|
|
|
|
To decode it, we begin by listing its words, from least to most significant,
|
|
To decode it, we begin by listing its words, from least to most significant,
|
|
along with the value of each word corresponding to its position in the wordlist:
|
|
along with the value of each word corresponding to its position in the wordlist:
|
|
|
|
|
|
- busy - 200
|
|
|
|
- apple - 59
|
|
|
|
- suicide - 1384
|
|
|
|
- cease - 221
|
|
|
|
- descend - 379
|
|
|
|
- trip - 1493
|
|
|
|
- themselves - 1433
|
|
|
|
- stood - 1348
|
|
|
|
- tie - 1459
|
|
|
|
- desperate - 386
|
|
|
|
- foot - 562
|
|
|
|
- dude - 439
|
|
|
|
|
|
+```text
|
|
|
|
+busy - 200
|
|
|
|
+apple - 59
|
|
|
|
+suicide - 1384
|
|
|
|
+cease - 221
|
|
|
|
+descend - 379
|
|
|
|
+trip - 1493
|
|
|
|
+themselves - 1433
|
|
|
|
+stood - 1348
|
|
|
|
+tie - 1459
|
|
|
|
+desperate - 386
|
|
|
|
+foot - 562
|
|
|
|
+dude - 439
|
|
|
|
+```
|
|
|
|
|
|
All that remains is to multiply the values by increasing powers of the base and
|
|
All that remains is to multiply the values by increasing powers of the base and
|
|
sum the results, just as we did in our ‘1234’ example above:
|
|
sum the results, just as we did in our ‘1234’ example above:
|
|
|
|
|
|
- 200 x 1626⁰ +
|
|
|
|
- 59 x 1626¹ +
|
|
|
|
- 1384 x 1626² +
|
|
|
|
- 221 x 1626³ +
|
|
|
|
- 379 x 1626⁴ +
|
|
|
|
- 1493 x 1626⁵ +
|
|
|
|
- 1433 x 1626⁶ +
|
|
|
|
- 1348 x 1626⁷ +
|
|
|
|
- 1459 x 1626⁸ +
|
|
|
|
- 386 x 1626⁹ +
|
|
|
|
- 562 x 1626¹⁰ +
|
|
|
|
- 439 x 1626¹¹
|
|
|
|
|
|
+```text
|
|
|
|
+200 x 1626⁰ +
|
|
|
|
+59 x 1626¹ +
|
|
|
|
+1384 x 1626² +
|
|
|
|
+221 x 1626³ +
|
|
|
|
+379 x 1626⁴ +
|
|
|
|
+1493 x 1626⁵ +
|
|
|
|
+1433 x 1626⁶ +
|
|
|
|
+1348 x 1626⁷ +
|
|
|
|
+1459 x 1626⁸ +
|
|
|
|
+386 x 1626⁹ +
|
|
|
|
+562 x 1626¹⁰ +
|
|
|
|
+439 x 1626¹¹
|
|
|
|
+```
|
|
|
|
|
|
While we could theoretically do the math with a calculator, a few lines of
|
|
While we could theoretically do the math with a calculator, a few lines of
|
|
Python will make our work much easier:
|
|
Python will make our work much easier:
|
|
|
|
|
|
- $ python3
|
|
|
|
- >>> sum = exp = 0
|
|
|
|
- >>> for word in (200,59,1384,221,379,1493,1433,1348,1459,386,562,439):
|
|
|
|
- >>> sum += word * 1626 ** exp
|
|
|
|
- >>> exp += 1
|
|
|
|
- >>> print(hex(sum))
|
|
|
|
- 0x456d7f5f1c4bfe3bc916b87560ae6a3e # the result in hexadecimal: matches our original hex seed above
|
|
|
|
|
|
+```python
|
|
|
|
+$ python3
|
|
|
|
+>>> sum = exp = 0
|
|
|
|
+>>> for word in (200,59,1384,221,379,1493,1433,1348,1459,386,562,439):
|
|
|
|
+>>> sum += word * 1626 ** exp
|
|
|
|
+>>> exp += 1
|
|
|
|
+>>> print(hex(sum))
|
|
|
|
+0x456d7f5f1c4bfe3bc916b87560ae6a3e # the result in hexadecimal: matches our original hex seed above
|
|
|
|
+```
|
|
|
|
|
|
In case you’re wondering why 1626 was chosen as the base: 1626 is just large
|
|
In case you’re wondering why 1626 was chosen as the base: 1626 is just large
|
|
enough to allow a 128-bit seed to be represented by twelve words. This can also
|
|
enough to allow a 128-bit seed to be represented by twelve words. This can also
|
|
be demonstrated at the Python prompt:
|
|
be demonstrated at the Python prompt:
|
|
|
|
|
|
- $ python3
|
|
|
|
- >>> 1626**12 >= 2**128
|
|
|
|
- True
|
|
|
|
- >>> 1625**12 >= 2**128
|
|
|
|
- False
|
|
|
|
|
|
+```python
|
|
|
|
+$ python3
|
|
|
|
+>>> 1626**12 >= 2**128
|
|
|
|
+True
|
|
|
|
+>>> 1625**12 >= 2**128
|
|
|
|
+False
|
|
|
|
+```
|
|
|
|
|
|
[01]: https://github.com/casascius/Bitcoin-Address-Utility
|
|
[01]: https://github.com/casascius/Bitcoin-Address-Utility
|
|
[02]: https://github.com/matja/bitcoin-tool
|
|
[02]: https://github.com/matja/bitcoin-tool
|