Bitcoin Forum

Bitcoin => Development & Technical Discussion => Topic started by: etotheipi on July 12, 2011, 03:12:57 PM



Title: ECDSA in python
Post by: etotheipi on July 12, 2011, 03:12:57 PM
As I work on developing my python backend for a BTC client, and finding it difficult to find a decent ECDSA implementation in python that would be sufficient for my purposes.  I have looked at the following:

M2Crypto -- looks like it's the best implementation in general, but I can't get the binary strings of the keys and signatures out of it.  I can only save them to PEM files. 
python-nss -- doesn't look to be cross-platform
pycrypto -- doesn't have ECDSA
python-ecdsa(*) -- temporary winner! -- https://github.com/warner/python-ecdsa

There's a lot of good info on cryptography packages, here:  http://mikeivanov.com/pc/python-crypto.pdf (http://mikeivanov.com/pc/python-crypto.pdf)

M2Crypto would be my first choice, but  as far as I can tell, there's no way to get the binary keys directly from the library.  You can generate a key, generate signatures, and save things to PEM files, but no access to the keys themselves.  I am not fond of writing the keys to HDD and then reverse-engineering the PEM file format to get the key out.  Maybe if you could write it to a RAM file... but still seems like a hack.  (maybe someone has experience with this?)

python-ecdsa seems to be the best choice, as it doesn't appear to have any limitations, or any non-standard dependencies beyond it's own files, and should work on any platform.  It looks slow, but I'm not convinced that's a problem unless you're going to verify the entire block-chain.

Anyone know of any other python library, or technique for getting python to do the DSA signing/verification needed for Bitcoin? 


Title: Re: ECDSA in python
Post by: etotheipi on July 12, 2011, 04:31:32 PM
Wow, that is exactly what I was looking for!  Everything you need in a single, dependency-less python file!  I will surely donate to Lis!

P.S. - I did a search for "python ecdsa" in the forum search bar.  I only got 3 hits (probably 4 now, with this post).  I guess I needed to dig a little deeper.

-Eto


Title: Re: ECDSA in python
Post by: etotheipi on July 13, 2011, 12:38:02 AM
Okay, so the ECDSA library by Lis is fantastic, except I'm missing one critical piece of the puzzle -- I cannot for my life figure out how signatures are encoded.  A signature consists of two integers, (r,s), DER-encoded.  I'm digging through the very-dry DER encoding document, and I can't figure it out.  Most people use an SSL library for DER encoding/decoding, but I am trying not to have such dependencies in my client.

Is anyone familiar enough with the DER encoding to know how to break apart a binary signature into the two integers r and s?



Title: Re: ECDSA in python
Post by: etotheipi on July 13, 2011, 01:44:44 PM
Update:  After enough hex-diving and specification documents, I figured out how they are encoded.  I'm sure it would be irresponsible to blindly decode a DER chunk without checking byte codes/flags, but I imagine this will never change in Bitcoin, so I am foregoing a proper DER library.

Byte Listing:
--------------
1:  \x30
2:  1-byte length of DER-encoded (r,s) pair (assume 68)

3:  \x02
4:  1-byte length of r-value  (assume 32)
5-36:  integer value of r

37:  \x02
38:  1-byte length of s-value (assume 32)
39-70: integer value of s

71:  \x01
--------------

The values of r and s aren't always 32 bits so you still need to read all the length bytes.  The \x30 and \x02 bytes are bit strings of flags related to DER encoding scheme.


Title: Re: ECDSA in python
Post by: titeuf_87 on July 13, 2011, 04:11:47 PM
This thread (http://forum.bitcoin.org/index.php?topic=18051.0) contains some sample code for M2Crypto and in a later post a code sample about how DER encoding works.

Hope this helps!


Title: Re: ECDSA in python
Post by: Joric on July 13, 2011, 10:04:11 PM
I've spend some time to get i2d_ECPrivateKey/i2o_ECPublicKey exports working (see https://github.com/joric/pywallet)
Relevant code (it uses only ctypes):
Code:
# Had to import i2d_ECPrivateKey/i2o_ECPublicKey from dynamic libs.
# Looks like PyCrypto/M2Crypto/pyOpenSSL modules do not support them.

dlls = list()
if 'win' in sys.platform:
    for d in ('libeay32.dll', 'libssl32.dll', 'ssleay32.dll'):
        try:
            dlls.append( cdll.LoadLibrary(d) )
        except:
            pass
else:
    dlls.append( cdll.LoadLibrary('libssl.so') )
           
class BIGNUM_Struct (Structure):
    _fields_ = [("d",c_void_p),("top",c_int),("dmax",c_int),("neg",c_int),("flags",c_int)]
               
class BN_CTX_Struct (Structure):
    _fields_ = [ ("_", c_byte) ]

BIGNUM = POINTER( BIGNUM_Struct )
BN_CTX = POINTER( BN_CTX_Struct )

def load_func( name, args, returns = c_int):
    d = sys.modules[ __name__ ].__dict__
    f = None
   
    for dll in dlls:
        try:
            f = getattr(dll, name)
            f.argtypes = args
            f.restype  = returns
            d[ name ] = f
            return
        except:
            pass
    raise ImportError('Unable to load required functions from SSL dlls')
   
load_func( 'BN_new', [], BIGNUM )
load_func( 'BN_CTX_new', [], BN_CTX )
load_func( 'BN_CTX_free', [BN_CTX], None   )
load_func( 'BN_num_bits', [BIGNUM], c_int )
load_func( 'BN_bn2bin',  [BIGNUM, c_char_p] )
load_func( 'BN_bin2bn',  [c_char_p, c_int, BIGNUM], BIGNUM )
load_func( 'EC_KEY_new_by_curve_name', [c_int], c_void_p )
load_func( 'EC_KEY_get0_group', [c_void_p], c_void_p)
load_func( 'EC_KEY_get0_private_key', [c_void_p], BIGNUM)
load_func( 'EC_POINT_new', [c_void_p], c_void_p)
load_func( 'EC_POINT_free', [c_void_p])
load_func( 'EC_POINT_mul', [c_void_p, c_void_p, BIGNUM, c_void_p, BIGNUM, BN_CTX], c_int)
load_func( 'EC_KEY_set_private_key', [c_void_p, BIGNUM], c_void_p)
load_func( 'EC_KEY_set_public_key', [c_void_p, c_void_p], c_void_p)
load_func( 'i2d_ECPrivateKey', [ c_void_p, POINTER(POINTER(c_char))], c_int )
load_func( 'i2o_ECPublicKey', [ c_void_p, POINTER(POINTER(c_char))], c_int )

def BN_num_bytes(a):
    return ((BN_num_bits(a)+7)/8)

NID_secp256k1 = 714

pkey = 0

def EC_KEY_regenerate_key(eckey, priv_key):
group = EC_KEY_get0_group(eckey)
ctx = BN_CTX_new()
pub_key = EC_POINT_new(group)
EC_POINT_mul(group, pub_key, priv_key, None, None, ctx)
EC_KEY_set_private_key(eckey, priv_key)
EC_KEY_set_public_key(eckey, pub_key)
EC_POINT_free(pub_key)
BN_CTX_free(ctx)

def GetSecret(pkey):
bn = EC_KEY_get0_private_key(pkey)
nSize = BN_num_bytes(bn)
b = create_string_buffer(nSize)
BN_bn2bin(bn, b)
return b.raw

def GetPrivKey(pkey):
nSize = i2d_ECPrivateKey(pkey, None)
p = create_string_buffer(nSize)
  i2d_ECPrivateKey(pkey, byref(cast(p, POINTER(c_char))))
return p.raw

def GetPubKey(pkey):
nSize = i2o_ECPublicKey(pkey, None)
p = create_string_buffer(nSize)
i2o_ECPublicKey(pkey, byref(cast(p, POINTER(c_char))))
return p.raw

def Hash(data):
s1 = hashlib.sha256()
s1.update(data)
h1 = s1.digest()
s2 = hashlib.sha256()
s2.update(h1)
h2 = s2.digest()
return h2

def EncodeBase58Check(vchIn):
hash = Hash(vchIn)
return b58encode(vchIn + hash[0:4])

def DecodeBase58Check(psz):
vchRet = b58decode(psz, None)
key = vchRet[0:-4]
csum = vchRet[-4:]
hash = Hash(key)
cs32 = hash[0:4]
if cs32 != csum:
return None
else:
return key

def SecretToASecret(privkey):
vchSecret = privkey[9:9+32]
# add 1-byte version number
vchIn = "\x80" + vchSecret
return EncodeBase58Check(vchIn)

def ASecretToSecret(key):
vch = DecodeBase58Check(key)
if vch:
return vch[1:]
else:
return False

def importprivkey(db, key):
vchSecret = ASecretToSecret(key)

if not vchSecret:
return False

pkey = EC_KEY_new_by_curve_name(NID_secp256k1)
bn = BN_bin2bn(vchSecret, 32, BN_new())
EC_KEY_regenerate_key(pkey, bn)

secret = GetSecret(pkey)
private_key = GetPrivKey(pkey)
public_key = GetPubKey(pkey)
addr = public_key_to_bc_address(public_key)

print "Address: %s" % addr
print "Privkey: %s" % SecretToASecret(private_key)

update_wallet(db, 'key', { 'public_key' : public_key, 'private_key' : private_key })
update_wallet(db, 'name', { 'hash' : addr, 'name' : '' })

return True


Title: Re: ECDSA in python
Post by: TierNolan on July 13, 2011, 10:25:59 PM
Update:  After enough hex-diving and specification documents, I figured out how they are encoded.  I'm sure it would be irresponsible to blindly decode a DER chunk without checking byte codes/flags, but I imagine this will never change in Bitcoin, so I am foregoing a proper DER library.

Byte Listing:
--------------
1:  \x30
2:  1-byte length of DER-encoded (r,s) pair (assume 68)

3:  \x02
4:  1-byte length of r-value  (assume 32)
5-36:  integer value of r

37:  \x02
38:  1-byte length of s-value (assume 32)
39-70: integer value of s

71:  \x01
--------------

The values of r and s aren't always 32 bits so you still need to read all the length bytes.  The \x30 and \x02 bytes are bit strings of flags related to DER encoding scheme.

R and S are 33 bytes if it would cause a negative number.

02_20_8000.....

is a 32 byte integer with the MSB = 1.  This is negative, so they change it to

02_21_008000.....

This is a 33 byte integer, but with leading zeros, so it is considered positive.


Title: Re: ECDSA in python
Post by: Joric on July 24, 2011, 03:11:16 AM
Made some investigations about 279-byte DER key.

There is an embedded DER encoder/decoder in the python-ecdsa library (https://github.com/warner/python-ecdsa).

Example i2d_ECPrivateKey output (from http://bitcointools.appspot.com):

Address: 1AJ3vE2NNYW2Jzv3fLwyjKF1LYbZ65Ez64
Private key: 5JMhGPWc3pkdgPd9jqVZkRtEp3QB3Ze8ihv62TmmvzABmkNzBHw

i2d_ECPrivateKey:
Code:
30820113020101042047510706d76bc74a5d57bdcffc68c9bbbc2d496bef87c9
1de7f616129ac62b5fa081a53081a2020101302c06072a8648ce3d0101022100
fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f3006040100040107044104
79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817984
83ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b802
2100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101a
144034200046211d9b7836892c8eef49c4d0cad7797815eff95108e1d30745c0
3577596c9c00d2cb1ab27c7f95c28771278f89b7ff40da49fe9b4ee834a3f6a88
324db837d8

Javascript ASN1 decoder (http://lapo.it/asn1js/) decodes it to:

Code:
SEQUENCE(4 elem)
    INTEGER1
    OCTET STRING(32 byte) A0DC65FFCA799873CBEA0AC274015B9526505DAA..
    [0]    (1)
        SEQUENCE(6 elem)
            INTEGER 1
            SEQUENCE(2 elem)
                OBJECT IDENTIFIER 1.2.840.10045.1.1
                INTEGER (256 bit)
        SEQUENCE(2 elem)
            OCTET STRING(1 byte) 00
            OCTET STRING(1 byte) 07
        OCTET STRING(65 byte) 0479BE667EF9DCBBAC55A06295CE870B07029BFC..
        INTEGER(256 bit)
        INTEGER1
    [1]    (1)
    BIT STRING(520 bit)

I wrote python-ecdsa SigningKey method that generates the same output as i2d_ECPrivateKey (279 bytes):

Code:
def i2d(self):

    _p = self.curve.curve.p()
    _r = self.curve.generator.order() # self.curve.order
    _Gx = self.curve.generator.x()
    _Gy = self.curve.generator.y()

    oid2 = (1, 2, 840, 10045, 1, 1) # ecdsa-with-SHA1 + id-prime-Field
    encoded_oid2 = der.encode_oid(*oid2)

    encoded_gxgy = "\x04" + ("%64x" % _Gx).decode('hex') + ("%64x" % _Gy).decode('hex')

    param_sequence = der.encode_sequence (
        der.encode_integer(1),
            der.encode_sequence (
            encoded_oid2,
            der.encode_integer(_p),
        ),
        der.encode_sequence (
            der.encode_octet_string("\x00"),
            der.encode_octet_string("\x07"),
        ),
        der.encode_octet_string(encoded_gxgy),
        der.encode_integer(_r),
        der.encode_integer(1),
    );

    encoded_vk = "\x00\x04" + self.get_verifying_key().to_string()

    return der.encode_sequence (
        der.encode_integer(1),
        der.encode_octet_string(self.to_string()),
        der.encode_constructed(0, param_sequence),
        der.encode_constructed(1, der.encode_bitstring(encoded_vk)),
    )

Also see https://github.com/joric/brutus (ecdsa_pure.py and ecdsa_ssl.py, I've updated them recently).


Title: Re: ECDSA in python
Post by: Telariust on August 10, 2019, 09:03:26 PM
sry for necro-up, but this topic is consistent
( search tag: python, btc, bitcoin , ecc , ecdsa , secp256k1, lib , library, compare )

Comparing bitcoin libraries, generation speed of rand privkey to pubkey
(maybe someone will need)

Python37
Code:
C:\Python37>python.exe _benchmark_bitcoin_lib.py
[timeit] 10 loops, 1.14888 sec                    ecdsa
[timeit] 10 loops, 0.613698 sec                   pycoin
[timeit] 10 loops, 0.0630259 sec                  starkbank-ecdsa
[timeit] 10 loops, 0.0613364 sec                  pybitcointools
[timeit] 10 loops, 0.0381225 sec                  fastecdsa
[timeit] 10 loops, 0.0427408 sec                  python-bitcoinlib
[timeit] 10 loops, 0.0214886 sec                  openssl
[timeit] 10 loops, 0.000839192 sec                coincurve

Python27
Code:
C:\Python27>python.exe _benchmark_bitcoin_lib.py
[timeit] 10 loops, 1.33738 sec                    ecdsa
[timeit] 10 loops, 0.660795 sec                   pycoin
[timeit] 10 loops, 0.00585777 sec                 starkbank-ecdsa
[timeit] 10 loops, 0.0984026 sec                  pybitcointools
[timeit] 10 loops, 0.0389676 sec                  python-bitcoinlib
[timeit] 10 loops, 0.0010109 sec                  coincurve


in short: 
 - coincurve lib the undisputed leader (https://github.com/ofek/coincurve)
 - starkbank anomal x10 faster under python2
 - pycoin have problem mode openssl (i dont only for me local or global)


for python3
Code:
#!/usr/bin/env python3
#####################
import timeit

rounds = 10

import random
randrange = random.SystemRandom().randrange


#####################
# ecdsa
# (use python)
# python -m pip install  ecdsa==0.13

def test_ecdsa(test_name = 'ecdsa', rounds = 10):
loops = rounds
try:
import ecdsa
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
#secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

from ecdsa.ecdsa import generator_secp256k1, Public_key, Private_key
#from ecdsa.util import string_to_number, number_to_string
g = generator_secp256k1
pubkey = Public_key( g, g * secret ).point
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from ecdsa.ecdsa import generator_secp256k1, Public_key, Private_key; g=generator_secp256k1; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'Public_key(g,g*random.randint(1,n)).point'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# starkbank-ecdsa
# (use python with quick jacobian multiply)
# python -m pip install  starkbank-ecdsa==0.1.4

def test_starkbank_ecdsa(test_name = 'starkbank-ecdsa', rounds = 10):
loops = rounds
try:
import ellipticcurve
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

from ellipticcurve.privateKey import PrivateKey
pubkey = PrivateKey.fromString(secret).publicKey().toString()
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from ellipticcurve.privateKey import PrivateKey; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret = random.randint(1,n); secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big"); PrivateKey.fromString(secret).publicKey().toString();'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# bitcoin (pybitcointools)
# (use python with quick jacobian multiply)
# (Author: Vitalik Buterin)
# python -m pip install  bitcoin==1.1.42
# python -m pip install  pybitcointools==1.1.15

def test_pybitcointools(test_name = 'pybitcointools', rounds = 10):
loops = rounds
try:
import pybitcointools
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)


n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
#secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

from pybitcointools import fast_multiply, G
pubkey = fast_multiply(G, secret)
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from pybitcointools import fast_multiply, G; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'fast_multiply(G, random.randint(1,n))'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# pycoin
# (use python, use openssl(fail))
# python -m pip install  pycoin==0.80

def test_pycoin(test_name = 'pycoin', rounds = 10):
loops = rounds
#if os.getenv("PYCOIN_NATIVE") != "openssl":
try:
import pycoin
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
#secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

#from pycoin.key import Key
#pubkey = Key(secret_exponent=secret).public_pair()
#print('[pubkey] {0}'.format(pubkey));exit(0);

from pycoin.ecdsa import public_pair_for_secret_exponent, generator_secp256k1
pubkey = public_pair_for_secret_exponent(generator_secp256k1, secret)
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from pycoin.ecdsa import public_pair_for_secret_exponent, generator_secp256k1; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'public_pair_for_secret_exponent(generator_secp256k1, random.randint(1,n))'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# bit
# (use coincurve)
# python -m pip install  bit==0.4.3

#####################
# pybitcoin
# (use pybitcointools + use ecdsa)
# python -m pip install  pybitcoin==0.9.9

#####################
# python-bitcoin
# (use ecdsa)
# python -m pip install  python-bitcoin==0.0.10

#####################
# bitcoinlib
# (use ecdsa)
# python -m pip install  bitcoinlib==0.4.4

#####################
# python-bitcoinlib
# (use openssl)
# python -m pip install  python-bitcoinlib==0.10.1
# (inst path to PythonXX/Lib/site-packages/bitcoin , its conflict with pybitcointools)

def test_python_bitcoinlib(test_name = 'python-bitcoinlib', rounds = 10):
loops = rounds
try:
import bitcoin
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

from bitcoin.wallet import CKey
pubkey = CKey(secret,compressed=False).pub
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from bitcoin.wallet import CKey; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret = random.randint(1,n);secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big");CKey(secret,compressed=False).pub'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )


from bitcoin.core.key import CECKey, CPubKey
pubkey = CECKey()
pubkey.set_secretbytes(secret)
pubkey.set_compressed(compressed=False)
pubkey = CPubKey(pubkey.get_pubkey(), pubkey)
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from bitcoin.core.key import CECKey, CPubKey; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret = random.randint(1,n);secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big");pubkey = CECKey();pubkey.set_secretbytes(secret);pubkey = CPubKey(pubkey.get_pubkey(), pubkey);'

#print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# py_ecc
# (use python with quick jacobian multiply)
# python -m pip install  py_ecc==1.4.7
# (bitcoin_easy)

#####################
# ecc-0.0.1





#####################
# bitcoin-utils
# (use ecdsa)
# python -m pip install  bitcoin-utils==0.2.2

#####################
# bitcoin_tools
# (use python)
# python -m pip install  bitcoin_tools==0.0.13

#####################
# python_bitcoin_tools
# (use ecdsa)
# python -m pip install  python_bitcoin_tools==0.2.3



#####################
#
# (use )
# python -m pip install 

#exit(0);



#####################
# openssl

def test_openssl(test_name = 'openssl', rounds = 10):
loops = rounds
try:
import ctypes
ssl_library = ctypes.cdll.LoadLibrary("libeay32.dll")
#ssl_library = ctypes.cdll.LoadLibrary(r"C:\Windows\System32\libeay32.dll")
#ssl_library = ctypes.cdll.LoadLibrary(r"C:\Windows\SysWOW64\libeay32.dll")
#ssl_library = ctypes.cdll.LoadLibrary("libssl.so")
except Exception:
print('[error] import {} failed; need libeay32.dll/.so'.format(test_name,test_name))
return(1)

NID_secp256k1 = 714


def get_public_key1(private_key, curve_name=NID_secp256k1):
k = ssl_library.EC_KEY_new_by_curve_name(curve_name)

if ssl_library.EC_KEY_generate_key(k) != 1:
raise Exception("internal error")

size = ssl_library.i2o_ECPublicKey(k, 0)
storage = ctypes.create_string_buffer(size)
ssl_library.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(storage)))
public_key = storage.raw

ssl_library.EC_KEY_free(k)
return public_key


def get_public_key2(private_key, curve_name=NID_secp256k1):
k = ssl_library.EC_KEY_new_by_curve_name(curve_name)
   
storage = ctypes.create_string_buffer(private_key)
bignum_private_key = ssl_library.BN_new()
ssl_library.BN_bin2bn(storage, 32, bignum_private_key)

group = ssl_library.EC_KEY_get0_group(k)
point = ssl_library.EC_POINT_new(group)

ssl_library.EC_POINT_mul(group, point, bignum_private_key, None, None, None)
ssl_library.EC_KEY_set_private_key(k, bignum_private_key)
ssl_library.EC_KEY_set_public_key(k, point)

size = ssl_library.i2o_ECPublicKey(k, 0)
storage = ctypes.create_string_buffer(size)
ssl_library.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(storage)))
public_key = storage.raw

ssl_library.EC_POINT_free(point)
ssl_library.BN_free(bignum_private_key)
ssl_library.EC_KEY_free(k)
return public_key

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, 'big')

#pubkey = get_public_key1(secret)
pubkey = get_public_key2(secret)
#print('[pubkey] {0}'.format(pubkey.hex()));exit(0);

setup = 'import random; import ctypes; ssl_library = ctypes.cdll.LoadLibrary("libeay32.dll"); n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = '''

if 1:
NID_secp256k1 = 714


def get_public_key1(private_key, curve_name=NID_secp256k1):
k = ssl_library.EC_KEY_new_by_curve_name(curve_name)

if ssl_library.EC_KEY_generate_key(k) != 1:
raise Exception("internal error")

size = ssl_library.i2o_ECPublicKey(k, 0)
storage = ctypes.create_string_buffer(size)
ssl_library.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(storage)))
public_key = storage.raw

ssl_library.EC_KEY_free(k)
return public_key


def get_public_key2(private_key, curve_name=NID_secp256k1):
k = ssl_library.EC_KEY_new_by_curve_name(curve_name)
   
storage = ctypes.create_string_buffer(private_key)
bignum_private_key = ssl_library.BN_new()
ssl_library.BN_bin2bn(storage, 32, bignum_private_key)

group = ssl_library.EC_KEY_get0_group(k)
point = ssl_library.EC_POINT_new(group)

ssl_library.EC_POINT_mul(group, point, bignum_private_key, None, None, None)
ssl_library.EC_KEY_set_private_key(k, bignum_private_key)
ssl_library.EC_KEY_set_public_key(k, point)

size = ssl_library.i2o_ECPublicKey(k, 0)
storage = ctypes.create_string_buffer(size)
ssl_library.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(storage)))
public_key = storage.raw

ssl_library.EC_POINT_free(point)
ssl_library.BN_free(bignum_private_key)
ssl_library.EC_KEY_free(k)
return public_key

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
secret=random.randint(1,n);
secret=secret.to_bytes((secret.bit_length()+7)//8 or 1, "big");
pubkey = get_public_key2(secret);
'''
print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# coincurve
# python -m pip install  coincurve==9.0.0
# (use libsecp256k1.dll/.so)



def test_coincurve(test_name = 'coincurve', rounds = 10):
loops = rounds
try:
import coincurve
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

from coincurve.keys import PrivateKey, PublicKey
pubkey = PublicKey.from_valid_secret(secret).point()
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from coincurve.keys import PrivateKey, PublicKey; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret=random.randint(1,n); secret=secret.to_bytes((secret.bit_length()+7)//8 or 1, "big"); PublicKey.from_valid_secret(secret).point();'
print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#stmt  = "secret=random.randint(1,n); secret=secret.to_bytes((secret.bit_length()+7)//8 or 1, 'big'); PublicKey.from_valid_secret(secret).format(False);"
#print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# fastecdsa
# python -m pip install  fastecdsa==1.6.5
# (use Cython)



def test_fastecdsa(test_name = 'fastecdsa', rounds = 10):
loops = rounds
try:
import fastecdsa
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
#secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

from fastecdsa import keys, curve
pubkey = keys.get_public_key(secret, curve.P256)
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from fastecdsa import keys, curve; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret=random.randint(1,n); keys.get_public_key(secret, curve.P256);'
print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )


#exit(0);


#####################
#####################
#####################



test_ecdsa()
test_pycoin()
test_starkbank_ecdsa()
test_pybitcointools()
test_fastecdsa()
test_python_bitcoinlib()
test_openssl()
test_coincurve()


need some fix for python2
Code:
#!/usr/bin/env python2
#####################
import timeit

rounds = 10

import random
randrange = random.SystemRandom().randrange


#####################
# ecdsa
# (use python)
# python -m pip install  ecdsa==0.13

def test_ecdsa(test_name = 'ecdsa', rounds = 10):
loops = rounds
try:
import ecdsa
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
#secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

from ecdsa.ecdsa import generator_secp256k1, Public_key, Private_key
#from ecdsa.util import string_to_number, number_to_string
g = generator_secp256k1
pubkey = Public_key( g, g * secret ).point
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from ecdsa.ecdsa import generator_secp256k1, Public_key, Private_key; g=generator_secp256k1; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'Public_key(g,g*random.randint(1,n)).point'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# starkbank-ecdsa
# (use python with quick jacobian multiply)
# python -m pip install  starkbank-ecdsa==0.1.4

def test_starkbank_ecdsa(test_name = 'starkbank-ecdsa', rounds = 10):
loops = rounds
try:
import ellipticcurve
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
#secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")
secret = bytes((secret.bit_length()+7)//8 or 1)

from ellipticcurve.privateKey import PrivateKey
pubkey = PrivateKey.fromString(secret).publicKey().toString()
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from ellipticcurve.privateKey import PrivateKey; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret = random.randint(1,n); secret = bytes((secret.bit_length()+7)//8 or 1); PrivateKey.fromString(secret).publicKey().toString();'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# bitcoin (pybitcointools)
# (use python with quick jacobian multiply)
# (Author: Vitalik Buterin)
# python -m pip install  bitcoin==1.1.42
# python -m pip install  pybitcointools==1.1.15

def test_pybitcointools(test_name = 'pybitcointools', rounds = 10):
loops = rounds
try:
import pybitcointools
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)


n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
#secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

from pybitcointools import fast_multiply, G
pubkey = fast_multiply(G, secret)
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from pybitcointools import fast_multiply, G; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'fast_multiply(G, random.randint(1,n))'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# pycoin
# (use python, use openssl(fail))
# python -m pip install  pycoin==0.80

def test_pycoin(test_name = 'pycoin', rounds = 10):
loops = rounds
#if os.getenv("PYCOIN_NATIVE") != "openssl":
try:
import pycoin
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
#secret = secret.to_bytes((secret.bit_length()+7)//8 or 1, "big")

#from pycoin.key import Key
#pubkey = Key(secret_exponent=secret).public_pair()
#print('[pubkey] {0}'.format(pubkey));exit(0);

from pycoin.ecdsa import public_pair_for_secret_exponent, generator_secp256k1
pubkey = public_pair_for_secret_exponent(generator_secp256k1, secret)
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from pycoin.ecdsa import public_pair_for_secret_exponent, generator_secp256k1; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'public_pair_for_secret_exponent(generator_secp256k1, random.randint(1,n))'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# bit
# (use coincurve)
# python -m pip install  bit==0.4.3

#####################
# pybitcoin
# (use pybitcointools + use ecdsa)
# python -m pip install  pybitcoin==0.9.9

#####################
# python-bitcoin
# (use ecdsa)
# python -m pip install  python-bitcoin==0.0.10

#####################
# bitcoinlib
# (use ecdsa)
# python -m pip install  bitcoinlib==0.4.4

#####################
# python-bitcoinlib
# (use openssl)
# python -m pip install  python-bitcoinlib==0.10.1
# (inst path to PythonXX/Lib/site-packages/bitcoin , its conflict with pybitcointools)

def test_python_bitcoinlib(test_name = 'python-bitcoinlib', rounds = 10):
loops = rounds
try:
import bitcoin
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
secret = bytes((secret.bit_length()+7)//8 or 1)

from bitcoin.wallet import CKey
pubkey = CKey(secret,compressed=False).pub
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from bitcoin.wallet import CKey; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret = random.randint(1,n);secret = bytes((secret.bit_length()+7)//8 or 1);CKey(secret,compressed=False).pub'

print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )


from bitcoin.core.key import CECKey, CPubKey
pubkey = CECKey()
pubkey.set_secretbytes(secret)
pubkey.set_compressed(compressed=False)
pubkey = CPubKey(pubkey.get_pubkey(), pubkey)
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from bitcoin.core.key import CECKey, CPubKey; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret = random.randint(1,n);secret = bytes((secret.bit_length()+7)//8 or 1, "big");pubkey = CECKey();pubkey.set_secretbytes(secret);pubkey = CPubKey(pubkey.get_pubkey(), pubkey);'

#print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# py_ecc
# (use python with quick jacobian multiply)
# python -m pip install  py_ecc==1.4.7
# (bitcoin_easy)

#####################
# ecc-0.0.1





#####################
# bitcoin-utils
# (use ecdsa)
# python -m pip install  bitcoin-utils==0.2.2

#####################
# bitcoin_tools
# (use python)
# python -m pip install  bitcoin_tools==0.0.13

#####################
# python_bitcoin_tools
# (use ecdsa)
# python -m pip install  python_bitcoin_tools==0.2.3



#####################
#
# (use )
# python -m pip install 

#exit(0);



#####################
# openssl

def test_openssl(test_name = 'openssl', rounds = 10):
loops = rounds
try:
import ctypes
ssl_library = ctypes.cdll.LoadLibrary("libeay32.dll")
#ssl_library = ctypes.cdll.LoadLibrary(r"C:\Windows\System32\libeay32.dll")
#ssl_library = ctypes.cdll.LoadLibrary(r"C:\Windows\SysWOW64\libeay32.dll")
#ssl_library = ctypes.cdll.LoadLibrary("libssl.so")
except Exception:
print('[error] import {} failed; need libeay32.dll/.so'.format(test_name,test_name))
return(1)

NID_secp256k1 = 714


def get_public_key1(private_key, curve_name=NID_secp256k1):
k = ssl_library.EC_KEY_new_by_curve_name(curve_name)

if ssl_library.EC_KEY_generate_key(k) != 1:
raise Exception("internal error")

size = ssl_library.i2o_ECPublicKey(k, 0)
storage = ctypes.create_string_buffer(size)
ssl_library.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(storage)))
public_key = storage.raw

ssl_library.EC_KEY_free(k)
return public_key


def get_public_key2(private_key, curve_name=NID_secp256k1):
k = ssl_library.EC_KEY_new_by_curve_name(curve_name)
   
storage = ctypes.create_string_buffer(private_key)
bignum_private_key = ssl_library.BN_new()
ssl_library.BN_bin2bn(storage, 32, bignum_private_key)

group = ssl_library.EC_KEY_get0_group(k)
point = ssl_library.EC_POINT_new(group)

ssl_library.EC_POINT_mul(group, point, bignum_private_key, None, None, None)
ssl_library.EC_KEY_set_private_key(k, bignum_private_key)
ssl_library.EC_KEY_set_public_key(k, point)

size = ssl_library.i2o_ECPublicKey(k, 0)
storage = ctypes.create_string_buffer(size)
ssl_library.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(storage)))
public_key = storage.raw

ssl_library.EC_POINT_free(point)
ssl_library.BN_free(bignum_private_key)
ssl_library.EC_KEY_free(k)
return public_key

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
secret = bytes((secret.bit_length()+7)//8 or 1)

#pubkey = get_public_key1(secret)
pubkey = get_public_key2(secret)
#print('[pubkey] {0}'.format(pubkey.hex()));exit(0);

setup = 'import random; import ctypes; ssl_library = ctypes.cdll.LoadLibrary("libeay32.dll"); n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = '''

if 1:
NID_secp256k1 = 714


def get_public_key1(private_key, curve_name=NID_secp256k1):
k = ssl_library.EC_KEY_new_by_curve_name(curve_name)

if ssl_library.EC_KEY_generate_key(k) != 1:
raise Exception("internal error")

size = ssl_library.i2o_ECPublicKey(k, 0)
storage = ctypes.create_string_buffer(size)
ssl_library.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(storage)))
public_key = storage.raw

ssl_library.EC_KEY_free(k)
return public_key


def get_public_key2(private_key, curve_name=NID_secp256k1):
k = ssl_library.EC_KEY_new_by_curve_name(curve_name)
   
storage = ctypes.create_string_buffer(private_key)
bignum_private_key = ssl_library.BN_new()
ssl_library.BN_bin2bn(storage, 32, bignum_private_key)

group = ssl_library.EC_KEY_get0_group(k)
point = ssl_library.EC_POINT_new(group)

ssl_library.EC_POINT_mul(group, point, bignum_private_key, None, None, None)
ssl_library.EC_KEY_set_private_key(k, bignum_private_key)
ssl_library.EC_KEY_set_public_key(k, point)

size = ssl_library.i2o_ECPublicKey(k, 0)
storage = ctypes.create_string_buffer(size)
ssl_library.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(storage)))
public_key = storage.raw

ssl_library.EC_POINT_free(point)
ssl_library.BN_free(bignum_private_key)
ssl_library.EC_KEY_free(k)
return public_key

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
secret=random.randint(1,n);
secret=bytes((secret.bit_length()+7)//8 or 1);
pubkey = get_public_key2(secret);
'''
print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);


#####################
# coincurve
# python -m pip install  coincurve==9.0.0
# (use libsecp256k1.dll/.so)



def test_coincurve(test_name = 'coincurve', rounds = 10):
loops = rounds
try:
import coincurve
except Exception:
print('[error] import {} failed; try: python -m pip install {}'.format(test_name,test_name))
return(1)

n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;
#secret = random.randint(1,n)
secret = 0xebaaedce6af48a03bbfd25e8cd0364141fffffffffffffffffffffffffffffff;
secret = bytes((secret.bit_length()+7)//8 or 1)

from coincurve.keys import PrivateKey, PublicKey
pubkey = PublicKey.from_valid_secret(secret).point()
#print('[pubkey] {0}'.format(pubkey));exit(0);

setup = 'import random; from coincurve.keys import PrivateKey, PublicKey; n=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141;'
stmt  = 'secret=random.randint(1,n); secret=bytes((secret.bit_length()+7)//8 or 1); PublicKey.from_valid_secret(secret).point();'
print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#stmt  = "secret=random.randint(1,n); secret=secret.to_bytes((secret.bit_length()+7)//8 or 1, 'big'); PublicKey.from_valid_secret(secret).format(False);"
#print('[timeit] {0} loops, {1:.6} sec '.format( loops, timeit.timeit(stmt, setup, number=loops) ).ljust(50,' ') + test_name )

#exit(0);

#####################
#####################
#####################



test_ecdsa()
test_pycoin()
test_starkbank_ecdsa()
test_pybitcointools()
test_python_bitcoinlib()
#test_openssl()
test_coincurve()



Title: Re: ECDSA in python
Post by: pooya87 on August 11, 2019, 03:05:36 AM
- coincurve lib the undisputed leader (https://github.com/ofek/coincurve)

it is worth noting that libraries such as python-ecdsa are written purely in python with no optimization and they also work for all the elliptic curves while a library like coincurve is a wrapper around libsecp256k1 which is a heavily optimized library written in C and only works for 1 curve (the one bitcoin uses).

it was a good comparison though. thanks for posting.


Title: Re: ECDSA in python
Post by: darosior on August 11, 2019, 02:45:17 PM
For what it worth, I've found this one (https://github.com/ethereum/py_ecc/blob/master/py_ecc/secp256k1/secp256k1.py) quite neat when tinkering with transactions : a simple file without any dependency. It's from the Ethereum guys but they use the same curve as Bitcoin. If you are looking for broader ECDSA than secp256k1, then python-ecdsa (https://pypi.org/project/ecdsa/) is cool.