Satoshi Selected ECDSA with The Secp256k1 Curve and SHA-256. Are Other Options Available?

Over 10 years ago, Satoshi Nakamoto wrote a classic white paper on Bitcoin, and the rest is history:

Photo by Vitaly Mazur on Unsplash

Satoshi Selected ECDSA with The Secp256k1 Curve and SHA-256. Are Other Options Available?

Over 10 years ago, Satoshi Nakamoto wrote a classic white paper on Bitcoin, and the rest is history:

While the paper lacks any real implementation details, Satoshi selected the secp256k1 curve and the ECDSA method for signatures — actually he developed the code originally for Microsoft Windows, and using C++.

Basically, Bob would generate a key pair: a private key and a public key. When required, Bob would sign a transaction with his public key, and then everyone could validate this with his public key. The great thing with ECDSA is that there is no need to actually distribute the public key (as with RSA) and use PKI — the public key was embedded in the signature and linked to Bob’s identity. It was a genius decision and removed the dependence on the PKI infrastructure (and which was controlled by just a few companies). No more could public keys be revoked by those controlling the Internet — typically the root certificate providers). This was public key anarchy and a rebellion against the centralised approaches of the Internet.

ECDSA Signatures

At the core of the signature is the generation of two values: r and s. In ECDSA, Bob creates a random private key (priv), and then a public key from:

Next, in order to create a signature for a message of M, he creates a random number (k) and generates the signature of:

and where priv is the private key and h=H(M) The signature is then (r,s) and where r is the x-co-ordinate of the point kG. h is the SHA-256 hash of the message (M), and converted into an integer value.

And, so, Bitcoin and Ethereum now use ECDSA and secp256k1 as the core of their trusted infrastructure. This uses SHA-256 for the hash of the message. But, does it always have to be secp256k1 and SHA-256? Well, no. ECDSA can scale to most of the popular curves and can cope with most types of hash functions.

Coding

The most common curves used are NIST, secp and Brainpool. These are defined by the number of bits in the curve, such as 128, 160 and 256. The NIST 256-bit curve is then NIST256p. For hashing, we will use the common hashing methods of MD5 (128-bit hash), SHA-1 (160-bit hash), SHA-256 (256-bit hash), SHA-512 (512-bit hash), BLAKE2b (256-bit hash), SHAKE128 (128-bit hash) and SHAKE256 (256-bit hash). The coding is [here]:

from ecdsa import SigningKey, NIST192p,NIST224p, NIST256p,NIST384p,NIST521p,SECP256k1, BRAINPOOLP160r1, BRAINPOOLP192r1,BRAINPOOLP224r1, BRAINPOOLP256r1, BRAINPOOLP320r1, BRAINPOOLP384r1, BRAINPOOLP512r1
import sys
import hashlib
def getCurve(name):
name=name.lower()
if (name=="nist192p"): return(NIST192p)
if (name=="nist224p"): return(NIST224p)
if (name=="nist256p"): return(NIST256p)
if (name=="nist384p"): return(NIST384p)
if (name=="nist521p"): return(NIST521p)
if (name=="secp256k1"): return(SECP256k1)
if (name=="brainpoolp160r1"): return(BRAINPOOLP160r1)
if (name=="brainpoolp192r1"): return(BRAINPOOLP192r1)
if (name=="brainpoolp224r1"): return(BRAINPOOLP224r1)
if (name=="brainpoolp256r1"): return(BRAINPOOLP256r1)
if (name=="brainpoolp320r1"): return(BRAINPOOLP320r1)
if (name=="brainpoolp384r1"): return(BRAINPOOLP384r1)
if (name=="brainpoolp512r1"): return(BRAINPOOLP512r1)


message="Hello"
msg=message.encode()
name="NIST192p"
hashname="SHA256"
if (len(sys.argv)>1):
message=(sys.argv[1])
if (len(sys.argv)>2):
name=(sys.argv[2])
if (len(sys.argv)>3):
hashname=(sys.argv[3])

curvetype=getCurve(name)
Gx=curvetype.generator.x()
Gy=curvetype.generator.y()

sk = SigningKey.generate(curve=curvetype)
vk = sk.verifying_key

h =bytearray(hashlib.sha256(message.encode()).digest())
if (hashname=="MD5"): h =bytearray(hashlib.md5(message.encode()).digest())
elif (hashname=="SHA1"): h =bytearray(hashlib.sha1(message.encode()).digest())
elif (hashname=="SHA512"): h =bytearray(hashlib.sha512(message.encode()).digest())
sig = sk.sign(h)
rtn=vk.verify(sig, h)
print(f"Message: ",message)
print(f"\nCurve: ",curvetype)
print(f"Generator:\nGx={Gx}\nGy={Gy}\nOrder={curvetype.order}")

print(f"\nHash method: ",hashname)
print(f"Hash: ",h.hex())
print(f"\nPrivate key: ",sk.to_string().hex())
pub_x=vk.to_string()[0:len(vk.to_string())//2]
pub_y=vk.to_string()[len(vk.to_string())//2:]

print(f"\nPublic key:\nx={pub_x.hex()}\ny={pub_y.hex()}")
siglen=len(sig)
r=sig[0:siglen//2]
s=sig[siglen//2:]
print(f"\nSignature:\nr={r.hex()}\ns={s.hex()}")
print(f"Verified: {rtn}")

and a sample test for secp256k1 [here]:

Message:  qwerty
Curve:  SECP256k1
Generator:
Gx=55066263022277343669578718895168534326250603453777594175500187360389116729240
Gy=32670510020758816978083085130507043184471273380659243275938904335757337482424
Order=115792089237316195423570985008687907852837564279074904382605163141518161494337
Hash method:  SHA256
Hash: 65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5
Private key:  f74d91b6a40dfbdc061e6749d2d848e4e552799e6f6661ddf3d47a13390662d5
Public key:
x=0a4661012d86058f5e9c1bbabd0ad9be5c787e936db3ea68370036208f7fc9e5
y=5f212a2db75c7d26637a1e8bc4821e67e0c8d24ca2e322704bb549bbc0f02fc7
Signature:
r=a2aab82f7aea9428b351efc88753543c1e6e8a08f92a03c38f8e82b7cf9800a4
s=37bc4e090224064912a642d3569cfce2d098b58e3f2979e3fc30d0b29b99f338
Verified: True

and for NIST-P521:

Message:  qwerty
Curve:  NIST521p
Generator:
Gx=2661740802050217063228768716723360960729859168756973147706671368418802944996427808491545080627771902352094241225065558662157113545570916814161637315895999846
Gy=3757180025770020463545507224491183603594455134769762486694567779615544477440556316691234405012945539562144444537289428522585666729196580810124344277578376784
Order=6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449
Hash method:  SHA256
Hash: 65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5
Private key:  00c87f1e33bc5c25d1e6622c7fb38e1c64e6578c8d358335446cfb0d3aa3163fb8204411a7008fbb2561eb887b38b77ef0bbdc347533865a898d673e3bfe6b520f7a
Public key:
x=01d2a9b351a2abe870344e846c7a5ebb76e9c31e26f78bbcac1ca81913be56a4d3ec7a4ef9593a81de568c50cb3c335d5672f7a71e4c94b3318473f5faa3c90e60e2
y=01df6a37f8fa79ccf951a4d031eed8090d57290a6cd242e33911a333377f7ff4a771d6c8d40db20ff7f16872bfca7ada828cd9e5b9270043572e4f001b3947ab5e04
Signature:
r=01e8f3f8cc928b91d736b9a8d38dcfeae431e5029401b9e77189133ab73d00428edd3fecd4db5a4d394deb2e956a828cff09afb735b69bc503d663fd3050ec9434ef
s=01450e1cf42f29592ea96836b2b86fb7c2eaae602ac4c8cc31c009c47df6df4499eeed33184fa524a8a632efeb2c9fd532f43ba011bff1ab153a22c2fb404b8a654c
Verified: True

Conclusions

ECDSA needs to be handled with care, as the random nonce value needs to be truly random and never reused. If you want to find out more about ECDSA, try here:

https://asecuritysite.com/ecdsa/ecdsa_sig

Note, that ECDSA is also used in the PKI, and is often used to digitally sign TLS connections for the key hand-shake (along with RSA), and in signing digital certificates. In TLS terms, this is known as ECDHE, and where ECDSA is used to create the authentication part of the key exchange.

So, it’s not just used in Bitcoin and Ethereum, it is used to secure the Internet. We must thank Satoshi, though, for his adoption of ECDSA. While not perfect, it is certainly a great step forward for trust on the Internet.