Simon Volpert httpay / master coinaddress / utils.py
master

Tree @master (Download .tar.gz)

utils.py @masterraw · history · blame

from binascii import hexlify

from ecdsa.keys import VerifyingKey
from ecdsa.curves import SECP256k1
from ecdsa.ellipticcurve import Point
from ecdsa.numbertheory import square_root_mod_prime


def int_to_hex(x: int, size) -> bytes:
    """Encode a long value as a hex string, 0-padding to size.

    Note that size is the size of the resulting hex string. So, for a 32Byte
    long size should be 64 (two hex characters per byte"."""
    f_str = "{0:0%sx}" % size
    return f_str.format(x).lower().encode()


def verifying_key_from_hex(key: bytes):
    """Load the VerifyingKey from a compressed or uncompressed hex public key.
    """
    id_byte = key[0]
    if not isinstance(id_byte, int):
        id_byte = ord(id_byte)
    if id_byte == 4:
        # Uncompressed public point
        # 1B ID + 32B x coord + 32B y coord = 65 B
        if len(key) != 65:
            raise Exception("Invalid key length")
        return create_verifying_key(
            int(hexlify(key[1:33]), 16),
            int(hexlify(key[33:]), 16)
        )
    elif id_byte in [2, 3]:
        # Compressed public point
        if len(key) != 33:
            raise Exception("Invalid key length")
        y_odd = bool(id_byte & 0x01)  # 0 even, 1 odd
        x = int(hexlify(key[1:]), 16)
        # The following x-to-pair algorithm was lifted from pycoin
        # I still need to sit down an understand it. It is also described
        # in http://www.secg.org/collateral/sec1_final.pdf
        curve = SECP256k1.curve
        p = curve.p()
        # For SECP256k1, curve.a() is 0 and curve.b() is 7, so this is
        # effectively (x ** 3 + 7) % p, but the full equation is kept
        # for just-in-case-the-curve-is-broken future-proofing
        alpha = (pow(x, 3, p) + curve.a() * x + curve.b()) % p
        beta = square_root_mod_prime(alpha, p)
        y_even = not y_odd
        if y_even == bool(beta & 1):
            return create_verifying_key(x, p - beta)
        else:
            return create_verifying_key(x, beta)
    raise Exception("The given key is not in a known format.")


def create_verifying_key(x, y):
    point = Point(SECP256k1.curve, x, y)
    return VerifyingKey.from_public_point(point, curve=SECP256k1)