🖊️
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₂
→ 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-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 | SSH, TLS 1.3, GnuPG, Signal, WireGuard |
| 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 signed with ECDSA P-256 or Ed25519. ~95% of HTTPS connections use EC-based signatures. Certificate Transparency logs record every signed cert.
₿
Bitcoin: ECDSA (secp256k1) + Schnorr (Taproot). Ethereum: ECDSA with public key recovery (saves 32 bytes per tx). Solana, Cosmos: Ed25519.
🔑
OpenSSH defaults to Ed25519 since 2014. 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 | 128-bit | 1,312 B | 2,420 B | 2,560 B |
| ML-DSA-65 | 192-bit | 1,952 B | 3,293 B | 4,032 B |
| ML-DSA-87 | 256-bit | 2,592 B | 4,595 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³ + 2x + 3 mod 97, Generator G = (3, 6), order n = 5 (very small for demonstration). Real ECDSA uses 256-bit primes.
import hashlib, os
# --- Ed25519 via Python's standard library (3.6+) ---
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey, Ed25519PublicKey
)
from cryptography.hazmat.primitives import serialization
# 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
from cryptography.hazmat.backends import default_backend
# Key generation on NIST P-256
private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
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 (uses RFC 6979 deterministic nonce internally)
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") # 70-72 bytes
# 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
// Uses Montgomery modular exponentiation (square-and-multiply)
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;
// Montgomery multiplication (simplified — see Presentation 01)
function automatic logic [WIDTH-1:0] mont_mul(
input logic [WIDTH-1:0] a, b, m
);
// ... full Montgomery reduction pipeline ...
return (a * b) % m; // placeholder for synthesis
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 parameters loaded via config registers (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) state <= IDLE;
else case (state)
IDLE: if (start) state <= SCALAR_MUL;
SCALAR_MUL: if (ec_done) state <= CALC_R;
CALC_R: begin
sig_r <= kG_x % curve_n; // r = x₁ mod n
state <= (sig_r == 0) ? IDLE : MOD_INV;
end
MOD_INV: if (inv_done) state <= CALC_S;
CALC_S: begin
// s = k⁻¹ · (e + r·d) mod n
sig_s <= mont_mul(k_inv,
mont_add(msg_hash, mont_mul(sig_r, priv_key)));
state <= (sig_s == 0) ? IDLE : DONE;
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 TPMs generated RSA keys with low-entropy primes. Though not ECDSA-specific, it demonstrates how hardware RNG flaws undermine any signature scheme. Estonian e-ID cards were vulnerable.
Timing side-channel in a smart card's ECDSA implementation leaked nonce bits through variable-time modular reduction. ~10,000 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)
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)