Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/key.py
#!/usr/bin/env python3 | |||||
# Copyright (c) 2011 Sam Rushing | # Copyright (c) 2011 Sam Rushing | ||||
# Copyright 2019 The Bitcoin Developers | |||||
"""ECC secp256k1 OpenSSL wrapper. | """ECC secp256k1 OpenSSL wrapper. | ||||
WARNING: This module does not mlock() secrets; your private keys may end up on | WARNING: This module does not mlock() secrets; your private keys may end up on | ||||
disk in swap! Use with caution! | disk in swap! Use with caution! | ||||
This file is modified from python-bitcoinlib. | This file is modified from python-bitcoinlib. | ||||
""" | """ | ||||
import ctypes | import ctypes | ||||
import ctypes.util | import ctypes.util | ||||
import hashlib | import hashlib | ||||
import hmac | |||||
import sys | import sys | ||||
ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or 'libeay32') | ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or 'libeay32') | ||||
ssl.BN_new.restype = ctypes.c_void_p | ssl.BN_new.restype = ctypes.c_void_p | ||||
ssl.BN_new.argtypes = [] | ssl.BN_new.argtypes = [] | ||||
ssl.BN_free.restype = None | ssl.BN_free.restype = None | ||||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | |||||
ssl.EC_POINT_free.restype = None | ssl.EC_POINT_free.restype = None | ||||
ssl.EC_POINT_free.argtypes = [ctypes.c_void_p] | ssl.EC_POINT_free.argtypes = [ctypes.c_void_p] | ||||
ssl.EC_POINT_mul.restype = ctypes.c_int | ssl.EC_POINT_mul.restype = ctypes.c_int | ||||
ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, | ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, | ||||
ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] | ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] | ||||
ssl.EC_KEY_priv2oct.restype = ctypes.c_size_t | |||||
ssl.EC_KEY_priv2oct.argtypes = [ | |||||
ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t] | |||||
ssl.EC_POINT_point2oct.restype = ctypes.c_size_t | |||||
ssl.EC_POINT_point2oct.argtypes = [ctypes.c_void_p, ctypes.c_void_p, | |||||
ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p] | |||||
# this specifies the curve used with ECDSA. | # this specifies the curve used with ECDSA. | ||||
NID_secp256k1 = 714 # from openssl/obj_mac.h | NID_secp256k1 = 714 # from openssl/obj_mac.h | ||||
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 | SECP256K1_FIELDSIZE = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f | ||||
SECP256K1_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 | |||||
SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 | SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 | ||||
# Thx to Sam Devlin for the ctypes magic 64-bit fix. | # Thx to Sam Devlin for the ctypes magic 64-bit fix. | ||||
def _check_result(val, func, args): | def _check_result(val, func, args): | ||||
if val == 0: | if val == 0: | ||||
raise ValueError | raise ValueError | ||||
else: | else: | ||||
return ctypes.c_void_p(val) | return ctypes.c_void_p(val) | ||||
ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p | ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p | ||||
ssl.EC_KEY_new_by_curve_name.errcheck = _check_result | ssl.EC_KEY_new_by_curve_name.errcheck = _check_result | ||||
def jacobi(a, n): | |||||
"""Jacobi symbol""" | |||||
# Based on the Handbook of Applied Cryptography (HAC), algorithm 2.149. | |||||
# This function has been tested by comparison with a small | |||||
# table printed in HAC, and by extensive use in calculating | |||||
# modular square roots. | |||||
# Borrowed from python ecdsa package (function originally from Peter Pearson) | |||||
# ... modified to use bitwise arithmetic when possible, for speed. | |||||
assert n >= 3 | |||||
assert n & 1 == 1 | |||||
a = a % n | |||||
if a == 0: | |||||
return 0 | |||||
if a == 1: | |||||
return 1 | |||||
a1, e = a, 0 | |||||
while a1 & 1 == 0: | |||||
a1, e = a1 >> 1, e+1 | |||||
if e & 1 == 0 or n & 7 == 1 or n & 7 == 7: | |||||
s = 1 | |||||
else: | |||||
s = -1 | |||||
if a1 == 1: | |||||
return s | |||||
if n & 3 == 3 and a1 & 3 == 3: | |||||
s = -s | |||||
return s * jacobi(n % a1, a1) | |||||
def nonce_function_rfc6979(privkeybytes, msg32, algo16=b'', ndata=b''): | |||||
# see nonce_function_rfc6979() in secp256k1.c; and details in hash_impl.h | |||||
assert len(privkeybytes) == 32 | |||||
assert len(msg32) == 32 | |||||
assert len(algo16) in (0, 16) | |||||
assert len(ndata) in (0, 32) | |||||
V = b'\x01'*32 | |||||
K = b'\x00'*32 | |||||
blob = bytes(privkeybytes) + msg32 + ndata + algo16 | |||||
# initialize | |||||
K = hmac.HMAC(K, V+b'\x00'+blob, 'sha256').digest() | |||||
V = hmac.HMAC(K, V, 'sha256').digest() | |||||
K = hmac.HMAC(K, V+b'\x01'+blob, 'sha256').digest() | |||||
V = hmac.HMAC(K, V, 'sha256').digest() | |||||
# loop forever until an in-range k is found | |||||
k = 0 | |||||
while True: | |||||
# see RFC6979 3.2.h.2 : we take a shortcut and don't build T in | |||||
# multiple steps since the first step is always the right size for | |||||
# our purpose. | |||||
V = hmac.HMAC(K, V, 'sha256').digest() | |||||
T = V | |||||
assert len(T) >= 32 | |||||
k = int.from_bytes(T, 'big') | |||||
if k > 0 and k < SECP256K1_ORDER: | |||||
break | |||||
K = hmac.HMAC(K, V+b'\x00', 'sha256').digest() | |||||
V = HMAC_K(V) | |||||
return k | |||||
class CECKey(): | class CECKey(): | ||||
"""Wrapper around OpenSSL's EC_KEY""" | """Wrapper around OpenSSL's EC_KEY""" | ||||
POINT_CONVERSION_COMPRESSED = 2 | POINT_CONVERSION_COMPRESSED = 2 | ||||
POINT_CONVERSION_UNCOMPRESSED = 4 | POINT_CONVERSION_UNCOMPRESSED = 4 | ||||
def __init__(self): | def __init__(self): | ||||
self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1) | self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1) | ||||
▲ Show 20 Lines • Show All 84 Lines • ▼ Show 20 Lines | def sign(self, hash, low_s=True): | ||||
while len(low_s_bytes) > 1 and low_s_bytes[0] == 0 and low_s_bytes[1] < 0x80: | while len(low_s_bytes) > 1 and low_s_bytes[0] == 0 and low_s_bytes[1] < 0x80: | ||||
low_s_bytes = low_s_bytes[1:] | low_s_bytes = low_s_bytes[1:] | ||||
new_s_size = len(low_s_bytes) | new_s_size = len(low_s_bytes) | ||||
new_total_size_byte = ( | new_total_size_byte = ( | ||||
total_size + new_s_size - s_size).to_bytes(1, byteorder='big') | total_size + new_s_size - s_size).to_bytes(1, byteorder='big') | ||||
new_s_size_byte = (new_s_size).to_bytes(1, byteorder='big') | new_s_size_byte = (new_s_size).to_bytes(1, byteorder='big') | ||||
return b'\x30' + new_total_size_byte + mb_sig.raw[2:5 + r_size] + new_s_size_byte + low_s_bytes | return b'\x30' + new_total_size_byte + mb_sig.raw[2:5 + r_size] + new_s_size_byte + low_s_bytes | ||||
def sign_schnorr(self, msg32): | |||||
"""Create Schnorr signature (BIP-Schnorr convention).""" | |||||
# buffer for private key | |||||
privkeybuf = ctypes.create_string_buffer(32) | |||||
# buffer for uncompressed R coord | |||||
Rbuf = ctypes.create_string_buffer(65) | |||||
# buffer for compressed pubkey | |||||
pubkeybuf = ctypes.create_string_buffer(33) | |||||
assert ssl.EC_KEY_priv2oct(self.k, privkeybuf, 32) == 32 | |||||
privkey = int.from_bytes(privkeybuf, 'big') | |||||
k = nonce_function_rfc6979( | |||||
privkeybuf, msg32, algo16=b"Schnorr+SHA256 ") | |||||
# calculate R point and get its uncompressed form | |||||
group = ssl.EC_KEY_get0_group(self.k) | |||||
pubkey = ssl.EC_KEY_get0_public_key(self.k) | |||||
R = ssl.EC_POINT_new(group) | |||||
ctx = ssl.BN_CTX_new() | |||||
kbn = ssl.BN_bin2bn(k.to_bytes(32, 'big'), 32, None) | |||||
assert ssl.EC_POINT_mul(group, R, kbn, None, None, ctx) | |||||
assert 65 == ssl.EC_POINT_point2oct( | |||||
group, R, self.POINT_CONVERSION_UNCOMPRESSED, Rbuf, 65, ctx) | |||||
# (also grab the pubkey while we have the ctx open) | |||||
assert 33 == ssl.EC_POINT_point2oct( | |||||
group, pubkey, self.POINT_CONVERSION_COMPRESSED, pubkeybuf, 33, ctx) | |||||
ssl.BN_free(kbn) | |||||
ssl.BN_CTX_free(ctx) | |||||
ssl.EC_POINT_free(R) | |||||
Ry = int.from_bytes(Rbuf[33:65], 'big') # y coord | |||||
if jacobi(Ry, SECP256K1_FIELDSIZE) == -1: | |||||
k = SECP256K1_ORDER - k | |||||
rbytes = Rbuf[1:33] # x coord big-endian | |||||
e = int.from_bytes(hashlib.sha256( | |||||
rbytes + pubkeybuf + msg32).digest(), 'big') | |||||
s = (k + e*privkey) % SECP256K1_ORDER | |||||
return rbytes + s.to_bytes(32, 'big') | |||||
def verify(self, hash, sig): | def verify(self, hash, sig): | ||||
"""Verify a DER signature""" | """Verify a DER signature""" | ||||
return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1 | return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1 | ||||
def set_compressed(self, compressed): | def set_compressed(self, compressed): | ||||
if compressed: | if compressed: | ||||
form = self.POINT_CONVERSION_COMPRESSED | form = self.POINT_CONVERSION_COMPRESSED | ||||
else: | else: | ||||
Show All 35 Lines | class CPubKey(bytes): | ||||
def __repr__(self): | def __repr__(self): | ||||
# Always have represent as b'<secret>' so test cases don't have to | # Always have represent as b'<secret>' so test cases don't have to | ||||
# change for py2/3 | # change for py2/3 | ||||
if sys.version > '3': | if sys.version > '3': | ||||
return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) | return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) | ||||
else: | else: | ||||
return '%s(b%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) | return '%s(b%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) | ||||
if __name__ == '__main__': | |||||
# Test Schnorr implementation. | |||||
# duplicate the deterministic sig test from src/test/key_tests.cpp | |||||
privkeybytes = bytes.fromhex( | |||||
"12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747") | |||||
private_key = CECKey() | |||||
private_key.set_secretbytes(privkeybytes) | |||||
def sha(b): | |||||
return hashlib.sha256(b).digest() | |||||
msg = b"Very deterministic message" | |||||
msghash = sha(sha(msg)) | |||||
assert msghash == bytes.fromhex( | |||||
"5255683da567900bfd3e786ed8836a4e7763c221bf1ac20ece2a5171b9199e8a") | |||||
sig = private_key.sign_schnorr(msghash) | |||||
assert sig == bytes.fromhex( | |||||
"2c56731ac2f7a7e7f11518fc7722a166b02438924ca9d8b4d1113" | |||||
"47b81d0717571846de67ad3d913a8fdf9d8f3f73161a4c48ae81c" | |||||
"b183b214765feb86e255ce") |