edgecase
Author: StJohn Piano
Published: 2020-11-10
Datafeed Article 192
This article has been digitally signed by Edgecase Datafeed.
This article has been digitally signed by its author.
1342 words - 630 lines - 16 pages




GOAL



Develop a tool for validating a standard Bitcoin address. This tool should have a good command-line interface and a test suite.




CONTENTS



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




SUMMARY



I developed:
- a tool for validating a Bitcoin address from a private key.
- an accompanying test suite.

The tool has a useful command-line interface.

Please see the Downloadable Assets section for the tool and the test suite, and the How To Use The Tools section for instructions and examples.










DOWNLOADABLE ASSETS





Assets of this article:

List:

- validate_address.py
- test_validate_address.py


Asset: A tool that validates a Bitcoin address from a private key. Python 2.7.12.
validate_address.py [paywalled]

Asset: A test suite for the validation tool. Python 2.7.12, Pytest 4.6.11.
test_validate_address.py




Assets of other articles:

List:

- bitcoin_functions_2.py
- bjorn_edstrom_ripemd160.py
- ecdsa-0.10.tar.gz
- pypy_sha256.py


Asset: A library of functions for handling standard Bitcoin data types. Python 2.7.
bitcoin_functions_2.py [paywalled]

Asset: An implementation of RIPEMD-160, written by Björn Edström. Python 2.7.
bjorn_edstrom_ripemd160.py [paywalled]

Asset: A Python implementation of ECDSA cryptography, written by Peter Pearson. Python 2.7.
ecdsa-0.10.tar.gz [paywalled]

Asset: A Python implementation of SHA256. Author unknown. Python 2.7.
pypy_sha256.py [paywalled]




Dependency tree:
- test_validate_address.py
-- validate_address.py
--- bitcoin_functions_2.py
---- bjorn_edstrom_ripemd160.py
---- ecdsa-0.10.tar.gz
---- pypy_sha256.py











HOW TO USE THE TOOLS




Help:

python validate_address.py --help




Main:

python validate_address.py -f address.txt


Note: By default, the tool looks for a file named 'address.txt' in its local directory, so in the above command the filepath argument is optional.

python validate_address.py




Test:

pytest -q test_validate_address.py


Note: The test suite file needs to be in the same directory as the tool file.



Examples:

stjohn@judgement:work$ python validate_address.py --addressFilePath address.txt



[same as above, but in shorthand]
stjohn@judgement:work$ python validate_address.py -f address.txt



stjohn@judgement:work$ cat address.txt

1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT


[default address filepath is 'address.txt']
stjohn@judgement:work$ python validate_address.py



stjohn@judgement:work$ python validate_address.py --addressString 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT



stjohn@judgement:work$ python validate_address.py --addressString 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT --debug

INFO [81: validateBitcoinAddress] Address: 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT
DEBUG [89: validateBitcoinAddress] Number of leading '1' characters: 1
DEBUG [92: validateBitcoinAddress] base10 value inside address: 6230568508398901441201089632142185462656304983477985328510
INFO [95: validateBitcoinAddress] Address value in hex: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa39adbd7e
INFO [100: validateBitcoinAddress] Checksum: 39adbd7e
DEBUG [102: validateBitcoinAddress] Address value in hex without checksum: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
DEBUG [105: validateBitcoinAddress] Address value with leading zero bytes added back on: 00fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
DEBUG [108: validateBitcoinAddress] Recalculated checksum: 39adbd7e
DEBUG [115: validateBitcoinAddress] versionByte: 00
INFO [120: validateBitcoinAddress] publicKeyHash: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
INFO [124: validateBitcoinAddress] Address 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT validated. No problems found.


[invalid address - last character has been changed from 'T' to 'S'.]
stjohn@judgement:work$ python validate_address.py --addressString 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbS

Traceback (most recent call last): File "validate_address.py", line 172, in <module> main() File "validate_address.py", line 69, in main validateBitcoinAddress(args.addressString) File "validate_address.py", line 112, in validateBitcoinAddress raise ValueError(msg) ValueError: Recalculated checksum (39adbd7d) does not match original checksum (39adbd7e).



stjohn@judgement:work$ pytest -q test_validate_address.py

.................... [100%]
20 passed in 0.08 seconds





















PROJECT LOG



This project is going to be very similar to Generating a standard Bitcoin address #2.

My working definition of a standard Bitcoin address:
- An uncompressed single-signature Pay-To-Public-Key-Hash (P2PKH) address.

Standard addresses start with the character '1'.

Example standard Bitcoin address (34 characters):
1AGygbyEFYduWkkmZbbvirgS9kuBBMLJCP


Work machine:
- Name: Judgement
- Windows 10
- Windows Subsystem for Linux (WSL): Ubuntu 16.04
- I'm working in the WSL Ubuntu terminal.

My Python version:
stjohn@judgement:work$ python --version

Python 2.7.12


My Pytest version:
stjohn@judgement:work$ pytest --version

This is pytest version 4.6.11, imported from /home/stjohn/.local/lib/python2.7/site-packages/pytest.pyc


Create a project directory, called:
"validating_a_standard_bitcoin_address"

Relevant: In the previous project Generating a standard Bitcoin address, in the Notes / Discoveries section, there is a part named "Algorithm for generating a Bitcoin address".

Relevant: In the previous project Creating and signing a standard raw Bitcoin transaction, in the Notes / Discoveries section, there is a part named "Algorithm for deriving the public key hash from a standard Bitcoin address".

Browse to Edgecase Bitcoin Storage Toolset version 2 and download the following assets:

Dependency tree:
- bitcoin_functions_2.py
-- bjorn_edstrom_ripemd160.py
-- ecdsa-0.10.tar.gz
-- pypy_sha256.py


bitcoin_functions_2.py contains various functions that I'll use in this project.
- One in particular, base58check_to_hex, is very close to what I want for this project.

Create a new directory named "work" in the project directory. Move the downloaded assets into the work directory.

Unpack the zipped tape archive file ecdsa-0.10.tar.gz, by opening a terminal, changing directory to the work directory, and running the following command:
tar -zxvf ecdsa-0.10.tar.gz


This unpacking produced a new directory named "ecdsa-0.10" in the work directory. The directory "ecdsa-0.10" contains a directory named "ecdsa". Copy the "ecdsa" directory into the work directory, using the following command:
cp -r ecdsa-0.10/ecdsa .


Delete ecdsa-0.10 and ecdsa-0.10.tar.gz.

I'll use the data from the previous project Bitcoin address test set when writing tests.

List of things to validate:
- The address begins with a '1' character.
- The public key hash that it contains is 20 characters.
- The double-SHA256 checksum at the end is 4 characters.
- The checksum is correct.




[development work occurs here.]




I've written a tool for validating a bitcoin address, and an accompanying test suite:
- validate_address.py
- test_validate_address.py




Let's record some examples of using
validate_address.py




stjohn@judgement:work$ python validate_address.py -h

usage: validate_address.py [-h] [-f ADDRESSFILEPATH] [-s ADDRESSSTRING] [-l {debug,info,warning,error}] [-d] Derive a Bitcoin address from a Bitcoin private key. optional arguments: -h, --help show this help message and exit -f ADDRESSFILEPATH, --addressFilePath ADDRESSFILEPATH path to file containing the address (default: 'address.txt'). -s ADDRESSSTRING, --addressString ADDRESSSTRING address passed as a direct argument. This overrides --addressFilePath. -l {debug,info,warning,error}, --logLevel {debug,info,warning,error} Choose logging level (default: 'error'). -d, --debug Sets logLevel to 'debug'. This overrides --logLevel. Supplementary Notes: - A standard Bitcoin address is an uncompressed single-signature Pay-To-Public-Key-Hash (P2PKH) address. - Standard addresses start with the character '1'. - Example standard Bitcoin address (34 characters): 1AGygbyEFYduWkkmZbbvirgS9kuBBMLJCP



stjohn@judgement:work$ python validate_address.py --addressString 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT



stjohn@judgement:work$ python validate_address.py --addressString 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT --debug

INFO [81: validateBitcoinAddress] Address: 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT
DEBUG [89: validateBitcoinAddress] Number of leading '1' characters: 1
DEBUG [92: validateBitcoinAddress] base10 value inside address: 6230568508398901441201089632142185462656304983477985328510
INFO [95: validateBitcoinAddress] Address value in hex: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa39adbd7e
INFO [100: validateBitcoinAddress] Checksum: 39adbd7e
DEBUG [102: validateBitcoinAddress] Address value in hex without checksum: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
DEBUG [105: validateBitcoinAddress] Address value with leading zero bytes added back on: 00fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
DEBUG [108: validateBitcoinAddress] Recalculated checksum: 39adbd7e
DEBUG [115: validateBitcoinAddress] versionByte: 00
INFO [120: validateBitcoinAddress] publicKeyHash: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
INFO [124: validateBitcoinAddress] Address 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT validated. No problems found.


stjohn@judgement:work$ cat address.txt

1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT


stjohn@judgement:work$ python validate_address.py --addressFilePath address.txt



stjohn@judgement:work$ python validate_address.py -f address.txt



stjohn@judgement:work$ python validate_address.py -f address.txt --logLevel=info

- Address: 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT
- Address value in hex: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa39adbd7e
- Checksum: 39adbd7e
- publicKeyHash: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
- Address 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT validated. No problems found.









And some examples of testing the tool.

stjohn@judgement:work$ pytest test_validate_address.py

============================= test session starts ==============================
platform linux2 -- Python 2.7.12, pytest-4.6.11, py-1.9.0, pluggy-0.13.1
rootdir: /mnt/c/Users/User/Desktop/stuff/articles/validating_a_bitcoin_address/work
collected 20 items

test_validate_address.py .................... [100%]

========================== 20 passed in 0.13 seconds ===========================




stjohn@judgement:work$ pytest test_validate_address.py -q

.................... [100%]
20 passed in 0.08 seconds




stjohn@judgement:work$ pytest test_validate_address.py::test_valid_address

============================= test session starts ==============================
platform linux2 -- Python 2.7.12, pytest-4.6.11, py-1.9.0, pluggy-0.13.1
rootdir: /mnt/c/Users/User/Desktop/stuff/articles/validating_a_bitcoin_address/work
collected 1 item

test_validate_address.py . [100%]

=========================== 1 passed in 0.04 seconds ===========================




stjohn@judgement:work$ pytest -o log_cli=true --log-cli-level=DEBUG --log-format="%(levelname)s [%(lineno)s: %(funcName)s] %(message)s" test_validate_address.py::test_valid_address

============================= test session starts ==============================
platform linux2 -- Python 2.7.12, pytest-4.6.11, py-1.9.0, pluggy-0.13.1
rootdir: /mnt/c/Users/User/Desktop/stuff/articles/validating_a_bitcoin_address/work
collected 1 item

test_validate_address.py::test_valid_address
-------------------------------- live log call ---------------------------------
INFO [81: validateBitcoinAddress] Address: 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT
DEBUG [89: validateBitcoinAddress] Number of leading '1' characters: 1
DEBUG [92: validateBitcoinAddress] base10 value inside address: 6230568508398901441201089632142185462656304983477985328510
INFO [95: validateBitcoinAddress] Address value in hex: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa39adbd7e
INFO [100: validateBitcoinAddress] Checksum: 39adbd7e
DEBUG [102: validateBitcoinAddress] Address value in hex without checksum: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
DEBUG [105: validateBitcoinAddress] Address value with leading zero bytes added back on: 00fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
DEBUG [108: validateBitcoinAddress] Recalculated checksum: 39adbd7e
DEBUG [115: validateBitcoinAddress] versionByte: 00
INFO [120: validateBitcoinAddress] publicKeyHash: fe1a2ba2103e4b61d8ad73a980cc47626ae6a0aa
INFO [124: validateBitcoinAddress] Address 1QAZz5b4MzqG64Loq3qsvAQiwHqweY7mbT validated. No problems found.
PASSED [100%]

=========================== 1 passed in 0.04 seconds ===========================











Good. That's the end of this project.