edgecase
Author: StJohn Piano
Published: 2020-07-12
Datafeed Article 152
This article has been digitally signed by Edgecase Datafeed.
This article has been digitally signed by its author.
1887 words - 647 lines - 17 pages




GOAL



Develop tools that allow a Bitcoin private key to be stored with an arithmetic checksum tree. This makes private key storage more robust, because transcription errors can be located and fixed.




CONTENTS



- Goal
- Contents
- Brief Summary
- Summary
- Downloadable Assets
- How To Use The Tools
- Project Log




BRIEF SUMMARY



I developed two tools:
- private_key_to_storage_key.py
- storage_key_to_private_key.py
that allow a user to a) add an arithmetic checksum tree to a Bitcoin private key and b) validate the checksum tree and remove it.




SUMMARY



I developed two tools:
- private_key_to_storage_key.py
- storage_key_to_private_key.py
that allow a user to a) add an arithmetic checksum tree to a Bitcoin private key and b) validate the checksum tree and remove it.

In the previous project An investigation into recovering from transcription errors in Bitcoin private keys, I investigated the use of an arithmetic checksum tree in Bitcoin private key storage, and found that it greatly helped to locate transcription errors and increase key recovery speed.

A Bitcoin private key in several different formats:

1) Normal format:
7972b641101c0ad67b0e401b800a9b6f3225c97fc6b8115042cf66968c2fb2e5


2) Grid pattern:
7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
42cf 6696 8c2f b2e5


3) Grid pattern with an added arithmetic checksum tree:
9
ab31
96ed 0029 cb57 1b50

7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
42cf 6696 8c2f b2e5


I refer to a Bitcoin private key in format (3) as a "storage key".

A Bitcoin private key is 32 bytes. When written in hex characters, as in format (1), it is 64 bytes.

There are 107 bytes in the storage key (not counting the last newline byte). This is a data size increase of ~67%.

The code used in this recipe has been run in Python 2.7.5 on CentOS 7.6 and in Python 2.7.13 on Mac OS X 10.6.8. It should run successfully in Python 2.7.x.








DOWNLOADABLE ASSETS



Assets of this article:

List:

- private_key_to_storage_key.py
- storage_key_to_private_key.py


Asset: A script that generates an arithmetic checksum tree to a Bitcoin private key. It will print out the key in a grid format with the checksum tree added above the key.
private_key_to_storage_key.py [paywalled]

Asset: A script that validates a checksum tree stored with a key, then removes it. It will print out the key without the tree and without any whitespace.
storage_key_to_private_key.py [paywalled]








HOW TO USE THE TOOLS



Help commands:

python private_key_to_storage_key.py --help


python storage_key_to_private_key.py --help



Main commands:

python private_key_to_storage_key.py --privateKeyFilePath privateKey.txt > storageKey.txt


python storage_key_to_private_key.py --storageKeyFilePath storageKey.txt > privateKey.txt


Note: In the main commands, the flag
-f
can be used instead of
--privateKeyFilePath
and
--storageKeyFilePath
.


Example of use:


aineko:work2 stjohnpiano$ ls -1

privateKey.txt
private_key_to_storage_key.py
storage_key_to_private_key.py


aineko:work2 stjohnpiano$ cat privateKey.txt

7972b641101c0ad67b0e401b800a9b6f3225c97fc6b8115042cf66968c2fb2e5


aineko:work2 stjohnpiano$ python private_key_to_storage_key.py --privateKeyFilePath privateKey.txt

9
ab31
96ed 0029 cb57 1b50

7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
42cf 6696 8c2f b2e5


aineko:work2 stjohnpiano$ python private_key_to_storage_key.py --privateKeyFilePath privateKey.txt > storageKey.txt



aineko:work2 stjohnpiano$ cat storageKey.txt

9
ab31
96ed 0029 cb57 1b50

7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
42cf 6696 8c2f b2e5


aineko:work2 stjohnpiano$ python storage_key_to_private_key.py --storageKeyFilePath storageKey.txt

7972b641101c0ad67b0e401b800a9b6f3225c97fc6b8115042cf66968c2fb2e5


aineko:work2 stjohnpiano$ python storage_key_to_private_key.py --storageKeyFilePath storageKey.txt > privateKey2.txt



aineko:work2 stjohnpiano$ cat privateKey2.txt

7972b641101c0ad67b0e401b800a9b6f3225c97fc6b8115042cf66968c2fb2e5


aineko:work2 stjohnpiano$ diff privateKey.txt privateKey2.txt




Now make a copy of
storageKey.txt
and add an error. In the checksum tree, in line 3, in group 1, change character 3 to be 'f' instead of 'e'.

aineko:work2 stjohnpiano$ python storage_key_to_private_key.py -f storageKey2.txt


2 errors found in the checksum tree of storage key.

9
ab31
96fd 0029 cb57 1b50

7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
42cf 6696 8c2f b2e5

Error messages:
- Group 1 in the checksum line is '96fd'. In the checksum group of the storage key, the checksum is 'a' (checksum group, character 1). However, the re-generated checksum is 'b'.
- Group 3 in line 1 is '101c'. In the checksum line of the storage key, the checksum is 'f' (checksum line, group 1, character 3). However, the re-generated checksum is 'e'.



The error has been located and reported.




















PROJECT LOG



In the previous project An investigation into recovering from transcription errors in Bitcoin private keys, I investigated the use of an arithmetic checksum tree in Bitcoin private key storage, and found that it greatly helped to locate transcription errors and increase key recovery speed.

In this project, I'll develop tools for generating a checksum tree to be stored alongside a Bitcoin private key. Goals:
- Generate the checksum tree for a private key.
- Extract a private key from the checksum tree storage format.
- Check the integrity of the checksum tree.

First, I'll write write a small command-line tool that can convert a one-line Bitcoin private key to a grid-pattern Bitcoin private key with a checksum tree. Input is a file. Output is text that can be redirected to a file.

I'll call the one-line private key a "private key" and the grid-pattern key with a checksum tree a "storage key".

Input:
7972b641101c0ad67b0e401b800a9b6f3225c97fc6b8115042cf66968c2fb2e5


Output:
9
ab31
96ed 0029 cb57 1b50

7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
42cf 6696 8c2f b2e5


In the output:
- The 3 lines at the top are the checksum tree.
- The 4x4x4 grid at the bottom is the private key.

The corresponding address is:
16ASCUS3s7D4UQh6J9oHGuT19agPvD3PFj



Create a work directory.

Save the one-line private key in a file called "privateKey.txt".




How many bytes should be in a storage key?

Not counting the last newline, there should be 7 newline bytes.

There are 5 lines of 4 groups of 4 characters, with a space between group. For each line, there are a total of 3 spaces. Each line therefore has 4x4 + 3 = 19 bytes. Total bytes in 5 lines: 5 x 19 = 95

There are two remaining lines at the top of the checksum tree, one with 1 character and another with 4 characters. Total: 5 bytes.

Total bytes in the entire storage key:
7 + 95 + 5 = 107




[development occurs here]




[spiano@shovel work]$ python private_key_to_storage_key.py -f privateKey.txt

9
ab31
96ed 0029 cb57 1b50

7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
42cf 6696 8c2f b2e5



This is the expected output.


Store this in a file.


[spiano@shovel work]$ python private_key_to_storage_key.py -f privateKey.txt > storageKey.txt





Next: I'll write a second tool that extracts a private key from the checksum tree storage format. This second tool will also check the integrity of the checksum tree.




[development occurs here]




[spiano@shovel work]$ python storage_key_to_private_key.py -f storageKey.txt

7972b641101c0ad67b0e401b800a9b6f3225c97fc6b8115042cf66968c2fb2e5



This is the expected output.




Let's try adding various errors to the storageKey.




Make a copy of storageKey.txt (save it as storageKey2.txt). Add an error in the private key: In line 1, group 2, change the second character (6) to "b".


[spiano@shovel work]$ python storage_key_to_private_key.py -f storageKey2.txt


1 error found in the checksum tree of storage key.

9
ab31
96ed 0029 cb57 1b50

7972 bb41 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
42cf 6696 8c2f b2e5

Error messages:
- Group 2 in line 1 is 'bb41'. In the checksum line of the storage key, the checksum is '6' (checksum line, group 1, character 2). However, the re-generated checksum is 'b'.





Make a copy of storageKey2.txt (save it as storageKey3.txt). Add an error in the private key: In line 4, group 1, change the first character (4) to "2".


[spiano@shovel work]$ python storage_key_to_private_key.py -f storageKey3.txt


2 errors found in the checksum tree of storage key.

9
ab31
96ed 0029 cb57 1b50

7972 bb41 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
22cf 6696 8c2f b2e5

Error messages:
- Group 2 in line 1 is 'bb41'. In the checksum line of the storage key, the checksum is '6' (checksum line, group 1, character 2). However, the re-generated checksum is 'b'.
- Group 1 in line 4 is '22cf'. In the checksum line of the storage key, the checksum is '1' (checksum line, group 4, character 1). However, the re-generated checksum is 'f'.





Make a copy of storageKey3.txt (save it as storageKey4.txt). Add an error in the checksum group: Change the third character (3) to "8".


[spiano@shovel work]$ python storage_key_to_private_key.py -f storageKey4.txt


4 errors found in the checksum tree of storage key.

9
ab81
96ed 0029 cb57 1b50

7972 bb41 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
22cf 6696 8c2f b2e5

Error messages:
- The checksum group is 'ab81'. The checksum in the storage key is '9' (checksum character at the top of the tree). However, the re-generated checksum is 'e'.
- Group 3 in the checksum line is 'cb57'. In the checksum group of the storage key, the checksum is '8' (checksum group, character 3). However, the re-generated checksum is '3'.
- Group 2 in line 1 is 'bb41'. In the checksum line of the storage key, the checksum is '6' (checksum line, group 1, character 2). However, the re-generated checksum is 'b'.
- Group 1 in line 4 is '22cf'. In the checksum line of the storage key, the checksum is '1' (checksum line, group 4, character 1). However, the re-generated checksum is 'f'.





Make a copy of storageKey4.txt (save it as storageKey5.txt). Add an error in the checksum line: In group 4, change the first character (1) to "7".


[spiano@shovel work]$ python storage_key_to_private_key.py -f storageKey5.txt


5 errors found in the checksum tree of storage key.

9
ab81
96ed 0029 cb57 7b50

7972 bb41 101c 0ad6
7b0e 401b 800a 9b6f
3225 c97f c6b8 1150
22cf 6696 8c2f b2e5

Error messages:
- The checksum group is 'ab81'. The checksum in the storage key is '9' (checksum character at the top of the tree). However, the re-generated checksum is 'e'.
- Group 3 in the checksum line is 'cb57'. In the checksum group of the storage key, the checksum is '8' (checksum group, character 3). However, the re-generated checksum is '3'.
- Group 4 in the checksum line is '7b50'. In the checksum group of the storage key, the checksum is '1' (checksum group, character 4). However, the re-generated checksum is '7'.
- Group 2 in line 1 is 'bb41'. In the checksum line of the storage key, the checksum is '6' (checksum line, group 1, character 2). However, the re-generated checksum is 'b'.
- Group 1 in line 4 is '22cf'. In the checksum line of the storage key, the checksum is '7' (checksum line, group 4, character 1). However, the re-generated checksum is 'f'.





An extra test: In the previous project, I altered the private key slightly (by changing line 3 / group 1 / character 1 from "3" to "8", producing this new private key:

7972b641101c0ad67b0e401b800a9b6f8225c97fc6b8115042cf66968c2fb2e5


I then generated this checksum tree for it:

e
ab81
96ed 0029 1b57 1b50

7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
8225 c97f c6b8 1150
42cf 6696 8c2f b2e5


Save the new private key as privateKey2.txt.


[spiano@shovel work]$ python private_key_to_storage_key.py -f privateKey2.txt

e
ab81
96ed 0029 1b57 1b50

7972 b641 101c 0ad6
7b0e 401b 800a 9b6f
8225 c97f c6b8 1150
42cf 6696 8c2f b2e5





This is the expected output.


[spiano@shovel work]$ python private_key_to_storage_key.py -f privateKey2.txt > storageKey6.txt


[spiano@shovel work]$ python storage_key_to_private_key.py -f storageKey6.txt

7972b641101c0ad67b0e401b800a9b6f8225c97fc6b8115042cf66968c2fb2e5



The output is the new private key, as expected.




That's the end of this project.