1515WARNING: This module does not mlock() secrets; your private keys may end up on
1616disk in swap! Use with caution!
1717"""
18-
1918import ctypes
2019import ctypes .util
2120import hashlib
2221import sys
22+ import bitcoin
23+ import bitcoin .signature
24+
25+ _bchr = chr
26+ _bord = ord
27+ if sys .version > '3' :
28+ _bchr = lambda x : bytes ([x ])
29+ _bord = lambda x : x
30+ from io import BytesIO as BytesIO
31+ else :
32+ from cStringIO import StringIO as BytesIO
2333
2434import bitcoin .core .script
2535
@@ -128,6 +138,53 @@ def sign(self, hash):
128138 else :
129139 return self .signature_to_low_s (mb_sig .raw [:sig_size0 .value ])
130140
141+ def sign_compact (self , hash ):
142+ if not isinstance (hash , bytes ):
143+ raise TypeError ('Hash must be bytes instance; got %r' % hash .__class__ )
144+ if len (hash ) != 32 :
145+ raise ValueError ('Hash must be exactly 32 bytes long' )
146+
147+ sig_size0 = ctypes .c_uint32 ()
148+ sig_size0 .value = _ssl .ECDSA_size (self .k )
149+ mb_sig = ctypes .create_string_buffer (sig_size0 .value )
150+ result = _ssl .ECDSA_sign (0 , hash , len (hash ), mb_sig , ctypes .byref (sig_size0 ), self .k )
151+ assert 1 == result
152+
153+ if bitcoin .core .script .IsLowDERSignature (mb_sig .raw [:sig_size0 .value ]):
154+ sig = mb_sig .raw [:sig_size0 .value ]
155+ else :
156+ sig = self .signature_to_low_s (mb_sig .raw [:sig_size0 .value ])
157+
158+ sig = bitcoin .signature .DERSignature .deserialize (sig )
159+
160+ r_val = sig .r
161+ s_val = sig .s
162+
163+ # assert that the r and s are less than 32 long, excluding leading 0s
164+ assert len (r_val ) <= 32 or r_val [0 :- 32 ] == b'\x00 '
165+ assert len (s_val ) <= 32 or s_val [0 :- 32 ] == b'\x00 '
166+
167+ # ensure r and s are always 32 chars long by 0padding
168+ r_val = ((b'\x00 ' * 32 ) + r_val )[- 32 :]
169+ s_val = ((b'\x00 ' * 32 ) + s_val )[- 32 :]
170+
171+ # tmp pubkey of self, but always compressed
172+ pubkey = CECKey ()
173+ pubkey .set_pubkey (self .get_pubkey ())
174+ pubkey .set_compressed (True )
175+
176+ # bitcoin core does <4, but I've seen other places do <2 and I've never seen a i > 1 so far
177+ for i in range (0 , 4 ):
178+ cec_key = CECKey ()
179+ cec_key .set_compressed (True )
180+
181+ result = cec_key .recover (r_val , s_val , hash , len (hash ), i , 1 )
182+ if result == 1 :
183+ if cec_key .get_pubkey () == pubkey .get_pubkey ():
184+ return r_val + s_val , i
185+
186+ raise ValueError
187+
131188 def signature_to_low_s (self , sig ):
132189 der_sig = ECDSA_SIG_st ()
133190 _ssl .d2i_ECDSA_SIG (ctypes .byref (ctypes .pointer (der_sig )), ctypes .byref (ctypes .c_char_p (sig )), len (sig ))
@@ -185,6 +242,106 @@ def set_compressed(self, compressed):
185242 form = self .POINT_CONVERSION_UNCOMPRESSED
186243 _ssl .EC_KEY_set_conv_form (self .k , form )
187244
245+ def recover (self , sigR , sigS , msg , msglen , recid , check ):
246+ """
247+ Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields
248+ recid selects which key is recovered
249+ if check is non-zero, additional checks are performed
250+ """
251+ i = int (recid / 2 )
252+
253+ r = None
254+ s = None
255+ ctx = None
256+ R = None
257+ O = None
258+ Q = None
259+
260+ assert len (sigR ) == 32 , len (sigR )
261+ assert len (sigS ) == 32 , len (sigS )
262+
263+ try :
264+ r = _ssl .BN_bin2bn (bytes (sigR ), len (sigR ), _ssl .BN_new ())
265+ s = _ssl .BN_bin2bn (bytes ( sigS ), len (sigS ), _ssl .BN_new ())
266+
267+ group = _ssl .EC_KEY_get0_group (self .k )
268+ ctx = _ssl .BN_CTX_new ()
269+ order = _ssl .BN_CTX_get (ctx )
270+ ctx = _ssl .BN_CTX_new ()
271+
272+ if not _ssl .EC_GROUP_get_order (group , order , ctx ):
273+ return - 2
274+
275+ x = _ssl .BN_CTX_get (ctx )
276+ if not _ssl .BN_copy (x , order ):
277+ return - 1
278+ if not _ssl .BN_mul_word (x , i ):
279+ return - 1
280+ if not _ssl .BN_add (x , x , r ):
281+ return - 1
282+
283+ field = _ssl .BN_CTX_get (ctx )
284+ if not _ssl .EC_GROUP_get_curve_GFp (group , field , None , None , ctx ):
285+ return - 2
286+
287+ if _ssl .BN_cmp (x , field ) >= 0 :
288+ return 0
289+
290+ R = _ssl .EC_POINT_new (group )
291+ if R is None :
292+ return - 2
293+ if not _ssl .EC_POINT_set_compressed_coordinates_GFp (group , R , x , recid % 2 , ctx ):
294+ return 0
295+
296+ if check :
297+ O = _ssl .EC_POINT_new (group )
298+ if O is None :
299+ return - 2
300+ if not _ssl .EC_POINT_mul (group , O , None , R , order , ctx ):
301+ return - 2
302+ if not _ssl .EC_POINT_is_at_infinity (group , O ):
303+ return 0
304+
305+ Q = _ssl .EC_POINT_new (group )
306+ if Q is None :
307+ return - 2
308+
309+ n = _ssl .EC_GROUP_get_degree (group )
310+ e = _ssl .BN_CTX_get (ctx )
311+ if not _ssl .BN_bin2bn (msg , msglen , e ):
312+ return - 1
313+
314+ if 8 * msglen > n :
315+ _ssl .BN_rshift (e , e , 8 - (n & 7 ))
316+
317+ zero = _ssl .BN_CTX_get (ctx )
318+ # if not _ssl.BN_zero(zero):
319+ # return -1
320+ if not _ssl .BN_mod_sub (e , zero , e , order , ctx ):
321+ return - 1
322+ rr = _ssl .BN_CTX_get (ctx )
323+ if not _ssl .BN_mod_inverse (rr , r , order , ctx ):
324+ return - 1
325+ sor = _ssl .BN_CTX_get (ctx )
326+ if not _ssl .BN_mod_mul (sor , s , rr , order , ctx ):
327+ return - 1
328+ eor = _ssl .BN_CTX_get (ctx )
329+ if not _ssl .BN_mod_mul (eor , e , rr , order , ctx ):
330+ return - 1
331+ if not _ssl .EC_POINT_mul (group , Q , eor , R , sor , ctx ):
332+ return - 2
333+
334+ if not _ssl .EC_KEY_set_public_key (self .k , Q ):
335+ return - 2
336+
337+ return 1
338+ finally :
339+ if r : _ssl .BN_free (r )
340+ if s : _ssl .BN_free (s )
341+ if ctx : _ssl .BN_CTX_free (ctx )
342+ if R : _ssl .EC_POINT_free (R )
343+ if O : _ssl .EC_POINT_free (O )
344+ if Q : _ssl .EC_POINT_free (Q )
188345
189346class CPubKey (bytes ):
190347 """An encapsulated public key
@@ -204,6 +361,30 @@ def __new__(cls, buf, _cec_key=None):
204361 self .is_fullyvalid = _cec_key .set_pubkey (self ) != 0
205362 return self
206363
364+ @classmethod
365+ def recover_compact (cls , hash , sig ):
366+ """Recover a public key from a compact signature."""
367+ if len (sig ) != 65 :
368+ raise ValueError ("Signature should be 65 characters, not [%d]" % (len (sig ), ))
369+
370+ recid = (_bord (sig [0 ]) - 27 ) & 3
371+ compressed = (_bord (sig [0 ]) - 27 ) & 4 != 0
372+
373+ cec_key = CECKey ()
374+ cec_key .set_compressed (compressed )
375+
376+ sigR = sig [1 :33 ]
377+ sigS = sig [33 :65 ]
378+
379+ result = cec_key .recover (sigR , sigS , hash , len (hash ), recid , 0 )
380+
381+ if result < 1 :
382+ return False
383+
384+ pubkey = cec_key .get_pubkey ()
385+
386+ return CPubKey (pubkey , _cec_key = cec_key )
387+
207388 @property
208389 def is_valid (self ):
209390 return len (self ) > 0
0 commit comments