25

I need to sign a hash of 256 bits with ECDSA using a private key of 256 bits, just as bitcoin does, and I am reaching desperation because of the lack of documentation of ecdsa in python.

I found a lot of codes on the internet, but there was nothing as easy as just ecdsa.sign(msg, privkey) or similar, everything I found is a lot of code of mathematical stuff I don't understand, but yet they use the ecdsa library (I don't know why they wouldn't add a signing function in a library that is going to be used to sign stuff, instead a page of code is needed when using the library?).

This is the best code I found so far:

def ecdsa_sign(val, secret_exponent):
    """Return a signature for the provided hash, using the provided
    random nonce. It is absolutely vital that random_k be an unpredictable
    number in the range [1, self.public_key.point.order()-1].  If
    an attacker can guess random_k, he can compute our private key from a
    single signature. Also, if an attacker knows a few high-order
    bits (or a few low-order bits) of random_k, he can compute our private
    key from many signatures. The generation of nonces with adequate
    cryptographic strength is very difficult and far beyond the scope
    of this comment.

    May raise RuntimeError, in which case retrying with a new
    random value k is in order.
    """
    G = ecdsa.SECP256k1
    n = G.order()
    k = deterministic_generate_k(n, secret_exponent, val)
    p1 = k * G
    r = p1.x()
    if r == 0: raise RuntimeError("amazingly unlucky random number r")
    s = ( ecdsa.numbertheory.inverse_mod( k, n ) * ( val + ( secret_exponent * r ) % n ) ) % n
    if s == 0: raise RuntimeError("amazingly unlucky random number s")

    return signature_to_der(r, s)

def deterministic_generate_k(generator_order, secret_exponent, val, hash_f=hashlib.sha256):
    """
    Generate K value according to https://tools.ietf.org/html/rfc6979
    """
    n = generator_order
    order_size = (bit_length(n) + 7) // 8
    hash_size = hash_f().digest_size
    v = b'\x01' * hash_size
    k = b'\x00' * hash_size
    priv = intbytes.to_bytes(secret_exponent, length=order_size)
    shift = 8 * hash_size - bit_length(n)
    if shift > 0:
        val >>= shift
    if val > n:
        val -= n
    h1 = intbytes.to_bytes(val, length=order_size)
    k = hmac.new(k, v + b'\x00' + priv + h1, hash_f).digest()
    v = hmac.new(k, v, hash_f).digest()
    k = hmac.new(k, v + b'\x01' + priv + h1, hash_f).digest()
    v = hmac.new(k, v, hash_f).digest()

    while 1:
        t = bytearray()

        while len(t) < order_size:
            v = hmac.new(k, v, hash_f).digest()
            t.extend(v)

        k1 = intbytes.from_bytes(bytes(t))

        k1 >>= (len(t)*8 - bit_length(n))
        if k1 >= 1 and k1 < n:
            return k1

        k = hmac.new(k, v + b'\x00', hash_f).digest()
        v = hmac.new(k, v, hash_f).digest()

But I just can't trust a code like that because I have no idea what it does. Also, the comments in ecdsa_sign says that returns a signature given the value, the secret exponent, and a nonce. It says its very important to have a nonce, but I just can't figure out where that nonce is.

Is there any simple, one-line way to sign and verify ECDSA signatures using whatever trusted library in python on windows?

Jorky10
  • 591
  • 2
  • 6
  • 9
  • can you not sign it using openssl? – Padraic Cunningham Dec 24 '15 at 10:50
  • what are the pros of using openssl over ecdsa? – Jorky10 Dec 24 '15 at 11:02
  • I imagine the ecdsa lib uses openssl so there is no difference, you will have access to whatever you need using openssl https://www.openssl.org/docs/manmaster/crypto/ecdsa.html https://www.guyrutenberg.com/2013/12/28/creating-self-signed-ecdsa-ssl-certificate-using-openssl/ – Padraic Cunningham Dec 24 '15 at 11:05
  • I would agree that you shouldn't have to write code as detailed as the code in your example just to use ECDSA. Have you seen this Python ECDSA library? https://github.com/warner/python-ecdsa – David Grayson Dec 24 '15 at 18:27

3 Answers3

47

You can try using the python ecdsa package, using Python3:

pip3 install ecdsa

Usage:

import ecdsa
from hashlib import sha256

# SECP256k1 is the Bitcoin elliptic curve
sk = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1, hashfunc=sha256) # The default is sha1
vk = sk.get_verifying_key()
sig = sk.sign(b"message")
vk.verify(sig, b"message") # True

To verify an existing signature with a public key:

import ecdsa
from hashlib import sha256
message = b"message"
public_key = '98cedbb266d9fc38e41a169362708e0509e06b3040a5dfff6e08196f8d9e49cebfb4f4cb12aa7ac34b19f3b29a17f4e5464873f151fd699c2524e0b7843eb383'
sig = '740894121e1c7f33b174153a7349f6899d0a1d2730e9cc59f674921d8aef73532f63edb9c5dba4877074a937448a37c5c485e0d53419297967e95e9b1bef630d'

vk = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_key), curve=ecdsa.SECP256k1, hashfunc=sha256) # the default is sha1
vk.verify(bytes.fromhex(sig), message) # True

The package is compatible with Python 2 as well

Philip Couling
  • 13,581
  • 5
  • 53
  • 85
k26dr
  • 1,229
  • 18
  • 15
  • 1
    You can also use sign_digest and verify_digest for digested data, for instance, if you are passing a sha256 hash in binary. – John Smith Jul 31 '17 at 06:44
  • Is there any way to make the keys smaller? For instance, define their maximum size? – Nuclear_Man_D Sep 29 '17 at 16:46
  • @k26dr Thank you. Dang! I am using it for a blockchain.... And I already wrote enough that the blockchain works just no public key validation. There is no room for such a long public key... So the existing blockchain is trash. Oh well, lol. – Nuclear_Man_D Oct 09 '17 at 15:58
  • @Nuclear_Man_D if you mean that the public key this example generates is too long for the Blockchain, then you're right. that's why Bitcoin does some extra stuff with the public key! – Finlay Roelofs Dec 02 '17 at 01:20
  • 1
    as of March 9, 2022 using python 3.10.2, the verification part (the second part) fails – ianace Mar 09 '22 at 05:45
  • 1
    @ianace it's because the verifying part uses SHA256 in the example above. Just remove `, hashfunc=sha256` and it will work (as of Python 3.10.8). – Tyler Pantuso Nov 07 '22 at 02:23
4

How to install it:

pip install starkbank-ecdsa

How to use it:

# Generate Keys
privateKey = PrivateKey()
publicKey = privateKey.publicKey()

message = "My test message"

# Generate Signature
signature = Ecdsa.sign(message, privateKey)

# Verify if signature is valid
print Ecdsa.verify(message, signature, publicKey)

Full reference: https://github.com/starkbank/ecdsa-python

rcmstark
  • 1,031
  • 1
  • 14
  • 18
0

you can also use sep256k1 library in Python to ecdsa sign and verify. The public and private keys are the keys generated from Bip32 specifications and seed from Bip39 specifications.

 Private key  is  1149ab92fbc40993f21336206ca184a9dc2d5231eb575d2a0a6d56773bf0f356
 Public key  is  03c7ac999403591bceacca3d37598886f7c41943c8045c7e1cb5a9295d0003cc5b


from sawtooth_signing.secp256k1 import Secp256k1PrivateKey
from sawtooth_signing.secp256k1 import Secp256k1PublicKey

def sign_nonce(hex_private_key):
   nonce = random.randint(2**10, 2**32)
   checksum = hashlib.sha3_512(str(nonce).encode()).hexdigest()

   private_key = Secp256k1PrivateKey.from_hex(hex_private_key)
   message = private_key.secp256k1_private_key.ecdsa_sign(str(nonce).encode())
   serialized_message = private_key.secp256k1_private_key.ecdsa_serialize(message)
   hex_message = binascii.hexlify(serialized_message)
   return nonce, checksum, hex_message


def verify_nonce(nonce, checksum, message, hex_public_key):
   ##message is hex encoded
   message = binascii.unhexlify(message)
   public_key = Secp256k1PublicKey.from_hex(hex_public_key)
   unserialized = public_key.secp256k1_public_key.ecdsa_deserialize(message)
   result = public_key.secp256k1_public_key.ecdsa_verify(str(nonce).encode(),    unserialized)
  return result

The result will be True or False depending upon the verification. i have used uint32(typings) or int as a nonce, but any byte array or string can be used. strings need to be converted to bytes.

GraphicalDot
  • 2,644
  • 2
  • 28
  • 43