Skip to content

Instantly share code, notes, and snippets.

@Nikolaj-K
Last active January 10, 2022 21:43
Show Gist options
  • Save Nikolaj-K/d548a12a45599070ea89ff376803758b to your computer and use it in GitHub Desktop.
Save Nikolaj-K/d548a12a45599070ea89ff376803758b to your computer and use it in GitHub Desktop.
From secret private key to address and WIF - on Bitcoin
"""
Discussed in the video
https://youtu.be/LYN3h5DjeXw
See e.g.
https://allbitcoinprivatekeys.com/1
* For the following we use Bitcoin specification conventions.
* Essentially all those notions were around before and later blockchain
projects are all variations of Bitcoins conventions.
Relevant for this video:
* (1) Unformated Private key
- a.k.a. secret number
- a.k.a. exponent
* (2) Public key
- a.k.a. points
* (3) Multiplication of points on an eliptic curve
- Note: Exponentiation is just repeated multiplication, thus (1)
* (4) Base encodings
* (6) Hash functions
* (5) Checksums
* (6) Blockchain addresses
* (7) Wallet input format private key (WIF)
Topic of this video:
How to get (6) and (7) from (1), i.e. explicitly computing two functions.
Here (3), (4) and (5) are largely taken as blackbox here. But see videos on my channel.
Previous related videos:
Hash functions:
* VIDEO: What's a hash and what's the point?
https://youtu.be/wZiZjXOpaEs
* VIDEO: Hash function by analogy?
https://youtu.be/L-4T-pIJuQw
* VIDEO: SHA256 live coding on bit-level with Python
https://youtu.be/UziK-Hqzwi4
* VIDEO: Blockchain explained by analogy (focused on the chain of blocks itself)
https://youtu.be/w3sI8WVX-cc
Digital signatures:
* VIDEO: The Elliptic Curve Digital Signature Algorithm and raw transactions on Bitcoin
https://youtu.be/YZafZ3Rvd8I
See link therein as well as:
For use of some of this in a Bitcoin protocol transaction (tx), see
https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-1/
https://klmoney.wordpress.com/bitcoin-dissecting-transactions-part-2-building-a-transaction-by-hand/
* VIDEO: The private key: How to digitally sign a message?
https://youtu.be/FVc-UOHcajc
Relevant external Bitcoin wiki links:
* https://en.bitcoin.it/wiki/Secp256k1
* https://en.bitcoin.it/wiki/Base58Check_encoding
* https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
* https://en.bitcoin.it/wiki/Wallet_import_format
See also the Bitcoin Stackexchange.
In fact, other projects wiki's and StackExchanges will help too.
Needs you to install all the libaries I import from right below and is only guaraneed to run on pyhton 3.7+
Don't write me emails asking how to install python or something of the sort.
If you ask me something, use an adult sentence structure and attire.
Disclaimer: Due to potential bugs, don't use functions from this script for anything of value!
I have refactored the encodings and played with this script
a thousand times, so don't use its routines for anything of value!
You will anyway find many implementations of those things
online, especially in Cpp, Java, Rust, Javascript, Python.
"""
import binascii
import ecdsa
import hashlib
from base58 import b58encode # https://github.com/serengil/crypto/blob/master/python/base58.py
# See also https://en.bitcoin.it/wiki/Base58Check_encoding
def secret_to_address(secret, legacy=False):
"""
1. Exponent (secret private key) to point on curve
2. Point to x-cooridnate
3. Coordinate to checksummed obscrurification (address)
See also
https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
etc.
"""
pubk_pair = from_secret_pubk_point(secret)
compressed_pubk, pubk = _pubk_to_compressed_pubk(*pubk_pair)
address = _pubk_to_address(pubk) if legacy else _pubk_to_address(compressed_pubk)
return address
def from_secret_pubk_point(secret):
"""
See also https://en.bitcoin.it/wiki/Secp256k1
1. Compute point from secret number (a.k.a (unformated) private key, a.k.a. exponent)
2. Format pubk_vk
3. Project out coordinates
"""
CURVE = ecdsa.SECP256k1
sk = ecdsa.SigningKey.from_secret_exponent(secret, curve=CURVE)
pubk_vk = sk.verifying_key # the point
# Note: Rougly g^(secret + x) = g^secret * g^x, where g is a point that comes with the curve spec.
# Note: 0 < secret < _r (see SECP256k1 for max secret)
pubk = binascii.b2a_hex(pubk_vk.to_string()).decode('ascii')
pubk_x = pubk[:64]
pubk_y = pubk[64:]
#print(f"pubk:\t{pubk}\npubk_x:\t{pubk_x}\npubk_y:\t{pubk_y}\n")
return pubk_x, pubk_y
def _pubk_to_compressed_pubk(pubk_x, pubk_y):
"""
Prefix pubk_x with info about pubk_y.
Note:
print(f'Uncompressed pubk: 04 {pubk_x} {pubk_y}')
"""
EVEN_PREFIX = '02'
UNEVEN_PREFIX = '03'
LEGACY_PREFIX = '04'
# Prepend parity information
y_parity = ord(bytearray.fromhex(pubk_y[-2:])) % 2
prefix = EVEN_PREFIX if y_parity==0 else UNEVEN_PREFIX
compressed_pubk = prefix + pubk_x
#print(f"y_parity:\t\t{y_parity}, compressed_pubk: {compressed_pubk}")
pubk = LEGACY_PREFIX + pubk_x + pubk_y
return compressed_pubk, pubk
def _pubk_to_address(pubk):
"""
Hash and checksum the pubk.
Note: No discussion of Pay-to-ScriptHash addresses ("3..") here but only Pay-to-PubKeyHash ("1..")
Newer SegWit addresses ("bc..") are a deviation in some details.
"""
pubk_array = bytearray.fromhex(pubk)
# Compute key_hash
sha = hashlib.sha256() # See Nikolaj-K videos
sha.update(pubk_array)
rip = hashlib.new('ripemd160')
rip.update(sha.digest())
PREFIX = '00'
key_hash = PREFIX + rip.hexdigest() # .hexdigest() is hex ASCII
#print(f"key_hash:\t\t{key_hash}")
# Compute checksum to append to key_hash
sha = hashlib.sha256()
sha.update(bytearray.fromhex(key_hash))
checksum = sha.digest()
sha = hashlib.sha256()
sha.update(checksum)
checksum = sha.hexdigest()[0:8]
# Append checksum
address_hex = key_hash + checksum
# expected encoding
bs = bytes(bytearray.fromhex(address_hex))
address = b58encode(bs).decode('utf-8')
return address
def secret_to_wif(secret):
"""
See also
https://en.bitcoin.it/wiki/Wallet_import_format
etc.
"""
# Cast / Format
PREFIX = "80"
hex_string = hex(secret)[2:].zfill(64) # remove 0x and fill with zeros
pre_hash = PREFIX + hex_string
# Compute checksum via hashing
hash_1 = hashlib.sha256(binascii.unhexlify(pre_hash)).hexdigest()
hash_2 = hashlib.sha256(binascii.unhexlify(hash_1)).hexdigest()
checksum = hash_2[:8]
# Append checksum
pre_hash_checksum = pre_hash + checksum
# Cast / Format
from_hex_string = int(pre_hash_checksum, 16)
def _get(idx):
ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
m = 58**idx
idx = from_hex_string // m % 58
return ALPHABET[idx]
IDXS = range(100) # sufficiently long
wif_str = "".join(map(_get, IDXS))
# Reverse
rev_wif_str = wif_str[::-1].lstrip('1')
return rev_wif_str
if __name__ == "__main__":
EXAMPLE_PRIVATE_KEYS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 17, 18, 19, 7000000003, 9001000007000000003, 666]
for secret in EXAMPLE_PRIVATE_KEYS:
# Compute wallet-input-format of private key
wif = secret_to_wif(secret)
# Compute adresses
address_legacy = secret_to_address(secret, True)
address = secret_to_address(secret)
print(
f"priv key (secret):\t{secret}\n"
f"priv key (secret wif):\t{wif}\n"
f"legacy address:\t\t{address_legacy}\n"
f"address:\t\t{address}\n" + 80*"-"
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment