modified: Recovering-Your-Keys-Without-the-MMGen-Software.md
parent
70ff9466dd
commit
58951ac8e9
1 changed files with 138 additions and 135 deletions
|
|
@ -79,9 +79,9 @@ 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
|
||||
to hex and vice versa. Don’t forget to omit the checksum from the seed and
|
||||
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>
|
||||
|
||||
Other address types and passwords are generated by first “scrambling” the
|
||||
|
|
@ -125,62 +125,62 @@ string, e.g. `alice@fubar.io`, all separated by colons:
|
|||
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
|
||||
system:
|
||||
```bash
|
||||
# E.g. for LTC Segwit addresses:
|
||||
$ scramble_str='ltc:segwit'
|
||||
|
||||
# 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'
|
||||
|
||||
$ echo -n "$scramble_str" | openssl dgst -r -sha256 -mac hmac -macopt hexkey:456d7f5f1c4bfe3bc916b87560ae6a3e | xxd -r -p > scrambled-round0.bin
|
||||
# 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
|
||||
```
|
||||
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
|
||||
$ mv scrambled-round10.bin myseed.bin
|
||||
```
|
||||
#### <a name='a_gk'>Generating the keys</a>
|
||||
|
||||
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
|
||||
obtain the chain’s first link, we make a single SHA-512 hash of the seed and
|
||||
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:
|
||||
```bash
|
||||
$ sha256sum link1.bin | xxd -r -p | sha256sum
|
||||
05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
||||
|
||||
$ sha256sum link1.bin | xxd -r -p | sha256sum
|
||||
05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
||||
|
||||
# or, for the Segwit example:
|
||||
b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0
|
||||
|
||||
# or, for the password example:
|
||||
bd60b8ba034bbb40498667ee600bc0cc0b99eb19164e8d412a48f16da4e00d6b
|
||||
# or, for the Segwit example:
|
||||
b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0
|
||||
|
||||
# or, for the password example:
|
||||
bd60b8ba034bbb40498667ee600bc0cc0b99eb19164e8d412a48f16da4e00d6b
|
||||
```
|
||||
#### <a name='a_cr'>Checking the result (optional, address example)</a>
|
||||
|
||||
With `mmgen-tool`, we can easily generate the WIF key and address from this
|
||||
hexadecimal key and see that it’s correct:
|
||||
```bash
|
||||
$ mmgen-tool hex2wif 05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
||||
5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi
|
||||
|
||||
$ 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:
|
||||
```bash
|
||||
$ mmgen-tool hex2wif b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0 compressed=1
|
||||
L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F
|
||||
|
||||
$ 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
|
||||
some other way to do the hex-to-WIF conversion. We could use one of many
|
||||
key-manipulation tools available on the Internet, such as [this one][01], or
|
||||
|
|
@ -191,13 +191,13 @@ Meanwhile, let’s finish generating hex keys for the rest of our addresses (or
|
|||
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
|
||||
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.
|
||||
|
||||
If we’re generating keys for Ethereum and Monero, our work is done: the raw
|
||||
|
|
@ -208,51 +208,50 @@ hexadecimal keys are all we need. Otherwise, read on.
|
|||
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
|
||||
`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:
|
||||
|
||||
dTsCiRq4ZLMMD5WQVAvv
|
||||
|
||||
```bash
|
||||
dTsCiRq4ZLMMD5WQVAvv
|
||||
```
|
||||
#### <a name='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
|
||||
of work ahead of us. Let’s begin with our just-generated key #1 from seed
|
||||
`FE3C6545`:
|
||||
|
||||
05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36 (uncompressed example)
|
||||
b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0 (Segwit example)
|
||||
|
||||
```bash
|
||||
05d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36 # uncompressed example
|
||||
b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a0 # Segwit example
|
||||
```
|
||||
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`:
|
||||
```bash
|
||||
# uncompressed example:
|
||||
8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36
|
||||
|
||||
# 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
|
||||
contains a checksum, which we now generate by taking the first four bytes (eight
|
||||
characters) of the double SHA256 of the above result:
|
||||
```bash
|
||||
# uncompressed example:
|
||||
$ echo 8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e36 | xxd -r -p | sha256sum | xxd -r -p | sha256sum | cut -c 1-8
|
||||
7b818629
|
||||
|
||||
|
||||
# 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:
|
||||
|
||||
8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629 (uncompressed example)
|
||||
80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812 (Segwit example)
|
||||
|
||||
```bash
|
||||
8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629 # uncompressed example
|
||||
80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812 # Segwit example
|
||||
```
|
||||
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
|
||||
keys and addresses. He began with a Base-62 alphabet consisting of the ten
|
||||
|
|
@ -271,21 +270,22 @@ so forth all the way up to ‘z’, representing decimal fifty-seven.
|
|||
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
|
||||
out at the Python prompt:
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# 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
|
||||
# uncompressed example:
|
||||
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
|
||||
in decimal, converted from hexidecimal by Python’s `int()` function; the third
|
||||
line does the base-58 conversion; and the last line formats the result by
|
||||
|
|
@ -294,35 +294,37 @@ the leading zeroes (‘1’s).
|
|||
|
||||
Programmers unfamiliar with Python might find the following base conversion code
|
||||
clearer:
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
def numtob58(n):
|
||||
result = []
|
||||
while n:
|
||||
result = result + [b58a[n % 58]] # divide n by 58 and take the remainder
|
||||
n = n // 58
|
||||
return result
|
||||
|
||||
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>
|
||||
|
||||
Adapting our code a bit and putting it in a file gives us have a handy
|
||||
conversion utility we can use for any key:
|
||||
```python
|
||||
#!/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'))
|
||||
```
|
||||
Save this in the file `hex2b58.py` and invoke it as follows:
|
||||
```bash
|
||||
$ ./hex2b58.py 8005d7219524b983290138a60ada101370007f59a625c43a46f0f8d92950955e367b818629
|
||||
5HrrmMdQbELyW7iCns5kvSbN9GCPTqEfG7iP1PZiYk49yDDivTi
|
||||
|
||||
$ 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
|
||||
|
||||
$ ./hex2b58.py 80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812
|
||||
L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F
|
||||
|
||||
$ ./hex2b58.py 80b8e58ded53e9ba5a9f4e279a956c061a7da5487bde6a95f1ede0722d287881a00189bba812
|
||||
L3R8Fn21PsY3PWgT8BMggFwXswA2EZntwEGFS5mfDJpSiLq29a9F
|
||||
```
|
||||
#### <a name='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
|
||||
|
|
@ -401,24 +403,25 @@ sum the results, just as we did in our ‘1234’ example above:
|
|||
|
||||
While we could theoretically do the math with a calculator, a few lines of
|
||||
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
|
||||
#!/usr/bin/env 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
|
||||
enough to allow a 128-bit seed to be represented by twelve words. This can also
|
||||
be demonstrated at the Python prompt:
|
||||
|
||||
$ python3
|
||||
>>> 1626**12 >= 2**128
|
||||
True
|
||||
>>> 1625**12 >= 2**128
|
||||
False
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
>>> 1626**12 >= 2**128
|
||||
True
|
||||
>>> 1625**12 >= 2**128
|
||||
False
|
||||
```
|
||||
|
||||
[01]: https://github.com/casascius/Bitcoin-Address-Utility
|
||||
[02]: https://github.com/matja/bitcoin-tool
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue