diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -1,4 +1,6 @@ +#!/usr/bin/env python3 # Copyright (c) 2011 Sam Rushing +# Copyright 2019 The Bitcoin Developers """ECC secp256k1 OpenSSL wrapper. WARNING: This module does not mlock() secrets; your private keys may end up on @@ -10,6 +12,7 @@ import ctypes import ctypes.util import hashlib +import hmac import sys ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or 'libeay32') @@ -75,10 +78,19 @@ 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] +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. NID_secp256k1 = 714 # from openssl/obj_mac.h -SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 +SECP256K1_FIELDSIZE = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f +SECP256K1_ORDER = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 # Thx to Sam Devlin for the ctypes magic 64-bit fix. @@ -95,6 +107,71 @@ 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(): """Wrapper around OpenSSL's EC_KEY""" @@ -195,6 +272,51 @@ 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 + 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): """Verify a DER signature""" return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1 @@ -246,3 +368,24 @@ return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) else: 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")