A week ago or something, I decided it was a perfect time to start learning to program, and after doing some research, I came to the conclusion that Python is a good programming language to start with. In my opinion, the best approach to learning something is practice and practice, which is why I think that writing programs is the best way to learn how to write good programs. By combining my theoretical interest in Bitcoin and practical interest in programming, I believe I can implement things (related to Bitcoin and other fields) that I previously only read about and make them more "real."
Below is a program that takes a private key in hex format, converts it to WIF (compressed and uncompressed), calculates public keys (compressed and uncompressed) and corresponding addresses. Functions that do elliptic curve calculation (modulo inverse, point addition, doubling and multiplication) and elliptic curve parameters were taken from here:
https://github.com/wobine/blackboard101import hashlib
import base58
class PublicKey:
"""Calculating public key from private key"""
def __init__(self, private_key):
"""Initialize parameters of an elliptic curve"""
self.private_key = private_key
self.p_curve = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1 # The proven prime
self.n_curve = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 # Number of points in the field
self.a_curve, self.b_curve = 0, 7 # These two defines the elliptic curve. y^2 = x^3 + Acurve * x + Bcurve
self.gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
self.gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424
self.gpoint = (self.gx, self.gy) # This is generator point.
def hash(self, key):
"""Function performing SHA-256 hashing"""
sha = hashlib.sha256()
sha.update(bytes.fromhex(key))
return sha.hexdigest()
def public_address(self, key):
"""Function calculating address"""
ripemd = hashlib.new('ripemd160')
sha = hashlib.sha256()
sha.update(bytes.fromhex(key))
ripemd.update(sha.digest())
address = f"00{ripemd.hexdigest()}"
checksum = self.hash(self.hash(address))[:8]
address = f"{address}{checksum}"
address = base58.b58encode(bytes.fromhex(address)).decode("UTF-8")
return address
def privatewif(self):
"""Converting a private key to WIF"""
private_wif = f"80{hex(self.private_key)[2:]}"
private_wif_comp = f"80{hex(self.private_key)[2:]}01"
checksum = self.hash(self.hash(private_wif))[:8]
checksum_comp = self.hash(self.hash(private_wif_comp))[:8]
private_wif = f"{private_wif}{checksum}"
private_wif_comp = f"{private_wif_comp}{checksum_comp}"
private_wif = base58.b58encode(bytes.fromhex(private_wif)).decode("UTF-8")
private_wif_comp = base58.b58encode(bytes.fromhex(private_wif_comp)).decode("UTF-8")
print(f"\nWIF - private key\n{private_wif}")
print(f"Length: {len(private_wif)}\n")
print(f"WIF compressed - private key\n{private_wif_comp}")
print(f"Length: {len(private_wif_comp)}\n")
def modinv(self, a, n):
"""Extended Euclidean Algorithm"""
lm, hm = 1, 0
low, high = a%n, n
while low > 1:
ratio = int(high/low)
nm, new = int(hm-lm*ratio), int(high-low*ratio)
lm, low, hm, high = nm, new, lm, low
return lm % n
def ec_add(self, a, b):
"""Point addition"""
lam_add = ((b[1]-a[1]) * self.modinv(b[0]-a[0], self.p_curve)) % self.p_curve
x = (pow(lam_add, 2)-a[0]-b[0]) % self.p_curve
y = (lam_add*(a[0]-x)-a[1]) % self.p_curve
return x, y
def ec_double(self, a):
"""EC point doubling"""
lam = ((3*a[0]*a[0]+self.a_curve) * self.modinv((2*a[1]), self.p_curve)) % self.p_curve
x = int((lam*lam-2*a[0]) % self.p_curve)
y = int((lam*(a[0]-x)-a[1]) % self.p_curve)
return x, y
def ec_multiply(self, genpoint, scalarhex):
"""EC point multiplication"""
if scalarhex == 0 or scalarhex >= self.n_curve: raise Exception("Invalid Scalar/Private Key")
scalarbin = str(bin(scalarhex))[2:]
q = genpoint
for i in range (1, len(scalarbin)): #Double and add to multiply point
q = self.ec_double(q)
if scalarbin[i] == "1":
q = self.ec_add(q, genpoint)
return q
def public_calc(self):
"""Calculating a public key"""
public_key = self.ec_multiply(self.gpoint, self.private_key)
print("\nThe uncompressed public key (not address):")
print(public_key)
print("\nThe uncompressed public key (HEX):")
message = f"04{(hex(public_key[0])[2:]):0>64}{(hex(public_key[1])[2:]):0>64}"
print(message)
print(f"Length: {len(message)}\n")
message = self.public_address(message)
print(f"Address from uncompressed key\n{message}")
print("\nThe official Public Key - compressed:")
if public_key[1] % 2 == 1: # If the Y value for the Public Key is odd.
message = f"03{hex(public_key[0])[2:]:0>64}"
print(message)
print(f"Length: {len(message)}\n")
message = self.public_address(message)
print(f"Address from compressed key\n{message}")
else: # Or else, if the Y value is even.
message = f"02{hex(public_key[0])[2:]:0>64}"
print(message)
print(f"Length: {len(message)}\n")
message = self.public_address(message)
print(f"Address from compressed key\n{message}")
prompt = input(f"Please insert your private key in HEX format (0x): ")
try:
prompt = int(prompt, 16) # interpret the input as a base-16 number, a hexadecimal.
except ValueError:
print("You did not enter a hexadecimal number!")
my_key = PublicKey(prompt)
my_key.public_calc()
my_key.privatewif()
Source code is also available here:
https://github.com/witcher-sense/learning_python/blob/main/PrivateToPublic.pyIt only will work with Python 3.6 or higher, and you will need to make some changes to openssl.conf to get RIPEMD160 hashing working properly.
If you're on Linux, then you can do the following:
To locate a configuration file, insert the following command:
Copy the path and change your working directory, for example:
Open openssl.conf in any text editor. You will need a super user rights to edit this file. If you don't have it, ask your administrator for help:
Add the following lines to a configuration file (some of them are already there and some are waiting for "uncommenting"):
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
[provider_sect]
default = default_sect
legacy = legacy_sect
[default_sect]
activate = 1
[legacy_sect]
activate = 1
After these manipulations, RIPEDMD should start working.