Comparing Signature Schemes — ECDSA, EdDSA, Schnorr, BLS, and Forgery Pitfalls TODO
Concept
ECDSA is an elliptic-curve signature scheme that requires a secret random nonce per signature; if that nonce is reused or biased, the private key can be recovered from just two signatures. It also has signature malleability — both (r, s) and (r, -s mod n) are valid — so Ethereum constrains s to the lower half of the range; in exchange, the property that lets you recover the public key from a signature is what makes the ecrecover pattern possible. EdDSA, a Schnorr-family scheme, derives the nonce deterministically from the private key and the message hash, structurally eliminating nonce-reuse incidents — but implementations vary in cofactor handling and encoding-normalization checks, creating a consensus risk where one library accepts a signature that another rejects. Schnorr signatures' linear structure lets multiple keys and signatures be aggregated into one; BLS uses pairings to make signatures short and lets an unlimited number of them aggregate, which suits collecting a validator set's signatures, though verification is comparatively heavy. A common trap across aggregate schemes is the rogue-key attack, where an attacker crafts their own key relative to someone else's public key — defending against it needs proof of possession or a MuSig-style commitment procedure.
Subtle differences in signature-verification code translate directly into stolen funds or a consensus split between nodes, and nonce handling, malleability, and rogue-key attacks have each caused real incidents repeatedly. Choosing a scheme is choosing which pitfall you're taking on.
Code & Formula
# 서명 스킴 비교 — ECDSA의 nonce 재사용이 개인키를 복원시키는 함정을 토이 곡선 위에서 재현한다.
# 실제 secp256k1 대신 작은 소수 곡선으로 개념만 시연 (교육용, 프로덕션 서명엔 검증된 라이브러리 사용).
# 아주 작은 유한체 위의 "장난감" 타원곡선 대신, ECDSA 서명 수식만 정수 mod n 산술로 재현한다.
# 서명 공식: s = k^-1 * (h + r * priv_key) mod n (r 은 nonce k 로부터 유도된 값이라고 가정)
n = 1000000007 # 그룹 위수 역할을 하는 소수 (토이 값)
priv_key = 123456789 % n
def sign(h, k, r):
"""h: 메시지 해시, k: nonce, r: nonce로부터 나온 값(실제론 k*G의 x좌표)"""
k_inv = pow(k, -1, n)
s = (k_inv * (h + r * priv_key)) % n
return s
def recover_priv_key_from_nonce_reuse(h1, s1, h2, s2, r):
"""같은 nonce r로 서명한 서명 두 개만으로 개인키를 복원한다."""
# s1 - s2 = k^-1 * (h1 - h2) => k = (h1 - h2) / (s1 - s2)
k = ((h1 - h2) * pow((s1 - s2) % n, -1, n)) % n
# s1 = k^-1 * (h1 + r * priv) => priv = (s1 * k - h1) / r
recovered = ((s1 * k - h1) * pow(r, -1, n)) % n
return recovered
reused_nonce_k = 999999937
r = (reused_nonce_k * 7) % n # r 은 k 로부터 결정론적으로 유도된다고 가정 (토이 모델)
h1, h2 = 42, 4242 # 서로 다른 두 메시지의 해시
s1 = sign(h1, reused_nonce_k, r)
s2 = sign(h2, reused_nonce_k, r) # 실수로 같은 nonce 재사용
recovered_priv = recover_priv_key_from_nonce_reuse(h1, s1, h2, s2, r)
print("actual private key:", priv_key)
print("recovered from two signatures sharing a nonce:", recovered_priv)
print("nonce reuse breaks ECDSA:", recovered_priv == priv_key)
print()
print("EdDSA fixes this by deriving nonce deterministically as hash(priv_key || message),")
print("so the same key+message always reuses the SAME nonce safely (no accidental reuse across msgs).")
docs/code/algorithms/algorithms-85.py
Exercise
Produce two ECDSA signatures over different messages using the same nonce, then actually solve the resulting pair of equations to recover the private key.
Practical Connection
If Verex takes signed off-chain orders and verifies them on-chain, how you handle signature malleability, nonce reuse prevention, and domain separation (EIP-712) is exactly the defense line against order forgery and replay attacks.
Where it lands in Jayverse
- Verex: pick the aggregation scheme explicitly and defend against rogue keys. If the CLOB ever batches many market-maker signatures, BLS pairing-based aggregation suits an open, validator-like set while Schnorr/MuSig fits a small fixed multisig — document which is used and require proof of possession so an attacker can't craft a key relative to someone else's.
- Wallet: test that EIP-712 signature verification rejects the malleable counterpart, not just the canonical one. Since ecrecover relies on ECDSA's public-key-recovery property, add a test that a Wallet-signed payload's (r, -s mod n) variant is rejected, not silently accepted as a second valid signature.
Key expressions
| Expression | 뜻 · 쓰이는 자리 |
|---|---|
| in exchange | 그 대가로 · 단점을 감수하는 대신 얻는 장점을 말할 때. "the property that lets you recover the public key" |
| structurally eliminate | 구조적으로 아예 없애버리다 · 설계 자체가 문제를 원천 차단한다는 뜻. "structurally eliminating nonce-reuse incidents" |
| rogue-key attack | 불량 키 공격 · 남의 공개키를 이용해 자기 키를 조작하는 공격. "the rogue-key attack, where an attacker crafts their own key" |
| proof of possession | 소유 증명 · 개인키를 실제로 갖고 있음을 증명하는 절차. "needs proof of possession or a MuSig-style commitment procedure" |
| translate directly into | 곧바로 ~로 이어지다 · 작은 버그가 큰 사고로 직결된다는 뜻. "translate directly into stolen funds or a consensus split" |
| take on (a pitfall) | 결점·위험을 떠안다, 감수하다 · 어떤 방식을 고르면 그에 딸린 위험도 함께 진다는 뜻. "choosing which pitfall you're taking on" |
| ECDSA | 타원곡선 전자서명 알고리즘(Elliptic Curve Digital Signature Algorithm) · 논스 재사용 시 개인키가 노출되는 서명 방식. "ECDSA is an elliptic-curve signature scheme that requires a secret random nonce" |
| EdDSA | 에드워즈 곡선 전자서명(Edwards-curve Digital Signature Algorithm) · 논스를 결정론적으로 유도해 재사용 위험을 구조적으로 없앤 방식. "EdDSA, a Schnorr-family scheme, derives the nonce deterministically" |
| Schnorr | 슈노어 서명(Schnorr signature) · 여러 키와 서명을 하나로 합칠 수 있는 선형 구조를 가진 서명 방식. "Schnorr signatures' linear structure lets multiple keys and signatures be aggregated" |
| BLS | BLS 서명(Boneh-Lynn-Shacham signature) · 페어링으로 서명을 짧게 만들고 무제한 집계를 지원, 검증자 집합 서명 수집에 적합. "BLS uses pairings to make signatures short and lets an unlimited number" |
| MuSig | MuSig 다중서명 프로토콜(MuSig multi-signature scheme) · 불량 키 공격을 막기 위한 커밋먼트 절차에 쓰임. "a MuSig-style commitment procedure" |
If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.