With a digital signature, we sign a message with a private key, and then prove it with the related public key. The signature typically takes the form of (\(r,s\)), and where \(r\) and \(s\) are used with the message to provide the signature. In this case we will generate signatures for the main methods used in ECDSA for difference curves (such as secp256k1, NIST-P256 and Brainpool-256r1). The number value in the curve normally identifies the number of bits in the private key, such as secp256k1 having 256 bits in the private key. This relates to the bit length of the prime number used to define the field. With a standard ECDSA implementation we take 256 bits of random data to generate each of the signatures. Unfortunately, if we use the same random number for at least two signatures, we can recover the private key. Instead of using a random number, RFC6979 uses HMAC-SHA256(private_key, message) in order to overcome the private key leakage problem. The signature then becomes deterministic, and where we always produce the same output for a given set of inputs. In the same runs, we use a \(k\) value of 9, and which will always produces the same message and set of keys. In this way, the output is deterministic. We will also get the same signature each time for the RFC6979 version, and where the same message and key pair will always produce the same signature. In each case, we will generate a random key pair for the signature test.
Deterministic ECDSA Signatures - k value and RFC6979 |
Code
The code used is:
from ecpy.curves import Curve import secrets from ecpy.keys import ECPrivateKey from ecpy.ecdsa import ECDSA import hashlib from ecpy.formatters import decode_sig import sys,binascii curve = Curve.get_curve('secp256k1') G = curve.generator order = curve.order t=0 msg = 'hello' if (len(sys.argv)>1): msg=str(sys.argv[1]) if (len(sys.argv)>2): t=int(sys.argv[2]) msg=msg.encode() if (t==1): curve = Curve.get_curve('NIST-P192') elif (t==2): curve = Curve.get_curve('NIST-P224') elif (t==3): curve = Curve.get_curve('NIST-P256') elif (t==4): curve = Curve.get_curve('secp192k1') elif (t==5): curve = Curve.get_curve('secp160k1') elif (t==6): curve = Curve.get_curve('secp224k1') elif (t==7): curve = Curve.get_curve('Brainpool-p256r1') elif (t==8): curve = Curve.get_curve('Brainpool-p224r1') elif (t==9): curve = Curve.get_curve('Brainpool-p192r1') elif (t==10): curve = Curve.get_curve('Brainpool-p160r1') elif (t==11): curve = Curve.get_curve('secp256r1') print (f"Name: {curve.name}, y^2=x^3+a*x+b (mod p) Type: {curve.type}, Size: {curve.size}, a={curve.a}, b={curve.b}, G={curve.generator}, field={curve.field}, order={curve.order}") sk = ECPrivateKey(secrets.randbits(32*8), curve) if (curve.size==192): sk = ECPrivateKey(secrets.randbits(24*8), curve) elif (curve.size==224): sk = ECPrivateKey(secrets.randbits(28*8), curve) elif (curve.size==160): sk = ECPrivateKey(secrets.randbits(20*8), curve) pk = sk.get_public_key() print("Message: ",msg) print("\nPrivate key:", hex(sk.d)) print(f"Public key: ({hex(pk.W.x)},{hex(pk.W.y)}") print ("\n---- Signed with ECDSA ----") signer = ECDSA() sig = signer.sign(msg,sk) rtn=signer.verify(msg,sig,pk) print(f"Signature verification: {rtn}") print("\nSignature:", binascii.hexlify(sig).decode()) r,s = decode_sig(sig, fmt='DER') print (f"\n(r,s) = ({r},{s})") print ("\n---- Signed with RFC 6979 ----") signer = ECDSA() sig = signer.sign_rfc6979(msg,sk,hashlib.sha256) rtn=signer.verify(msg,sig,pk) print(f"Signature verification: {rtn}") print("\nSignature:", binascii.hexlify(sig).decode()) r,s = decode_sig(sig, fmt='DER') print (f"\n(r,s) = ({r},{s})") print ("\n---- Signed with random k ----") signer = ECDSA() k=secrets.randbits(32*8) sig = signer.sign_k(msg,sk,k) rtn=signer.verify(msg,sig,pk) print(f"Signature verification: {rtn}") print("\nSignature:", binascii.hexlify(sig).decode()) r,s = decode_sig(sig, fmt='DER') print (f"\n(r,s) = ({r},{s})")
A sample run:
Name: secp256k1, y^2=x^3+a*x+b (mod p) Type: weierstrass, Size: 256, a=0, b=7, G=(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 , 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8), field=115792089237316195423570985008687907853269984665640564039457584007908834671663, order=115792089237316195423570985008687907852837564279074904382605163141518161494337 Message: b'hello' Private key: 0x674dd200e0c0f99ec58090406c5e796228ca9427718780f7a49aa0ec31123f0d Public key: (0xafa1e8f0e05829a78c2704bfc53daaf33b2e8953fb725ed149d14f71dcf49122,0x60e63868d3891dd320418bc85feb835ab2939d73da12c0a3d73ee496828c306e ---- Signed with ECDSA ---- Signature verification: True Signature: 304502201be74095aef2ef98722b8cd9f6bddef60e248d11853a73034c50ad028b28bcb102210083d2271e13cf348105b671b110d97ce86fccacda2275e1d7a4ba54df6ec6caf8 (r,s) = (12621034330934524058899587553085185326032991118075910240623519069628472540337,59624291027003455205833319865025687653149318384603836898418558025640075250424) ---- Signed with RFC 6979 ---- Signature verification: True Signature: 30450220497f05b33ef42fc8bf193299e566cd958b0b4d512c39a4141e4055305b97b560022100ef127b2e9c686a96ab78d876b03c56843e29584caf3c7ec3362e043f33896a26 (r,s) = (33243266864997303181534489118683617033939264573126908211191399536607521912160,108135424229996551567419965418087841718050443506971158486379825818461400558118) ---- Signed with random k ---- Signature verification: True Signature: 3046022100c744528aa47ccdf020805cc1a40cac62d09a6974715d8c529d6795488e843db2022100ab0a5c2326122be675dacafe3d78eb0079207ac302ddf0b8f7b7faa739a09e38 (r,s) = (90130972149470575718242791399044975802719121421206317233586403460068617239986,77363801486657716736147309828633566280188714067130251518058320769002923662904) ---- Signed with k=9 ---- 9 Signature verification: True Signature: 3046022100acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe022100965677ce372284f9858293cc4d5454604d20120b77f03b63c27243a3351df569 (r,s) = (78173298682877769088723994436027545680738210601369041078747105985693655485630,67999703002431557331066997203737941178725964356516914758664672085151500334441)
In the last run, we use a \(k\) value of 9, and which always produces the same output for the same message. In this way, the output is deterministic. The same goes for the RFC6979 version, and where the same message and the same key pair, it will always produces the same signature.