🖊️
From elliptic-curve mathematics through NIST FIPS 186-5 standards
to SystemVerilog hardware accelerators and Python implementations
Modern Cryptography Series — Presentation 06 | ← / → to navigate | Esc overview
A digital signature binds a message to a signer's identity with three guarantees:
🔒
Only the holder of the private key can produce a valid signature for the associated public key.
🛡️
Any modification to the signed message — even a single bit flip — invalidates the signature.
📜
The signer cannot credibly deny having signed, since only their private key could have produced the signature.
All modern signature schemes follow the hash-then-sign paradigm:
h = H(m) and checks the signature against the public key. No private key needed — only the corresponding public key Q = d·G.
d by constructing a relationship between a random nonce point, the message hash, and the public key — but they differ in how the relationship is computed and what security proofs are available.
Given agreed-upon domain parameters (CURVE, G, n):
Field: 𝔽p where p = 2256 − 2224 + 2192 + 296 − 1
Order: n ≈ 2256 (256-bit private keys)
Security: 128-bit (vs. 3072-bit RSA)
Usage: TLS 1.3, PIV cards, AWS KMS, FIDO2
Field: 𝔽p where p = 2256 − 232 − 977
Order: n ≈ 2256
Cofactor: h = 1 (prime order, no subgroup issues)
Usage: Bitcoin, Ethereum, many blockchains
To sign message m with private key d:
k is used for two different messages m₁, m₂ yielding signatures (r, s₁) and (r, s₂), an attacker can recover the private key:k = (e₁ − e₂) · (s₁ − s₂)⁻¹ mod n → d = (s₁ · k − e₁) · r⁻¹ mod nk was static.
Given message m, signature (r, s), and public key Q:
Substitute Q = d·G and s = k⁻¹(e + r·d) into the verification equation:
u₁·G + u₂·Q = (e·w)·G + (r·w)·(d·G) = w·(e + r·d)·G = (s⁻¹)·(s·k)·G = k·G
The x-coordinate of k·G is exactly r, so the check passes. ✓
Eliminates the random nonce k entirely, making signatures reproducible and safe from RNG failures.
① Compute message hash h₁ = H(m)
② Initialize HMAC-DRBG with seed = privKey ‖ h₁
③ Generate candidate k via HMAC-SHA256 iterations
④ If k ∈ [1, n−1] and r ≠ 0 and s ≠ 0, accept
⑤ Otherwise, reseed and retry
✓ Same (key, message) → same signature
✓ No RNG required at signing time
✓ k is unique per (key, message) pair
⚠ Approved in FIPS 186-5 § 6.3.2
⚠ Determinism can ease fault injection attacks
Proposed by Claus-Peter Schnorr in 1989. Simpler, provably secure under the Random Oracle Model, and crucially — linear.
✓ Provable security — reduces to ECDLP in ROM
✓ Linearity: s₁ + s₂ is valid for P₁ + P₂ (with nonce R₁ + R₂ and a shared challenge e)
→ enables native multi-signatures (MuSig2)
✓ Non-malleable — unique (R, s) per (m, d, k)
✓ Batch verification — verify n sigs faster than n × single
✓ Fixed 64-byte encoding (vs. DER 70-72 bytes for ECDSA)
A Schnorr variant on twisted Edwards curves, designed by Bernstein et al. (2011). Deterministic nonce, constant-time, misuse-resistant.
| Curve | edwards25519: −x² + y² = 1 + dx²y² |
| Field | 𝔽p, p = 2255 − 19 |
| Order ℓ | 2252 + 27742…336 (≈252 bits) |
| Cofactor | h = 8 |
| Hash | SHA-512 |
| Key size | 32 bytes private, 32 bytes public |
| Sig size | 64 bytes |
| Security | ~128-bit |
| Property | ECDSA | EdDSA (Ed25519) | Schnorr (BIP 340) |
|---|---|---|---|
| Curve family | Weierstrass (P-256, secp256k1) | Twisted Edwards (ed25519, ed448) | Weierstrass (secp256k1) |
| Nonce | Random (or RFC 6979) | Deterministic (always) | Aux-randomized |
| Signature size | 70–72 bytes (DER) / 64 (raw) | 64 bytes | 64 bytes |
| Public key size | 33 bytes (compressed) | 32 bytes | 32 bytes (x-only) |
| Provable security | Not in standard model | ROM (Schnorr-based) | ROM |
| Signature malleability | Yes (s → n − s) | No | No |
| Batch verification | Not natively | Yes | Yes |
| Multi-signature | Complex | Possible | Native (MuSig2, FROST) |
| Public key recovery | Yes (save bandwidth) | No | No |
| FIPS approved | FIPS 186-5 | FIPS 186-5 | Not yet |
| Speed (sign, ed25519 ≈ 1.0×) | ~1.2× | 1.0× | ~1.1× |
Schnorr's linearity enables n-of-n and t-of-n signing protocols that produce a single standard signature.
① Each signer i has keypair (dᵢ, Pᵢ)
② Aggregate key: Pagg = Σ aᵢ · Pᵢ (with key aggregation coefficients aᵢ to prevent rogue-key attacks)
③ Each signer provides two nonces per round (only 2 rounds!)
④ Partial signatures: sᵢ = kᵢ + e · aᵢ · dᵢ
⑤ Final: s = Σ sᵢ → standard Schnorr sig (R, s)
Verifier sees single 64-byte signature, indistinguishable from solo Schnorr sig.
Flexible Round-Optimized Schnorr Threshold signatures allow any t of n participants to produce a valid signature.
① Distributed key generation (DKG) — each party gets a share
② Signing requires only t signers (2-round protocol)
③ Result: standard Schnorr signature — verifier is unaware of threshold
USE CASE Custody wallets, institutional signing, key escrow, MPC-TSS
Blind signatures: Signer signs without seeing the message — used in anonymous credential schemes and e-cash (Chaum, 1983).
Ring signatures: Prove membership in a set without revealing which member signed — used in Monero (CryptoNote) for transaction privacy.
| Standard | Algorithms | Status | Key Usage |
|---|---|---|---|
| FIPS 186-5 (2023) | RSA (PKCS #1 v1.5 & PSS), ECDSA, EdDSA | Current | US federal, TLS, code signing, PKI |
| RFC 6979 | Deterministic DSA/ECDSA | Active | Constrained devices, test reproducibility |
| RFC 8032 | Ed25519, Ed25519ph, Ed448 | Active | OpenSSH, TLS 1.3, GnuPG, DNSSEC, Tor |
| SP 800-186 | Curve parameters (P-256, P-384, ed25519, ed448) | Current | Companion to FIPS 186-5 |
| BIP 340 | Schnorr over secp256k1 | Bitcoin | Bitcoin Taproot (activated Nov 2021) |
| FIPS 204 (2024) | ML-DSA (CRYSTALS-Dilithium) | PQC | Post-quantum replacement for ECDSA |
| FIPS 205 (2024) | SLH-DSA (SPHINCS+) | PQC | Hash-based PQ signatures (conservative) |
🌐
Server certificates are signed with RSA or ECDSA (P-256/P-384); Ed25519 is allowed by TLS 1.3 but public CAs do not issue it. Certificate Transparency logs record every publicly trusted cert.
₿
Bitcoin: ECDSA (secp256k1) + Schnorr (Taproot). Ethereum: ECDSA with public key recovery (transactions carry no public key). Solana and Cosmos validators: Ed25519.
🔑
OpenSSH has supported Ed25519 since 6.5 (2014); ssh-keygen defaults to it since 9.5 (2023). GnuPG supports Ed25519 since 2.1. FIDO2/WebAuthn uses ECDSA P-256 for hardware security keys.
📱
Apple Secure Enclave: ECDSA P-256. Android Keystore: ECDSA. ARM TrustZone: ECDSA/EdDSA via hardware crypto accelerators.
📦
Windows Authenticode, macOS codesign, Android APK signing, Linux kernel module signing — all use digital signatures to prevent supply-chain attacks.
🏛️
X.509 certificate chains: Root CA → Intermediate CA → Leaf. Each link is a digital signature (RSA or ECDSA) binding an entity to its public key.
Digital signature operations are bottlenecks in TLS handshakes, blockchain validation, and IoT authentication. Hardware acceleration is essential.
| Platform | Curve | Sign | Verify | Slices |
|---|---|---|---|---|
| Virtex-5 | K-163 | 0.27 ms | 0.54 ms | 18.5k |
| Virtex-6 | B-163 | 0.8 ms | 0.4 ms | ~13k LUT |
| Alveo U250 | P-256 | — | 0.76 ms | ~50k LUT |
| Process | Area | Freq | Power |
|---|---|---|---|
| 45nm CMOS | 0.257 mm² | 532 MHz | 63 mW |
| 45nm (ECC only) | 0.121 mm² | 990 MHz | 39 mW |
Key bottleneck: EC scalar multiplication dominates ~90% of signing time. Montgomery ladder preferred for constant-time (SPA-resistant) operation.
Signature implementations leak secrets through timing, power, and electromagnetic emanations.
Timing attacks: Non-constant-time modular inversion or conditional branches during scalar multiplication reveal bits of k or d.
Simple Power Analysis (SPA): Single power trace distinguishes point-add (P+Q) from point-double (2P) in the double-and-add ladder.
Differential Power Analysis (DPA): Statistical correlation across many traces recovers key bits.
Electromagnetic (EM) attacks: Near-field probes capture leakage from on-chip wires.
Fault injection: Voltage glitches or laser pulses skip instructions or corrupt intermediate values, enabling Bellcore-style attacks on deterministic ECDSA.
Constant-time code: No data-dependent branches or memory lookups. Ed25519 is designed for this — all operations are uniform.
Montgomery ladder: Always performs both add and double, eliminating SPA distinguishability.
Scalar blinding: Replace d with d + r·n (same result mod n, but randomized trace).
Point blinding: Add random point before scalar mul, subtract after.
Coordinate randomization: Multiply projective coordinates by random λ — changes power profile without affecting result.
Redundant computation: Dual-rail logic, error detection codes for fault resistance. ~20% area overhead.
Shor's algorithm breaks ECDSA/EdDSA/Schnorr on a cryptographically relevant quantum computer. NIST has standardized replacements.
Based on Module Learning With Errors (MLWE) lattice problem.
Derived from CRYSTALS-Dilithium submission.
| Parameter Set | Security | PK | Sig | SK |
|---|---|---|---|---|
| ML-DSA-44 | Cat. 2 (~128) | 1,312 B | 2,420 B | 2,560 B |
| ML-DSA-65 | Cat. 3 (~192) | 1,952 B | 3,309 B | 4,032 B |
| ML-DSA-87 | Cat. 5 (~256) | 2,592 B | 4,627 B | 4,896 B |
Sign: ~100 µs (software). Verify: ~50 µs. Competitive with ECDSA for speed, but much larger keys and signatures.
Based on hash functions only (SPHINCS+).
Conservative assumption: only needs hash collision/preimage resistance.
| Parameter Set | Security | PK | Sig |
|---|---|---|---|
| SLH-DSA-128s | 128-bit | 32 B | 7,856 B |
| SLH-DSA-128f | 128-bit | 32 B | 17,088 B |
| SLH-DSA-256s | 256-bit | 64 B | 29,792 B |
Very slow signing (~100 ms) but tiny public keys. s = small sigs, f = fast signing.
Simplified ECDSA over a tiny curve for educational purposes. Uses a toy prime field.
Toy curve: y² = x³ + 5 mod 97, which has 79 points (a prime, so cofactor 1); generator G = (1, 43), order n = 79. Real ECDSA uses 256-bit primes.
# Ed25519 via the pyca 'cryptography' package (pip install cryptography;
# not the standard library). public_bytes_raw() needs version 40 or later.
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# Key generation
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# Sign
message = b"Digital signatures are provably secure"
signature = private_key.sign(message) # 64 bytes, deterministic
print(f"Public key : {public_key.public_bytes_raw().hex()}")
print(f"Signature : {signature.hex()}")
print(f"Sig length : {len(signature)} bytes")
# Verify
try:
public_key.verify(signature, message)
print("✓ Signature valid")
except Exception:
print("✗ Signature INVALID")
# Tamper test
try:
public_key.verify(signature, b"Tampered message!")
print("✓ Signature valid") # Should NOT reach here
except Exception:
print("✗ Tampered message detected — signature invalid")
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
# Key generation on NIST P-256
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# Extract private scalar d
d = private_key.private_numbers().private_value
print(f"Private key d: {d:#066x}")
# Sign: OpenSSL draws a random nonce k. For RFC 6979 use
# ec.ECDSA(hashes.SHA256(), deterministic_signing=True) (v43+)
message = b"ECDSA on NIST P-256"
signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
print(f"Signature (DER): {signature.hex()}")
print(f"DER length: {len(signature)} bytes") # usually 70-72
# Decode (r, s) from DER
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
r, s = decode_dss_signature(signature)
print(f"r = {r:#066x}")
print(f"s = {s:#066x}")
# Verify
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
print("✓ ECDSA signature verified")
The modular inverse k⁻¹ mod n is the most expensive non-EC operation in ECDSA signing. Fermat's little theorem: k⁻¹ = kn−2 mod n.
module mod_inverse #(
parameter int WIDTH = 256
)(
input logic clk,
input logic rst_n,
input logic start,
input logic [WIDTH-1:0] k, // value to invert
input logic [WIDTH-1:0] n, // modulus (curve order)
output logic [WIDTH-1:0] k_inv, // k^{-1} mod n
output logic done
);
// Fermat's little theorem: k^{-1} = k^{n-2} mod n
// Right-to-left square-and-multiply, one exponent bit per cycle
logic [WIDTH-1:0] exp; // exponent = n - 2
logic [WIDTH-1:0] base_reg; // current base (squared each cycle)
logic [WIDTH-1:0] result; // accumulator
logic [$clog2(WIDTH)-1:0] bit_idx;
logic running;
assign exp = n - 2;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
done <= 1'b0;
running <= 1'b0;
end else if (start) begin
base_reg <= k;
result <= {{(WIDTH-1){1'b0}}, 1'b1}; // 1
bit_idx <= '0;
running <= 1'b1;
done <= 1'b0;
end else if (running) begin
// Square-and-multiply: check bit_idx of exponent
if (exp[bit_idx])
result <= mont_mul(result, base_reg, n);
base_reg <= mont_mul(base_reg, base_reg, n);
if (bit_idx == WIDTH - 1) begin
running <= 1'b0;
done <= 1'b1;
end
bit_idx <= bit_idx + 1;
end
end
assign k_inv = result;
// Behavioural modular multiply (simulation model). A real core uses a
// Montgomery multiplier here: see Presentation 01 and deck 05.
function automatic logic [WIDTH-1:0] mont_mul(
input logic [WIDTH-1:0] a, b, m
);
logic [2*WIDTH-1:0] p;
p = {{WIDTH{1'b0}}, a} * {{WIDTH{1'b0}}, b}; // full 2W-bit product
return WIDTH'(p % {{WIDTH{1'b0}}, m});
endfunction
endmodule
module ecdsa_sign_top #(
parameter int N = 256 // Curve order bit-width
)(
input logic clk, rst_n, start,
input logic [N-1:0] msg_hash, // e = H(m), pre-computed
input logic [N-1:0] priv_key, // d
input logic [N-1:0] nonce_k, // k (from TRNG or RFC 6979)
// curve_n, mont_mul, mont_add: config register / modular ALU (omitted)
output logic [N-1:0] sig_r,
output logic [N-1:0] sig_s,
output logic valid
);
typedef enum logic [2:0] {
IDLE, SCALAR_MUL, CALC_R, MOD_INV, CALC_S, DONE
} state_t;
state_t state;
logic [N-1:0] kG_x; // x-coord of k·G
logic [N-1:0] k_inv; // k^{-1} mod n
logic ec_done, inv_done;
// Sub-modules (instantiation ports abbreviated)
ec_point_mul #(.N(N)) u_pmul (.*); // k·G → (kG_x, kG_y)
mod_inverse #(.WIDTH(N)) u_inv(.*); // k^{-1} mod n
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin state <= IDLE; valid <= 1'b0; end
else case (state)
IDLE: if (start) state <= SCALAR_MUL;
SCALAR_MUL: if (ec_done) state <= CALC_R;
CALC_R: begin
logic [N-1:0] r_v;
r_v = kG_x % curve_n; // r = x₁ mod n
sig_r <= r_v; // test r_v, not the old sig_r
state <= (r_v == 0) ? IDLE : MOD_INV; // r = 0: new k
end
MOD_INV: if (inv_done) state <= CALC_S;
CALC_S: begin
// s = k⁻¹ · (e + r·d) mod n
logic [N-1:0] s_v;
s_v = mont_mul(k_inv,
mont_add(msg_hash, mont_mul(sig_r, priv_key)));
sig_s <= s_v;
state <= (s_v == 0) ? IDLE : DONE; // s = 0: new k
end
DONE: begin valid <= 1'b1; state <= IDLE; end
endcase
end
endmodule
| Implementation | Algorithm | Sign | Verify | Throughput |
|---|---|---|---|---|
| OpenSSL 3.x (x86-64) | ECDSA P-256 | ~55 µs | ~175 µs | ~18k sign/s |
| OpenSSL 3.x (x86-64) | Ed25519 | ~42 µs | ~115 µs | ~24k sign/s |
| libsodium (x86-64) | Ed25519 | ~32 µs | ~88 µs | ~31k sign/s |
| Bitcoin Core (x86-64) | Schnorr secp256k1 | ~48 µs | ~52 µs | ~19k verify/s |
| FPGA Virtex-5 | ECDSA K-163 | ~270 µs | ~540 µs | ~1.8k sign/s |
| FPGA Alveo U250 | ECDSA P-256 | — | ~760 µs | ~1.3k verify/s |
| ASIC 45nm | ECDSA B-163 | ~6 µs | — | ~160k sign/s |
| Software (ref) | ML-DSA-44 | ~100 µs | ~50 µs | ~10k sign/s |
| ASIC PQ (12nm) | ML-DSA-87 | ~97 µs | ~58 µs | ~10k sign/s |
Sony used a static nonce k for signing PS3 firmware updates with ECDSA. The fail0verflow group recovered the private key instantly using the nonce-reuse equation. Result: full PS3 jailbreak, homebrew code execution.
Android's SecureRandom had a flaw producing insufficient entropy for ECDSA nonces. Multiple Bitcoin wallets generated duplicate k values, allowing attackers to extract private keys and steal funds.
Infineon's RSA library (TPMs, smart cards) generated primes with a special structure that made the keys practical to factor. Though not ECDSA-specific, it shows how flawed key generation in hardware undermines any signature scheme. Estonian e-ID cards were vulnerable.
Timing side-channel in a smart card's ECDSA implementation leaked the bit-length of each nonce through variable-time scalar multiplication. A few thousand signatures were enough to recover the private key via lattice attacks (Hidden Number Problem).
▸ Digital signatures provide authentication, integrity, and non-repudiation
▸ All modern schemes use hash-then-sign over elliptic curve groups
▸ ECDSA, EdDSA, and Schnorr are all rooted in ElGamal's framework
▸ The ECDLP trapdoor: Q = d·G is easy; recovering d from Q is infeasible
▸ Nonce security is paramount — reuse leaks the private key
Best Ed25519 for new systems (fast, deterministic, misuse-resistant)
Good ECDSA P-256 for FIPS compliance and existing infrastructure
BTC Schnorr (BIP 340) for Bitcoin/Taproot ecosystems
Future ML-DSA for post-quantum readiness (FIPS 204)
▸ FIPS 186-5 (2023): ECDSA + EdDSA approved; DSA deprecated
▸ RFC 8032: Ed25519 & Ed448 specification
▸ RFC 6979: Deterministic ECDSA nonce generation
▸ FIPS 204/205 (2024): ML-DSA & SLH-DSA for post-quantum
▸ EC scalar multiplication dominates ~90% of sign/verify time
▸ Montgomery ladder preferred for constant-time (SPA-resistant) operation
▸ ASIC 45nm achieves ~160k ECDSA signatures/second
▸ Post-quantum ML-DSA accelerators emerging (0.23 mm², 1.2 GHz)
Standards:
FIPS 186-5, Digital Signature Standard (2023)
NIST SP 800-186, Elliptic Curve Domain Parameters (2023)
RFC 6979, Deterministic DSA/ECDSA (2013)
RFC 8032, Edwards-Curve Digital Signature Algorithm (2017)
BIP 340, Schnorr Signatures for secp256k1 (2021)
FIPS 204, Module-Lattice-Based Digital Signature Standard (2024)
FIPS 205, Stateless Hash-Based Digital Signature Standard (2024)
Papers:
ElGamal, "A Public Key Cryptosystem…" IEEE ToIT (1985)
Schnorr, "Efficient Signature Generation…" J. Cryptology (1991)
Johnson, Menezes, Vanstone, "The ECDSA," IJIS (2001)
Bernstein et al., "High-speed high-security signatures," CHES (2011)
Nick, Ruffing, Seurin, "MuSig2," CRYPTO (2021)
Komlo, Goldberg, "FROST," SAC (2020); RFC 9591 (2024)
Ducas et al., "CRYSTALS-Dilithium," TCHES (2018)
Implementation:
libsodium — Ed25519 reference library
OpenSSL 3.x — ECDSA/EdDSA provider
Bitcoin Core — secp256k1 + Schnorr library
"Fast Hardware Implementation of ECDSA" — IEEE (2016)
"Lightweight HW Accelerator for PQ Digital Signature" — IACR ePrint (2022)