How a signature actually signs
Elliptic curves, signing math, and the one mistake that drains every wallet that makes it.
In the previous post you built a private key out of mouse jitter. Twelve words on paper, a 256-bit integer behind them, an address on five blockchains. That is the identity part. This post is about the authorization part — how you take that key and prove to a network that yes, this transaction is mine, without ever showing the key itself.
The whole trick rests on two pieces of math: a curve, and a function that turns “private key plus message” into a small chunk of bytes anyone can verify but no one can forge. By the end of this post you will sign a real ECDSA and Ed25519 signature in your browser, watch what happens when randomness misbehaves, and understand why a few duplicate random numbers in 2010 cost Sony its entire console security model.
1. The curve you’ve been hearing about
The phrase “elliptic curve” sounds like an ellipse. It is not. The curve secp256k1 — the one Bitcoin and Ethereum and Cosmos all sign with — is the set of points (x, y) satisfying:
y² = x³ + 7
…where x and y are not real numbers but integers modulo a very large prime. Visually it would look like a smooth swooping shape over the reals, but over a finite field it becomes a cloud of discrete points. You can’t draw 2²⁵⁶ of them, so the playground below uses a toy version (p = 97) — sparse enough to render, dense enough to feel like a curve.
The curve has two operations:
Point addition. Given two points P and Q on the curve, draw a line through them. The line hits the curve at exactly one other point. Flip that point across the x-axis. That is P + Q. This is a real operation, not metaphor; the algebra is just modular arithmetic.
Scalar multiplication. k · G means “add G to itself k times”. For small k you can do this on paper. For k = a 256-bit private key, it takes a few microseconds on a phone using a technique called double-and-add.
There is a special point called the generator, denoted G. It is a fixed constant baked into the curve spec — every wallet, every node, every chain agrees on the same G. Your public key is just:
pubkey = priv · G
Drag the slider below and watch k · G hop around. Notice that even though the points look randomly scattered, they’re following a precise mathematical structure.
Why can’t anyone work backwards — given pubkey and G, recover priv? Because there is no shortcut. To find priv, you would have to try every possible value (or use general-purpose discrete-log algorithms that scale to roughly 2^(n/2) — still astronomically large for 256-bit keys). This is the elliptic curve discrete logarithm problem, and the entire reason wallets work is that nobody has solved it.
2. Why secp256k1, and why Ed25519
Bitcoin chose secp256k1 in 2009. The reasons are partly historical, partly paranoid:
- The “k1” suffix means it’s a Koblitz curve, a family with a specific algebraic structure that makes point operations unusually fast on commodity CPUs.
- It is not NIST’s P-256 (
secp256r1). Satoshi explicitly picked a curve outside NIST’s standardization track, on the (well-founded) suspicion that NIST curves might have parameters chosen for surveillance-friendly reasons. - Ethereum (2015) inherited the choice for tooling compatibility. Cosmos (2019) inherited it for the same reason.
In 2011, Daniel Bernstein and collaborators designed Ed25519 — a different signing scheme over a different curve (Edwards form of Curve25519). Ed25519 was designed explicitly to fix ECDSA’s footguns:
- Signatures are deterministic — there’s no random nonce that can leak the key when it’s reused (we’ll see why this matters in Section 5).
- The math is simpler, with no special-case handling for “infinity” points.
- Verification is faster, and many signatures can be verified in a single batch operation.
Solana (2020) and Stellar (2014) picked Ed25519 for these reasons. A small note that confuses people: Curve25519 is the underlying curve, designed for Diffie-Hellman key exchange. Ed25519 is the signature scheme built on top of it. They share parameters but they’re not the same thing.
3. Signing a message: ECDSA
Here is how Bitcoin and Ethereum actually sign. Given a private key priv and a message m:
- Hash the message into a fixed-size value.
e = SHA-256(m), interpreted as a 256-bit integer. - Pick a random one-time secret
k, where0 < k < n(the group order). - Compute the point
R = k · G. Take its x-coordinate modulo n. Call thisr. - Compute
s = k⁻¹ · (e + r · priv) mod n. - The signature is
(r, s). About 64 bytes.
To verify, anyone with (pubkey, m, r, s) does:
- Compute
e = SHA-256(m). - Compute
R' = s⁻¹ · (e · G + r · pubkey). - Check that
R'.x mod n == r.
If yes, the sig is valid. The verifier never sees priv and never sees k. The math binds the signature uniquely to that specific message, that specific private key, and that specific random k.
Try it. Type a message, sign it twice, then verify:
A few things to notice:
- ECDSA changes every click. The signature is random because
kis random — so the same message signs differently every time. (Modern Bitcoin and Ethereum libraries use RFC 6979 deterministic ECDSA by default, wherekis derived deterministically from the private key and the message. The demo above intentionally bypasses RFC 6979 to show classic non-deterministic behavior — we’ll need it for Section 5.) - EdDSA does not change. Same key, same message → same 64 bytes, forever. This is by design.
- Tampering destroys both. Toggle the tamper switch (which appends an invisible space to the message) and verify again. Both signatures fail. The hash changed, so the math doesn’t line up.
4. EdDSA: same idea, sharper edges
Ed25519 follows the same broad shape — produce a one-time point R, combine with the private key, output (R, s) — but every step is hardened:
- Hash
(priv || message)to getk. No randomness involved. - Compute
R = k · Gon the Edwards-form curve. - Compute
s = k + hash(R, pubkey, message) · priv mod L. - The signature is
(R, s). 64 bytes, fixed.
The key idea: replace the random nonce with a deterministic one derived from the message. The signer no longer needs a working random number generator. The verifier has no way to tell — the sig is just as opaque — but the entire class of “your wallet got drained because your RNG was buggy” attacks vanishes.
RFC 6979 (2013) ported this idea back to ECDSA. The Bitcoin and Ethereum ecosystems adopted it within a year. If you’re using a recent wallet library, your ECDSA signatures are already deterministic. Older code, custom code, and embedded firmware are where things go wrong — which is where the next section’s story begins.
5. The one mistake that breaks everything
Look back at the ECDSA equations:
s = k⁻¹ · (e + r · priv)
Suppose you sign two different messages with the same k. Same k means same R = k · G, which means same r (because r = R.x mod n).
s₁ = k⁻¹ · (e₁ + r · priv)
s₂ = k⁻¹ · (e₂ + r · priv)
Subtract:
s₁ − s₂ = k⁻¹ · (e₁ − e₂)
Anyone who sees both signatures has s₁, s₂, e₁, e₂. They can solve for k:
k = (e₁ − e₂) · (s₁ − s₂)⁻¹ mod n
Now k is known. Plug it back into the original sign equation:
priv = (s₁ · k − e₁) · r⁻¹ mod n
Five lines of algebra. The private key is exposed. The wallet is drained.
This is not a bug in the math — it is the math working correctly. The signature scheme assumes k is single-use; reusing it is a violation of the contract.
It has happened a lot:
- PlayStation 3, 2010. Sony’s firmware signing process used a fixed
kfor every signature. The hacker collective fail0verflow and George “geohot” Hotz observed the matchingrvalues, recovered Sony’s master signing key in days, and demonstrated it on stage at the 27th Chaos Communication Congress. Sony sued. Sony lost. - Android, 2013. A bug in Android’s
SecureRandomproduced a small set ofkvalues across many wallets. Within hours of the bug being disclosed, attackers had drained Bitcoin from any address whose owner had ever signed two transactions on a buggy device. - Multiple browser wallets, 2018–2022. Custom signing code in early dApp wallets failed to seed
kcorrectly. Funds quietly vanished.
To feel the attack, watch it happen below. The scenario is pre-built: a wallet has signed two transactions with k = 42. Both signatures are public. Click “recover private key”:
Once the attacker has the key, they can sign whatever they like — including a brand-new transaction sending all the funds elsewhere. Try the “forge a new signature” box. The forged signature verifies against the victim’s public key, because at this point the attacker is the victim.
The only defense is to never reuse k. EdDSA achieves this by deriving k deterministically from the private key and message. RFC 6979 ECDSA does the same. Both fix the vulnerability at the protocol level so that even a totally broken RNG cannot cause it.
6. Ethereum’s clever trick: signature recovery
Most chains ship signatures with the public key attached. The verifier needs both, so they go on the wire together: pubkey (32 or 33 bytes) + signature (64 bytes) ≈ 96+ bytes of authentication overhead per transaction.
Ethereum does it differently. Ethereum signatures are (v, r, s) — three values, where v is a single byte. The pubkey is not transmitted. The verifier recovers the pubkey from (message, r, s, v) directly.
Why does this work? Recall that r is the x-coordinate of the point R = k · G. The curve is symmetric across the x-axis: for any x, there are usually two valid y values. v tells the verifier which y to pick (and, in modern Ethereum, also embeds the chain ID for replay protection — but that’s Post 2b’s problem). Once you have R, you can rearrange the verify equation:
R = s⁻¹ · (e · G + r · pubkey)
…and solve for pubkey instead of checking against a known one. You get back the public key that must have signed this message.
The saving: every Ethereum tx is ~32 bytes smaller than the same tx would be on a chain that ships explicit pubkeys. At gas market rates that adds up to real money over millions of transactions.
Why don’t Solana, Stellar, and Cosmos do this?
- Solana and Stellar use Ed25519. Ed25519 signature schemes don’t support pubkey recovery — the way the math is structured, you genuinely need the pubkey to verify; you can’t derive it from the signature alone.
- Cosmos uses secp256k1 ECDSA like Ethereum and could do recovery, but its tx format ships the public key in
auth_infoanyway for other reasons (account discovery, multi-sig, IBC), so the savings would be negligible.
Ethereum’s recovery trick is a small thing, but it’s the kind of thing that makes the chain look different on the wire — and helps explain why, despite using the same curve as Bitcoin, an Ethereum transaction is shaped nothing like a Bitcoin one.
7. What you can do with a signature
You can now sign messages. The blockchain still hasn’t seen any of this. The signature is just bytes on your machine.
In the next post we’ll see how those bytes get packaged into a transaction — what serialization format each chain uses (Ethereum’s RLP, Cosmos’s Protobuf, Solana’s bincode, Stellar’s XDR), how a transaction reaches a node, and how the network decides it’s final. Different chains do this very differently, and the differences explain most of the technical arguments people get into online.
But the signature itself is the heart of it. Everything else is logistics.