ECDSA: Elliptic Curve Digital Signature Algorithm
ECDSA is the elliptic curve version of DSA, used to sign TLS handshakes and Bitcoin transactions. Learn how signing and verifying work, why a repeated nonce reveals the private key, how RFC 6979 removes that risk, and how a signature is encoded.
Pashalis Laoutaris 23 min read
Try it
Interactive ECDSA Signatures Visualizer
🔐 ECDSA Signatures
ECDSA: Elliptic Curve Digital Signature Algorithm
Introduction
A digital signature proves that a message came from the holder of a private key and that nobody changed it afterwards. ECDSA is the signature scheme that uses elliptic curves. It is a variant of the Digital Signature Algorithm (DSA) that replaces the arithmetic modulo a prime with the arithmetic of points on a curve. A signature is a pair of numbers (r, s), each about as large as the curve’s group order.
ECDSA is one of the most widely deployed signature schemes. RFC 8446 requires every compliant TLS 1.3 application to support the signature scheme ecdsa_secp256r1_sha256, and the Bitcoin wiki says that Bitcoin uses ECDSA on the curve secp256k1. It also has a famous weakness. Every signature needs a fresh secret number called the nonce, and if it repeats or is predictable, the private key leaks.
This guide builds ECDSA on P-256, P-384, and secp256k1. It checks the code against the test vectors of RFC 6979 and against OpenSSL, and it demonstrates the nonce attack with real numbers. The elliptic curve guide covers the curve arithmetic. The ECDH guide uses the same three curves for key exchange. The DSA guide shows the original scheme, and the EdDSA guide shows a design that avoids the nonce problem by construction.
Table of Contents
- History and Standards
- Keys and Parameters
- Signing a Message
- Verifying a Signature
- Why Verification Works
- The Nonce Problem
- Deterministic Nonces with RFC 6979
- Encoding a Signature
- Interactive Visualizer
- A Worked Example
- Python Implementation
- Where It Is Used
- Limitations
- Security Status
- FAQ
- References
History and Standards
ECDSA is described in the ANSI standard X9.62 and, together with DSA, in the Federal Information Processing Standard 186 of NIST. RFC 6979 names both documents as the standard definitions of DSA and ECDSA. Wikipedia cites the 1999 paper “The Elliptic Curve Digital Signature Algorithm (ECDSA)” by Don Johnson and Alfred Menezes of Certicom Research as a source for its key size figures.
The scheme inherited one property from DSA that mattered more than anyone expected. RFC 6979 puts it plainly in its introduction: each signature needs a fresh value k, and it must be chosen randomly and uniformly with a cryptographically secure process. Even slight biases can be turned into attacks. RFC 6979, written by Pornin and published in August 2013, defines a way to derive k from the message and the private key instead. The resulting signatures are fully compatible with plain ECDSA, and verifiers need not know how k was produced.
Keys and Parameters
The signer and the verifier agree on a curve, a base point G of prime order n, and a hash function. This guide uses the three curves of the ECDH guide. The hash is SHA-256 for P-256 and secp256k1, and SHA-384 for P-384. Other hash functions work too. The test vectors of RFC 6979 include SHA-1, SHA-224, SHA-384, and SHA-512 with P-256, and my code reproduces all of them.
A private key is a random integer d from 1 to n - 1. The public key is the point Q = d * G. Wikipedia notes that the private key of ECDSA is believed to need about twice as many bits as the security level. A signature has about 4 times as many bits as the exponent of the security level. For the three curves here, a raw signature is 64 bytes on P-256 and secp256k1, and 96 bytes on P-384.
Signing a Message
To sign a message m with the private key d, the signer follows these steps. Wikipedia lists the same steps.
- Compute
e = HASH(m), then letzbe the leftmost bits ofe, as many as the bit length ofn. The valuezcan be larger thann, but never longer. - Pick a secret integer
kfrom 1 ton - 1. This is the nonce. - Compute the point
(x1, y1) = k * G. - Reduce the x coordinate to get
r = x1 mod n. Ifris 0, start again with a newk. - Combine everything into
s = k^-1 * (z + r * d) mod n. Ifsis 0, start again with a newk. - The signature is the pair
(r, s).
The inverse k^-1 is taken modulo n, which is why n must be prime. Wikipedia notes that (r, -s mod n) is a valid signature as well. My code confirms this on 75 random signatures: for each one, both (r, s) and (r, n - s) verify.
The hash is cut when it is longer than n. For P-256 and SHA-512, the number z is the first 256 bits of the hash. The code checks this against the RFC: the key of the example with SHA-512 and the message “sample” gives the r that the RFC prints, which is 8496a60b5e9b47c825488827e0495b0e3fa109ec4568fd3f8d1097678eb97f00. A shorter hash, such as SHA-1 on P-256, is used as it is.
Verifying a Signature
The verifier has the public key Q, the message, and the pair (r, s). The verifier works through these steps:
- Check that
randsare integers from 1 ton - 1. If not, the signature is invalid. - Check that
Qis a valid point. It must not be the point at infinity, its coordinates must be in range, and it must lie on the curve. Wikipedia adds a third check, thatn * Qis the point at infinity. On the curves of this guide the cofactor is 1, so that holds for every point. - Compute
e = HASH(m)andz, the leftmost bits, as the signer did. - Compute
w = s^-1 mod n, thenu1 = z * w mod nandu2 = r * w mod n. - Compute the point
X = u1 * G + u2 * Q. IfXis the point at infinity, the signature is invalid. - The signature is valid if
requals the x coordinate ofX, modulon.
Verification needs two scalar multiplications where signing needs one. Wikipedia mentions that an efficient implementation computes the inverse only once, and that a method known as Shamir’s trick computes the sum of the two multiplications faster than doing them separately. My code does the plain version.
Why Verification Works
The proof is short. Because multiplication of points distributes over addition, X = u1 * G + u2 * Q = (u1 + u2 * d) * G. Insert the definitions of u1 and u2 to get X = (z + r * d) * s^-1 * G. The signer defined s as k^-1 * (z + r * d), so (z + r * d) * s^-1 equals k. Then X = k * G, which is the point whose x coordinate became r.
This shows that a correct signature verifies. It does not show that a forged one fails. That depends on the hardness of the elliptic curve discrete logarithm problem, as for every scheme of this family. Wikipedia makes the same distinction.
The Nonce Problem
The number k must stay secret, and it must be different for every signature. Wikipedia explains why with two signatures (r, s) and (r, s') on different known messages m and m', made with the same k. Both have the same r, because r comes from k * G. Then s - s' = k^-1 * (z - z'), so an attacker can compute:
k = (z - z') / (s - s') mod n
d = (s * k - z) / r mod n
I ran this attack on P-256 with two test signatures. The messages were “pay Alice 10” and “pay Mallory 1000”. Both signatures had r = 7c8c2f742e39c2f8030f4040dd00613e5d4e7073cc8c4cd6322a41a38c2eb87f. The values s were 66eae4509d13a3c05b32ca57fc340089d01fa6b968f2f919416def4429712dec and 81741fcbb473565d9a4d62fbef3ddd65587ab307521f248135bfe8570611094b. The formulas returned k = aa80774d4d5551caf6fa984193008f1347d534c5708bcf9267849c7517ef2352 and the private key ff1e2613c4894f1f0d5714526df1d33d49d3e27b168e1ab34aae06482e36feb3, and both equal the secret values that the test started with. The attacker needs nothing but the public signatures and the messages.
This is not only a theory. Wikipedia reports two real cases. In December 2010, a group named fail0verflow announced that they had recovered the ECDSA private key that Sony used to sign software for the PlayStation 3. The attack worked because Sony’s k was static instead of random. In August 2013, bugs in the Java class SecureRandom sometimes produced repeated values of k, which let attackers recover keys of Android Bitcoin wallets.
A weak but not repeated k is also dangerous. RFC 6979 warns that even slight biases in the choice of k can be turned into attacks.
Deterministic Nonces with RFC 6979
RFC 6979 removes the need for a random number generator at signing time. It derives k from the private key and the hash of the message with HMAC. The same key and message always give the same k, and a different message gives a k that looks unrelated. The RFC requires that, for anyone who does not know the private key, the map from messages to k values must be computationally indistinguishable from a random function.
The procedure is a generator built from HMAC, which the RFC calls HMAC_DRBG. The procedure in outline has four steps:
- Start with
Vas a string of bytes of value 1 andKas a string of zero bytes, each as long as the hash output. - Mix in the private key and the hash of the message with two rounds of HMAC, once with the separator byte 0 and once with 1.
- Produce bits by repeating
V = HMAC(K, V)until there are enough, and read them as a number. - If the number is in the range from 1 to
n - 1, it isk. Otherwise updateKandVand try again.
My code follows this and reproduces the k of every vector in the RFC. For the key and the message “sample” in the worked example, k is a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60. For two messages with the same key, the nonces differ, as my test confirms.
Signatures made this way are ordinary ECDSA signatures. OpenSSL, which uses random nonces, accepts all 75 of the deterministic signatures that I made, and my code accepts all 75 that OpenSSL made. The RFC names two problems with random nonces. They make implementations harder to test, and they need a source of secure randomness that some systems, such as smartcards, lack. A deterministic nonce solves both at signing time. Key generation still needs good randomness.
Encoding a Signature
A signature is two numbers, and a protocol must write them as bytes. There are two common forms.
- Raw. The number
ras a fixed number of big-endian bytes, followed bys. That is 64 bytes on P-256 and secp256k1, and 96 bytes on P-384. - DER. A SEQUENCE of two INTEGERs, in the format of ASN.1. A DER integer is signed, so a zero byte is added in front when the top bit of the number is set. That makes the length vary. For P-256, my 75 test signatures include DER lengths of 70, 71, and 72 bytes. RFC 8446 says that TLS 1.3 carries an ECDSA signature as a DER-encoded
ECDSA-Sig-Valuestructure.
The example signature in this guide is efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8 in raw form, and in DER form it is 3046022100efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716022100f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8. The DER form has 72 bytes. It starts with 30 46 for the sequence and its length, and then 02 21 00 before r, because r starts with a byte of 80 or more.
A decoder must be strict. My code rejects DER with a non-minimal integer, a negative integer, a wrong length, or extra bytes. A signature that decodes in two different ways is a source of subtle bugs.
Interactive Visualizer
The visualizer above runs real ECDSA on three curves. It has two modes for each curve. In the signing mode you enter a private key and a message, and optionally a nonce k. If you leave the nonce empty, the visualizer uses the RFC 6979 value. The log shows the public key, the hash, the nonce, the point R, and the numbers r and s. It ends with the raw and DER forms and a self-check by verification.
In the verifying mode you enter a public key in uncompressed form, which starts with 04, a message, and a signature of r followed by s. The log shows the range checks, the check of the public key, the hash, w, the two scalars, the two multiplications, and the final comparison. Change one character of the message, or one hexadecimal digit of the signature, to see the signature fail.
The defaults for P-256 and P-384 are the key and the message “sample” from RFC 6979, so the first signature you make equals the RFC’s. The secp256k1 defaults reuse the P-256 key, because the RFC has no vector for that curve. To see the attack, sign two different messages with the same nonce, such as 1234, and compare the two values of r.
A Worked Example
The key pair of appendix A.2.5 of RFC 6979 is on P-256. The private key is c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721, and the public key has these coordinates:
x: 60fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6
y: 7903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299
To sign the message “sample” with SHA-256, the signer first hashes it:
e = af2bdbe1aa9b6ec1e2ade1d694f41fc71a831d0268e9891562113d8a62add1bf
RFC 6979 gives the nonce:
k = a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60
The point k * G has the x coordinate efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716. That is smaller than n, so r is the same number:
r = efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716
s = f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8
The signature agrees with the RFC. Now the verifier computes w = s^-1, which is 9a7ef69c985d9509b6017a803945de4730d8b786975e45e34560361500274eeb. The two scalars are u1 = a9cceaf9beeb5f3ef17670f8eb7f810b486952f78536ee77f31cff76caae5841 and u2 = 48dc5acda3b1ad61b01f62f0ec7e692d6b6ca086e80a10b4241298ec71e7211d. The sum u1 * G + u2 * Q has the x coordinate efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716, which is r, so the signature is valid. The second valid signature of the same message is (r, n - s), with s' = 0834e36ad29a83bf2bc9385e491d6099c8fdf9d1ed67aa7ea5f51f93782857a9.
Python Implementation
This is a complete ECDSA for three curves with the nonce of RFC 6979. It includes the raw and the DER encoding of signatures.
# ecdsa.py
# ECDSA (FIPS 186 and ANSI X9.62) on P-256, P-384, and secp256k1, with the deterministic nonce of RFC 6979.
# Teaching code. It is not constant time, so it must not handle real secrets.
import hashlib
import hmac
class Curve:
def __init__(self, name, p, a, b, gx, gy, n):
self.name, self.p, self.a, self.b = name, p, a, b
self.g = (gx, gy)
self.n = n # the order of the base point. The cofactor is 1 for all three curves.
self.size = (p.bit_length() + 7) // 8 # bytes in a coordinate and in r and s
P256 = Curve(
"P-256",
2 ** 256 - 2 ** 224 + 2 ** 192 + 2 ** 96 - 1,
-3,
0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B,
0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,
0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5,
0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551,
)
P384 = Curve(
"P-384",
2 ** 384 - 2 ** 128 - 2 ** 96 + 2 ** 32 - 1,
-3,
0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF,
0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7,
0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973,
)
SECP256K1 = Curve(
"secp256k1",
2 ** 256 - 2 ** 32 - 977,
0,
7,
0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,
)
CURVES = {c.name: c for c in (P256, P384, SECP256K1)}
# A point is a pair (x, y). The point at infinity is None.
def on_curve(curve, point):
x, y = point
p = curve.p
return 0 <= x < p and 0 <= y < p and (y * y - (x * x * x + curve.a * x + curve.b)) % p == 0
def add(curve, p1, p2):
if p1 is None:
return p2
if p2 is None:
return p1
p = curve.p
(x1, y1), (x2, y2) = p1, p2
if x1 == x2:
if (y1 + y2) % p == 0:
return None
slope = (3 * x1 * x1 + curve.a) * pow(2 * y1, -1, p) % p
else:
slope = (y2 - y1) * pow(x2 - x1, -1, p) % p
x3 = (slope * slope - x1 - x2) % p
return (x3, (slope * (x1 - x3) - y1) % p)
def multiply(curve, k, point):
result = None
while k:
if k & 1:
result = add(curve, result, point)
point = add(curve, point, point)
k >>= 1
return result
def public_key(curve, d):
if not 1 <= d < curve.n:
raise ValueError("the private key must be between 1 and n - 1")
return multiply(curve, d, curve.g)
# ---------- from the hash to a number ----------
def bits2int(data, qlen):
"""The leftmost qlen bits of data, as an integer."""
value = int.from_bytes(data, "big")
if len(data) * 8 > qlen:
value >>= len(data) * 8 - qlen
return value
def hash_to_int(curve, message, hash_name):
return bits2int(hashlib.new(hash_name, message).digest(), curve.n.bit_length())
# ---------- the nonce of RFC 6979 ----------
def rfc6979_nonce(curve, d, message, hash_name):
"""A nonce k from 1 to n - 1, derived from the private key and the message with HMAC."""
q, qlen = curve.n, curve.n.bit_length()
rlen = (qlen + 7) // 8
h1 = hashlib.new(hash_name, message).digest()
z1 = bits2int(h1, qlen)
h1_octets = (z1 - q if z1 >= q else z1).to_bytes(rlen, "big")
x_octets = d.to_bytes(rlen, "big")
def mac(key, data):
return hmac.new(key, data, hash_name).digest()
v = b"\x01" * len(h1)
k = b"\x00" * len(h1)
k = mac(k, v + b"\x00" + x_octets + h1_octets)
v = mac(k, v)
k = mac(k, v + b"\x01" + x_octets + h1_octets)
v = mac(k, v)
while True:
t = b""
while len(t) * 8 < qlen:
v = mac(k, v)
t += v
candidate = bits2int(t, qlen)
if 1 <= candidate < q:
return candidate
k = mac(k, v + b"\x00")
v = mac(k, v)
# ---------- signing and verifying ----------
def sign(curve, d, message, hash_name="sha256", k=None):
"""The signature (r, s). With k=None the nonce is the deterministic one of RFC 6979."""
n = curve.n
e = hash_to_int(curve, message, hash_name)
nonce = rfc6979_nonce(curve, d, message, hash_name) if k is None else k
if not 1 <= nonce < n:
raise ValueError("the nonce must be between 1 and n - 1")
r = multiply(curve, nonce, curve.g)[0] % n
s = pow(nonce, -1, n) * (e + r * d) % n
if r == 0 or s == 0:
raise ValueError("r or s is zero: choose another nonce") # a chance of about 2^-256
return r, s
def verify(curve, public, message, signature, hash_name="sha256"):
r, s = signature
n = curve.n
if not (1 <= r < n and 1 <= s < n):
return False
if not on_curve(curve, public):
return False
e = hash_to_int(curve, message, hash_name)
w = pow(s, -1, n)
point = add(curve, multiply(curve, e * w % n, curve.g), multiply(curve, r * w % n, public))
return point is not None and point[0] % n == r
# ---------- encodings of a signature ----------
def raw_encode(curve, signature):
return signature[0].to_bytes(curve.size, "big") + signature[1].to_bytes(curve.size, "big")
def raw_decode(curve, data):
if len(data) != 2 * curve.size:
raise ValueError("a raw signature has 2 * %d bytes" % curve.size)
return int.from_bytes(data[:curve.size], "big"), int.from_bytes(data[curve.size:], "big")
def _der_integer(value):
body = value.to_bytes(value.bit_length() // 8 + 1, "big") # a leading zero byte keeps the number positive
return b"\x02" + _der_length(len(body)) + body
def _der_length(n):
return bytes([n]) if n < 128 else bytes([0x81, n])
def der_encode(signature):
body = _der_integer(signature[0]) + _der_integer(signature[1])
return b"\x30" + _der_length(len(body)) + body
def der_decode(data):
def length(pos):
if data[pos] < 128:
return data[pos], pos + 1
if data[pos] == 0x81 and data[pos + 1] >= 128:
return data[pos + 1], pos + 2
raise ValueError("bad length")
def integer(pos):
if data[pos] != 2:
raise ValueError("expected an INTEGER")
size, pos = length(pos + 1)
body = data[pos:pos + size]
if not body or len(body) != size or body[0] & 0x80 or (len(body) > 1 and body[0] == 0 and not body[1] & 0x80):
raise ValueError("not a minimal positive integer")
return int.from_bytes(body, "big"), pos + size
if not data or data[0] != 0x30:
raise ValueError("expected a SEQUENCE")
size, pos = length(1)
if pos + size != len(data):
raise ValueError("wrong length")
r, pos = integer(pos)
s, pos = integer(pos)
if pos != len(data):
raise ValueError("extra bytes")
return r, s
if __name__ == "__main__":
# The key pair of RFC 6979, appendix A.2.5, on P-256
curve = P256
d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721
public = public_key(curve, d)
message = b"sample"
r, s = sign(curve, d, message, "sha256")
print("k =", format(rfc6979_nonce(curve, d, message, "sha256"), "064x"))
print("r =", format(r, "064x"))
print("s =", format(s, "064x"))
print("valid: ", verify(curve, public, message, (r, s)))
print("altered:", verify(curve, public, b"sampl3", (r, s)))
print("DER: ", der_encode((r, s)).hex())
Running it produces this output:
k = a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60
r = efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716
s = f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8
valid: True
altered: False
DER: 3046022100efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716022100f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8
I checked this code against several sources before writing it up. It reproduces all 20 signatures printed in RFC 6979 for P-256 and P-384, including the nonce k of each one. The test code reads them from the text of the RFC. This covers SHA-1, SHA-224, SHA-256, SHA-384, and SHA-512 with P-256, so the cutting and the padding of hashes of every length is tested. In Node.js, which uses OpenSSL, all 20 signatures of the RFC verify. Another 75 signatures that my code made on the three curves verify in OpenSSL, in the raw and DER forms, and a changed message is rejected. OpenSSL made the same number of signatures with random nonces, and my code verifies them all. On random inputs, signatures verify, altered messages and signatures are rejected, and values of r or s of 0 or n are refused. The repeated nonce attack recovers the key. The visualizer code contains its own SHA-256, SHA-384, and HMAC, which match OpenSSL in 630 test cases, and it matches all 163 test vectors that this Python code produces. I ran its page in a simulated browser.
Where It Is Used
- TLS 1.3. RFC 8446 says a compliant application MUST support digital signatures with
ecdsa_secp256r1_sha256. The TLS guide covers the handshake. - Bitcoin. The Bitcoin wiki says that Bitcoin uses secp256k1 with the ECDSA algorithm.
- Certificates. An ECDSA key can sign certificates in the way that an RSA key does. The ECC guide mentions ECDSA certificates for servers.
Limitations
This is a teaching implementation, not a production one:
- Not constant time. The multiplication branches on the bits of
kandd, and the inverse takes time that depends on its input. Wikipedia reports a timing attack from 2011 that retrieved the TLS private key of a server using OpenSSL with ECDSA over a binary field, which was fixed in OpenSSL 1.0.0f. Real code must not leak through timing. - Slow arithmetic. Each point operation inverts a number modulo
p. Real libraries use faster coordinates and a combined multiplication for verification. - No key generation. The code takes the private key as given. A real program must draw it from a secure random source.
- Hash choice. The code accepts any hash that Python offers. A real protocol fixes the hash for each curve.
- Malleable signatures. The pair
(r, n - s)is valid whenever(r, s)is. A system that uses the signature bytes as an identifier must account for this. - A given nonce is dangerous. The
koption of the code exists to demonstrate the attack. Never pass a nonce in real code. - Three curves only. The curve P-521 is not included.
Security Status
The security of ECDSA rests on the elliptic curve discrete logarithm problem on the chosen curve, and on the secrecy and uniqueness of every nonce. RFC 6979 mentions that biases in k can be exploited, and the cases of the PlayStation 3 and of the Android wallets show what a repeated k costs. Deterministic nonces remove that risk at signing time, as long as the implementation follows the RFC.
Wikipedia lists concerns that people have raised about ECDSA. Some are political: the trustworthiness of curves produced by NIST has been questioned, and well-known cryptographers have expressed doubts about how they were designed. Others are technical: the standard is hard to implement correctly, it is slow, and it has design flaws that reduce security in implementations that are not defensive. The signature scheme EdDSA derives its nonce from the key and the message by construction. The EdDSA guide and the Ed25519 guide cover it. Like all elliptic curve schemes, ECDSA does not resist a large quantum computer. The Dilithium guide covers a post-quantum signature.
FAQ
What is the difference between ECDSA and DSA?
DSA works in a subgroup of the integers modulo a large prime. ECDSA works in the group of points of an elliptic curve. The structure of the algorithm is the same, with r and s computed from a nonce, a hash, and the private key. ECDSA works with numbers of about the size of the group order, which is 256 bits on P-256. The DSA guide covers the original.
Why must the nonce never repeat?
Because two signatures with the same nonce form a pair of equations with two unknowns. The nonce k and the private key d can then be solved for with simple modular arithmetic. This guide shows the formulas and a working example. The Sony PlayStation 3 key was recovered this way.
Is a deterministic nonce safe?
It is safe when it is derived as in RFC 6979, with a secret key in the derivation. The RFC requires that the map from messages to nonces be indistinguishable from a random function for anyone who does not know the key. Signatures stay compatible with plain ECDSA. An implementation must still follow the RFC exactly.
Why does a signature have two valid forms?
Because (r, s) and (r, n - s) both satisfy the verification equation. The point X computed from the second one is the negative of the point from the first, and it has the same x coordinate. Wikipedia notes this too.
What is the difference between raw and DER signatures?
A raw signature is r and s as two fixed-length numbers. A DER signature is an ASN.1 structure whose length depends on the numbers. TLS 1.3 uses DER, as RFC 8446 states, and OpenSSL signed in both forms in my tests. Make sure that a signer and a verifier agree on the form.
Should I use ECDSA or EdDSA?
For new designs, EdDSA is simpler to use correctly. It derives its nonce from the key and the message by construction and fixes the curve, the hash, and the encoding. ECDSA remains required where standards demand it, for example in TLS 1.3, which says an application must support ecdsa_secp256r1_sha256. See the Ed25519 guide for the alternative.
References
-
Pornin, Thomas. “Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA).” RFC 6979, August 2013. Available at: https://www.rfc-editor.org/rfc/rfc6979
-
Rescorla, Eric. “The Transport Layer Security (TLS) Protocol Version 1.3.” RFC 8446, August 2018. Available at: https://www.rfc-editor.org/rfc/rfc8446
-
Wikipedia contributors. “Elliptic Curve Digital Signature Algorithm.” Wikipedia. Available at: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
-
“Secp256k1.” Bitcoin Wiki, revision of 14 December 2025. Available at: https://en.bitcoin.it/wiki/Secp256k1
-
Fu and Solinas, both of the NSA. “Elliptic Curve Groups modulo a Prime (ECP Groups) for IKE and IKEv2.” RFC 5903, June 2010. Available at: https://www.rfc-editor.org/rfc/rfc5903
-
Johnson, Don, and Menezes, Alfred. “The Elliptic Curve Digital Signature Algorithm (ECDSA).” Certicom Research, 1999.