0

This script gets the public address from the uncompressed private key.

import ecdsa
import hashlib
import base58

private_key = input("privatekey: ") # e41b45e722251672c01a28e4fada590471fea09f90d13b143033ed3a1107ef49

# Convert hex private key to bytes
private_key = bytes.fromhex(private_key)

# Derivation of the private key
signing_key = ecdsa.SigningKey.from_string(
    private_key, curve=ecdsa.SECP256k1)
verifying_key = signing_key.get_verifying_key()

public_key = bytes.fromhex("04") + verifying_key.to_string()

# Hashes of public key
sha256_1 = hashlib.sha256(public_key)
ripemd160 = hashlib.new("ripemd160")
ripemd160.update(sha256_1.digest())

# Adding prefix to identify Network
hashed_public_key = bytes.fromhex("00") + ripemd160.digest()

# Checksum calculation
checksum_full = hashlib.sha256(
    hashlib.sha256(hashed_public_key).digest()).digest()
checksum = checksum_full[:4]

# Adding checksum to hashpubkey
bin_addr = hashed_public_key + checksum

# Encoding to address
address = str(base58.b58encode(bin_addr))
final_address = address[2:-1]  # change 2

print(final_address)

Format (private keys):

        uncompressed: 0x80 + [32-byte secret] + [4 bytes of Hash() of previous 33 bytes], base58 encoded
        compressed: 0x80 + [32-byte secret] + 0x01 + [4 bytes of Hash() previous 34 bytes], base58 encoded
    
    case 1:

    secret (hex): 1111111111111111111111111111111111111111111111111111111111111111
    uncompressed:
        secret (base58): 5HwoXVkHoRM8sL2KmNRS217n1g8mPPBomrY7yehCuXC1115WWsh
        pubkey (hex): 044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1
        address (base58): 1MsHWS1BnwMc3tLE8G35UXsS58fKipzB7a
    compressed:
        secret (base58): L4xkFv5sbttQcsgnTjzqJuQdNfHC5gk9zsK5HmaV822qu79zJA7L
        pubkey (hex): 034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa
        address (base58): 1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9

I need to get this code to work with two formats, but I don't understand what needs to be fixed. If I insert a compression private key 682cf5791be8d4526aacf63bb7ab201f5cd1d732c1f634e5839379191b02295925 , I get an error:

ecdsa.keys.MalformedPointError: Invalid length of private key, received 33, expected 32

I understand that the bottom part of this code can be used, but I am failing to lack knowledge.

def __private_to_compressed_public(private_key):
        private_hex = codecs.decode(private_key, 'hex')
        # Get ECDSA public key
        key = ecdsa.SigningKey.from_string(private_hex, curve=ecdsa.SECP256k1).verifying_key
        key_bytes = key.to_string()
        key_hex = codecs.encode(key_bytes, 'hex')
        # Get X from the key (first half)
        key_string = key_hex.decode('utf-8')
        half_len = len(key_hex) // 2
        key_half = key_hex[:half_len]
        # Add bitcoin byte: 0x02 if the last digit is even, 0x03 if the last digit is odd
        last_byte = int(key_string[-1], 16)
        bitcoin_byte = b'02' if last_byte % 2 == 0 else b'03'
        public_key = bitcoin_byte + key_half
        return public_key 

I've done a lot of tests, but I can't figure out what's wrong. Tell me how to modify the code correctly.

Thank you in advance for your help.

3
  • Why are you adding an extra byte into the compressed private key?
    – Claris
    Feb 24, 2021 at 17:44
  • This code for an example to get an uncompressed key. I tried to change the code but can't, the code breaks after running ecdsa library when I add a compressed key. It does not reach the lower parameters.
    – Jon Do
    Feb 24, 2021 at 17:55
  • The WIF-encoded private key has a flag byte indicating whether to use a compressed or uncompressed public key, but the private key of elliptic curve cryptography itself does not distinguish between compressed and uncompressed. Feb 25, 2021 at 9:22

1 Answer 1

1

If I get your code and use "e41b45e722251672c01a28e4fada590471fea09f90d13b143033ed3a1107ef49" as secret hex value AKA secret private key... then I get result: "12sF1DbBbPaoNYrs28Qm7waiCcAVoF93Nn" which is uncompressed public address.

Check my script, it is more extended, but with a copule of imports for an educational point of view, to show all the process. You will get compressed and uncompressed public keys from a given secret in hexadecimal value.

First, the script will get uncompressed private key and compressed private key in hex:

'04417A55413D948D79F5194F1F2CD670F078CB7F6D3A2F2B12E8CDF9A3268CAD3BAAA3251D2587D4E57ACBCE7991B72355EA33C44DBCF260D09B6C921879A61AA4'
'02417A55413D948D79F5194F1F2CD670F078CB7F6D3A2F2B12E8CDF9A3268CAD3B'

Then it will get both public uncompressed and compressed public keys.

The code:

#!/usr/bin/python3
from hashlib import sha256, new
import binascii

PCURVE = 2 ** 256 - 2 ** 32 - 2 ** 9 - 2 ** 8 - 2 ** 7 - 2 ** 6 - 2 ** 4 - 1  # The proven prime
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141  # Number of points in the field
ACURVE = 0
BCURVE = 7  # These two defines the elliptic curve. y^2 = x^3 + Acurve * x + Bcurve
Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424
GPOINT = (Gx, Gy)  # This is our generator point. Trillions of dif ones possible


def modinv(a: int, n: int = PCURVE):
    # MAXIMO COMUN DIVISOR: Extended Euclidean Algorithm/'division' in elliptic curves
    lm, hm = 1, 0
    resto = a % n
    high = n
    while resto > 1:
        ratio = high // resto
        nm = hm - lm * ratio
        new = high - resto * ratio
        lm, resto, hm, high = nm, new, lm, resto
    return lm % n


def ECadd(a, b):  # Not true addition, invented for EC. Could have been called anything.
    LamAdd = ((b[1] - a[1]) * modinv(b[0] - a[0], PCURVE)) % PCURVE
    x = (LamAdd * LamAdd - a[0] - b[0]) % PCURVE
    y = (LamAdd * (a[0] - x) - a[1]) % PCURVE
    return x, y


def ECdouble(a):  # This is called point doubling, also invented for EC.
    Lam = ((3 * a[0] * a[0] + ACURVE) * modinv((2 * a[1]), PCURVE)) % PCURVE
    x = (Lam * Lam - 2 * a[0]) % PCURVE
    y = (Lam * (a[0] - x) - a[1]) % PCURVE
    return x, y


def EccMultiply(gen_point: tuple, scalar_hex: int):  # Double & add. Not true multiplication
    if scalar_hex == 0 or scalar_hex >= N:
        raise Exception("Invalid Scalar/Private Key")
    ScalarBin = str(bin(scalar_hex))[2:]  # string binario sin el comienzo 0b
    Q = gen_point  # esto es una tupla de dos integer del punto de generacion de la curva
    for i in range(1, len(ScalarBin)):
        Q = ECdouble(Q)
        if ScalarBin[i] == "1":
            Q = ECadd(Q, gen_point)  #
    return Q


def private_to_hex_publics(hex_private_key: hex):
    public_key = EccMultiply(GPOINT, hex_private_key)
    public_uncompressed = f"04{hex(public_key[0])[2:].upper()}{hex(public_key[1])[2:].upper()}"

    if public_key[1] % 2 == 1:  # If the Y value for the Public Key is odd.
        public_compressed = ("03" + str(hex(public_key[0])[2:]).zfill(64).upper())
    else:  # Or else, if the Y value is even.
        public_compressed = ("02" + str(hex(public_key[0])[2:]).zfill(64).upper())
    return public_uncompressed, public_compressed


def hash_256_from_hex_string_like_bytes(hexstring: str):
    return sha256(bytes.fromhex(hexstring)).hexdigest()


def ripemd160_from_hex_string_like_bytes(hexstring: str):
    return new('ripemd160', bytes.fromhex(hexstring)).hexdigest()


def b58encode(hex_string, expected_length=None):
    v = binascii.unhexlify(hex_string)
    alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    lev, number = 1, 0
    for char in reversed(v):
        number += lev * char
        lev = lev << 8  # 2^8
    string = ""
    while number:
        number, modulo = divmod(number, 58)
        string = alphabet[modulo] + string
    if not expected_length:
        return string
    elif len(string) != expected_length:
        raise Exception(f"b58encode: Expected length={expected_length} obtained length={len(string)}")
    else:
        return string


def sha256_get_checksum(hex_string_to_checksum):
    hasha1 = hash_256_from_hex_string_like_bytes(hex_string_to_checksum)
    # print("HashA1", hasha1)
    hasha2 = hash_256_from_hex_string_like_bytes(hasha1)
    # print("HashA2", hasha2)
    return hasha2[:8].upper()


def sha_ripe_digest(hex_string_to_checksum):
    hashc1 = hash_256_from_hex_string_like_bytes(hex_string_to_checksum)
    hashc2 = ripemd160_from_hex_string_like_bytes(hashc1)
    return hashc2.upper()


def wif_from_private(privkey: hex):
    # put 80 for bitcoin and concatenate with privkey
    prepend = "80"
    private_key_str = hex(privkey)[2:].zfill(64)
    prepended = (prepend + private_key_str).upper()
    compressed = (prepend + private_key_str + "01").upper()

    if len(prepended) != 66 or len(compressed) != 68:
        raise Exception("WIF conversion: Wrong prepended or compressed private key, length not 66")

    uncompressed_checksum = sha256_get_checksum(prepended)
    compressed_checksum = sha256_get_checksum(compressed)
    private_key_uncompressed_checksum = prepended + uncompressed_checksum
    private_key_compressed_checksum = compressed + compressed_checksum
    private_key_WIF_uncompressed_Base58 = b58encode(private_key_uncompressed_checksum, 51)
    private_key_WIF_compressed_Base58 = b58encode(private_key_compressed_checksum, 52)

    print("PREPENDED:\t\t\t\t", prepended)
    print("PRIV_UNCOMP+CHECKSUM:\t\t\t", private_key_uncompressed_checksum)
    print("Private_key_WIF_uncompressed_Base58:\t", private_key_WIF_uncompressed_Base58)
    print("PRIV_COMP+CHECKSUM:\t\t\t", private_key_compressed_checksum)
    print("Private_key_WIF_compressed_Base58:\t", private_key_WIF_compressed_Base58)
    return private_key_WIF_uncompressed_Base58, private_key_WIF_compressed_Base58


def hex_public_to_public_addresses(hex_publics):
    uncompressed = hex_publics[0]
    public_key_hashC_uncompressed = "00" + sha_ripe_digest(uncompressed)
    checksum = sha256_get_checksum(public_key_hashC_uncompressed)
    PublicKeyChecksumC = public_key_hashC_uncompressed + checksum
    public_address_uncompressed = "1" + b58encode(PublicKeyChecksumC, 33)
    print("Public address uncompressed:\t", public_address_uncompressed)

    compressed = hex_publics[1]
    PublicKeyVersionHashD = "00" + sha_ripe_digest(compressed)
    compressed_checksum = sha256_get_checksum(PublicKeyVersionHashD)
    PublicKeyChecksumC = PublicKeyVersionHashD + compressed_checksum
    public_address_compressed = "1" + b58encode(PublicKeyChecksumC, 33)
    print("Public address compressed:\t", public_address_compressed)
    return public_address_uncompressed, public_address_compressed


if __name__ == "__main__":
    privkey = 0xe41b45e722251672c01a28e4fada590471fea09f90d13b143033ed3a1107ef49
    print(f"PRIVATE KEY:\t {hex(privkey)[2:].zfill(64).upper()}")

    # Public hex test
    hex_publics = private_to_hex_publics(privkey)
    print(hex_publics)
    print()

    # WIF creation test
    wif = wif_from_private(privkey)
    print(wif)

    # Public keys
    public = hex_public_to_public_addresses(hex_publics)
    print(public)

The script is inspired by this site.

Finally you will obtain the two public keys, uncompressed and compressed:

'12sF1DbBbPaoNYrs28Qm7waiCcAVoF93Nn', '1BMKGYmgjrvmSDtvLZvKjyVMnbi6FLmcVi'

Hope this will help you.

Note: it will give you the WIF format too.

Full output:

PRIVATE KEY:     E41B45E722251672C01A28E4FADA590471FEA09F90D13B143033ED3A1107EF49
('04417A55413D948D79F5194F1F2CD670F078CB7F6D3A2F2B12E8CDF9A3268CAD3BAAA3251D2587D4E57ACBCE7991B72355EA33C44DBCF260D09B6C921879A61AA4', '02417A55413D948D79F5194F1F2CD670F078CB7F6D3A2F2B12E8CDF9A3268CAD3B')

PREPENDED:                               80E41B45E722251672C01A28E4FADA590471FEA09F90D13B143033ED3A1107EF49
PRIV_UNCOMP+CHECKSUM:                    80E41B45E722251672C01A28E4FADA590471FEA09F90D13B143033ED3A1107EF496CB6A884
Private_key_WIF_uncompressed_Base58:     5KYkFr9FMmBVcwjLbJinAw94b985tgyt4PL9Jhcjsk6J6ZRfxjM
PRIV_COMP+CHECKSUM:                      80E41B45E722251672C01A28E4FADA590471FEA09F90D13B143033ED3A1107EF4901070CD9AC
Private_key_WIF_compressed_Base58:       L4s7vXuQNe1KKeZsURDQNqxaqgrGb9U4MwZVf8GkEyCNTKyEk3iK
('5KYkFr9FMmBVcwjLbJinAw94b985tgyt4PL9Jhcjsk6J6ZRfxjM', 'L4s7vXuQNe1KKeZsURDQNqxaqgrGb9U4MwZVf8GkEyCNTKyEk3iK')
Public address uncompressed:     12sF1DbBbPaoNYrs28Qm7waiCcAVoF93Nn
Public address compressed:       1BMKGYmgjrvmSDtvLZvKjyVMnbi6FLmcVi

Editing original code by Jon Do request

import ecdsa
import hashlib
import base58

def bytes_to_hex_string(b: bytes):
    return ''.join('{:02x}'.format(x) for x in b).upper()

def get_prepend_if_even_or_odd_for_compressed(x_point):
    y = int(x_point[64:], 16)
    if y % 1 == 1:
        return "03"  # odd
    return "02"  # even

private_key = "e41b45e722251672c01a28e4fada590471fea09f90d13b143033ed3a1107ef49"

# Convert hex private key to bytes
private_key = bytes.fromhex(private_key)
print(f"Private key hex string: \t\t {bytes_to_hex_string(private_key)}")
print(f"Private key bytes: \t\t\t {private_key}")
# Derivation of the private key
signing_key = ecdsa.SigningKey.from_string(private_key, curve=ecdsa.SECP256k1)

verifying_key = signing_key.get_verifying_key()
print(f"Verifying key, x and y points (bytes):\t{verifying_key.to_string()}", type(verifying_key.to_string()))

x_point = bytes_to_hex_string(verifying_key.to_string())
print(f"Uncompressed private key (hex):\t\t 04{x_point.upper()}")

even_odd = get_prepend_if_even_or_odd_for_compressed(x_point)
print(f"Compressed private key in (hex):\t {even_odd}{x_point.upper()[:64]}")

# public_key = bytes.fromhex("04") + verifying_key.to_string()  # this is error, you're using uncompressed private key
public_key = bytes.fromhex(even_odd) + verifying_key.to_string()[:32]

# Hashes of public key
sha256_1 = hashlib.sha256(public_key)  # now public_key contains compressed private key
ripemd160 = hashlib.new("ripemd160")
ripemd160.update(sha256_1.digest())

# Adding prefix to identify Network
hashed_public_key = bytes.fromhex("00") + ripemd160.digest() # hashed_private_key is public key

# Checksum calculation
checksum_full = hashlib.sha256(
    hashlib.sha256(hashed_public_key).digest()).digest()
checksum = checksum_full[:4]

# Adding checksum to hashpubkey
bin_addr = hashed_public_key + checksum

# Encoding to address
address = str(base58.b58encode(bin_addr))
final_address = address[2:-1]  # change 2

print(f"Public compressed key (hex): \t\t {final_address}")

The new output is:

Private key hex string:                  E41B45E722251672C01A28E4FADA590471FEA09F90D13B143033ED3A1107EF49
Private key bytes:                       b'\xe4\x1bE\xe7"%\x16r\xc0\x1a(\xe4\xfa\xdaY\x04q\xfe\xa0\x9f\x90\xd1;\x1403\xed:\x11\x07\xefI'
Verifying key, x and y points (bytes):  b'AzUA=\x94\x8dy\xf5\x19O\x1f,\xd6p\xf0x\xcb\x7fm:/+\x12\xe8\xcd\xf9\xa3&\x8c\xad;\xaa\xa3%\x1d%\x87\xd4\xe5z\xcb\xcey\x91\xb7#U\xea3\xc4M\xbc\xf2`\xd0\x9bl\x92\x18y\xa6\x1a\xa4' <class 'bytes'>
Uncompressed private key (hex):          04417A55413D948D79F5194F1F2CD670F078CB7F6D3A2F2B12E8CDF9A3268CAD3BAAA3251D2587D4E57ACBCE7991B72355EA33C44DBCF260D09B6C921879A61AA4
Compressed private key in (hex):         02417A55413D948D79F5194F1F2CD670F078CB7F6D3A2F2B12E8CDF9A3268CAD3B
Public compressed key (hex):             1BMKGYmgjrvmSDtvLZvKjyVMnbi6FLmcVi
5
  • Thanks. You have a very nice script. But I need to modify my script for my application. Would you suggest how you can fix it?
    – Jon Do
    Feb 26, 2021 at 13:04
  • I will try and add it to the previous response
    – Nand0san
    Feb 26, 2021 at 13:06
  • Added some prints for explaining the error and commented errors @JonDo
    – Nand0san
    Feb 26, 2021 at 14:01
  • I figured out what the problem is, your code in both cases accepts an uncompressed private key as input (Hex e41b45e722251672c01a28e4fada590471fea09f90d13b143033ed3a1107ef49). My task was to send a compressed private key as input (Hex-compressed 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01) and from it I have to get the address. As a result, in the first version, the scripts work fine, and in the second case, an error occurs. Perhaps I did not explain the problem well, in any case, thanks for the help and I will be grateful if you can help me solve my question.
    – Jon Do
    Feb 26, 2021 at 20:38
  • Hi @JonDo, maybe we can open another question and I will be happy to answer in it. Please send me a message and I will check it
    – Nand0san
    Feb 27, 2021 at 11:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.