Skip to content

Commit 2326b21

Browse files
committed
Merge #185: add ability to use libsecp256k1 for signing (supports RFC6979)
560513f call secp256k1_context_randomize() on _libsecp256k1_context (Dmitry Petukhov) cdcfd3b add ability to use libsecp256k1 for signing (supports RFC6979) (Dmitry Petukhov) Pull request description: This is done in a straightforward way, as we added specific functions to check for presence of libsecp256k1 and to enable using it for signing. We or someone else might devise a universal way to plug in different signing libraries, later. This can be used today by someone who need RFC6979 signing, and also may be useful when implementing pluggable signing library scheme. Two new functions are added to core/key.py: is_libsec256k1_available() -- to check that libsec256k1 is present in the system use_libsecp256k1_for_signing(do_use) - if do_use is True, sign() function will use libsec256k1 (it will check _libsecp256k1_enable_signing global flag) if do_use is False, _libsecp256k1_enable_signing flag is set to False, and openssl will be used within sign() function. Top commit has no ACKs. Tree-SHA512: 13a601bd7727ffcd395b10ca9d1c5743248cf429923528ac4059ef4fc880ee67e68f4cd66a06776fffe13a4ceb45b2c42a73f9d96f5080e395fd9b26a56ceccb
2 parents f8b1ebc + 560513f commit 2326b21

File tree

2 files changed

+134
-1
lines changed

2 files changed

+134
-1
lines changed

bitcoin/core/key.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import ctypes.util
2020
import hashlib
2121
import sys
22+
from os import urandom
2223
import bitcoin
2324
import bitcoin.signature
2425

@@ -32,6 +33,12 @@
3233

3334
_ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('ssl') or 'libeay32')
3435

36+
_libsecp256k1_path = ctypes.util.find_library('secp256k1')
37+
_libsecp256k1_enable_signing = False
38+
_libsecp256k1_context = None
39+
_libsecp256k1 = None
40+
41+
3542
class OpenSSLException(EnvironmentError):
3643
pass
3744

@@ -185,12 +192,56 @@ def _check_res_void_p(val, func, args): # pylint: disable=unused-argument
185192
_ssl.o2i_ECPublicKey.restype = ctypes.c_void_p
186193
_ssl.o2i_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long]
187194

195+
_ssl.BN_num_bits.restype = ctypes.c_int
196+
_ssl.BN_num_bits.argtypes = [ctypes.c_void_p]
197+
_ssl.EC_KEY_get0_private_key.restype = ctypes.c_void_p
198+
188199
# this specifies the curve used with ECDSA.
189200
_NID_secp256k1 = 714 # from openssl/obj_mac.h
190201

191202
# test that OpenSSL supports secp256k1
192203
_ssl.EC_KEY_new_by_curve_name(_NID_secp256k1)
193204

205+
SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0)
206+
SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9)
207+
SECP256K1_CONTEXT_SIGN = \
208+
(SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
209+
210+
211+
def is_libsec256k1_available():
212+
return _libsecp256k1_path is not None
213+
214+
215+
def use_libsecp256k1_for_signing(do_use):
216+
global _libsecp256k1
217+
global _libsecp256k1_context
218+
global _libsecp256k1_enable_signing
219+
220+
if not do_use:
221+
_libsecp256k1_enable_signing = False
222+
return
223+
224+
if not is_libsec256k1_available():
225+
raise ImportError("unable to locate libsecp256k1")
226+
227+
if _libsecp256k1_context is None:
228+
_libsecp256k1 = ctypes.cdll.LoadLibrary(_libsecp256k1_path)
229+
_libsecp256k1.secp256k1_context_create.restype = ctypes.c_void_p
230+
_libsecp256k1.secp256k1_context_create.errcheck = _check_res_void_p
231+
_libsecp256k1.secp256k1_context_randomize.restype = ctypes.c_int
232+
_libsecp256k1.secp256k1_context_randomize.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
233+
_libsecp256k1_context = _libsecp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN)
234+
assert(_libsecp256k1_context is not None)
235+
seed = urandom(32)
236+
result = _libsecp256k1.secp256k1_context_randomize(_libsecp256k1_context, seed)
237+
assert 1 == result
238+
239+
240+
241+
_libsecp256k1_enable_signing = True
242+
243+
244+
194245
# From openssl/ecdsa.h
195246
class ECDSA_SIG_st(ctypes.Structure):
196247
_fields_ = [("r", ctypes.c_void_p),
@@ -258,12 +309,39 @@ def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()):
258309
r = self.get_raw_ecdh_key(other_pubkey)
259310
return kdf(r)
260311

312+
def get_raw_privkey(self):
313+
bn = _ssl.EC_KEY_get0_private_key(self.k)
314+
bn = ctypes.c_void_p(bn)
315+
size = (_ssl.BN_num_bits(bn) + 7) / 8
316+
mb = ctypes.create_string_buffer(int(size))
317+
_ssl.BN_bn2bin(bn, mb)
318+
return mb.raw.rjust(32, b'\x00')
319+
320+
def _sign_with_libsecp256k1(self, hash):
321+
raw_sig = ctypes.create_string_buffer(64)
322+
result = _libsecp256k1.secp256k1_ecdsa_sign(
323+
_libsecp256k1_context, raw_sig, hash, self.get_raw_privkey(), None, None)
324+
assert 1 == result
325+
sig_size0 = ctypes.c_size_t()
326+
sig_size0.value = 75
327+
mb_sig = ctypes.create_string_buffer(sig_size0.value)
328+
result = _libsecp256k1.secp256k1_ecdsa_signature_serialize_der(
329+
_libsecp256k1_context, mb_sig, ctypes.byref(sig_size0), raw_sig)
330+
assert 1 == result
331+
# libsecp256k1 creates signatures already in lower-S form, no further
332+
# conversion needed.
333+
return mb_sig.raw[:sig_size0.value]
334+
335+
261336
def sign(self, hash): # pylint: disable=redefined-builtin
262337
if not isinstance(hash, bytes):
263338
raise TypeError('Hash must be bytes instance; got %r' % hash.__class__)
264339
if len(hash) != 32:
265340
raise ValueError('Hash must be exactly 32 bytes long')
266341

342+
if _libsecp256k1_enable_signing:
343+
return self._sign_with_libsecp256k1(hash)
344+
267345
sig_size0 = ctypes.c_uint32()
268346
sig_size0.value = _ssl.ECDSA_size(self.k)
269347
mb_sig = ctypes.create_string_buffer(sig_size0.value)

bitcoin/tests/test_wallet.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111

1212
from __future__ import absolute_import, division, print_function, unicode_literals
1313

14+
import hashlib
1415
import unittest
1516

1617
from bitcoin.core import b2x, x
1718
from bitcoin.core.script import CScript, IsLowDERSignature
18-
from bitcoin.core.key import CPubKey
19+
from bitcoin.core.key import CPubKey, is_libsec256k1_available, use_libsecp256k1_for_signing
1920
from bitcoin.wallet import *
2021

2122
class Test_CBitcoinAddress(unittest.TestCase):
@@ -279,3 +280,57 @@ def test_sign_invalid_hash(self):
279280
hash = b'\x00' * 32
280281
with self.assertRaises(ValueError):
281282
sig = key.sign(hash[0:-2])
283+
284+
285+
class Test_RFC6979(unittest.TestCase):
286+
def test(self):
287+
if not is_libsec256k1_available():
288+
return
289+
290+
use_libsecp256k1_for_signing(True)
291+
292+
# Test Vectors for RFC 6979 ECDSA, secp256k1, SHA-256
293+
# (private key, message, expected k, expected signature)
294+
test_vectors = [
295+
(0x1, "Satoshi Nakamoto", 0x8F8A276C19F4149656B280621E358CCE24F5F52542772691EE69063B74F15D15, "934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d82442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"),
296+
(0x1, "All those moments will be lost in time, like tears in rain. Time to die...", 0x38AA22D72376B4DBC472E06C3BA403EE0A394DA63FC58D88686C611ABA98D6B3, "8600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"),
297+
(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140, "Satoshi Nakamoto", 0x33A19B60E25FB6F4435AF53A3D42D493644827367E6453928554F43E49AA6F90, "fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d06b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"),
298+
(0xf8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181, "Alan Turing", 0x525A82B70E67874398067543FD84C83D30C175FDC45FDEEE082FE13B1D7CFDF1, "7063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c58dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"),
299+
(0xe91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2, "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", 0x1F4B84C23A86A221D233F2521BE018D9318639D5B8BBD6374A8A59232D16AD3D, "b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6")
300+
]
301+
for vector in test_vectors:
302+
secret = CBitcoinSecret.from_secret_bytes(x('{:064x}'.format(vector[0])))
303+
encoded_sig = secret.sign(hashlib.sha256(vector[1].encode('utf8')).digest())
304+
305+
assert(encoded_sig[0] == 0x30)
306+
assert(encoded_sig[1] == len(encoded_sig)-2)
307+
assert(encoded_sig[2] == 0x02)
308+
309+
rlen = encoded_sig[3]
310+
rpos = 4
311+
assert(rlen in (32, 33))
312+
313+
if rlen == 33:
314+
assert(encoded_sig[rpos] == 0)
315+
rpos += 1
316+
rlen -= 1
317+
318+
rval = encoded_sig[rpos:rpos+rlen]
319+
spos = rpos+rlen
320+
assert(encoded_sig[spos] == 0x02)
321+
322+
spos += 1
323+
slen = encoded_sig[spos]
324+
assert(slen in (32, 33))
325+
326+
spos += 1
327+
if slen == 33:
328+
assert(encoded_sig[spos] == 0)
329+
spos += 1
330+
slen -= 1
331+
332+
sval = encoded_sig[spos:spos+slen]
333+
sig = b2x(rval + sval)
334+
assert(str(sig) == vector[3])
335+
336+
use_libsecp256k1_for_signing(False)

0 commit comments

Comments
 (0)