Signature Malleability in Solidity, Explained
Quick answer. Signature malleability means anyone can turn a valid ECDSA signature into a second valid one for the same signer by replacing s with n - s. Accepting s from only half the range stops it.
You have probably heard of signature malleability. You may even know how to spot it in a smart contract, without knowing why the exploit exists under the hood.
This guide demystifies it. It starts with a vulnerable contract, explores the basic elliptic curve cryptography behind Ethereum signatures, demonstrates the attack, and ends with the fix.
A Vulnerable Example
Here is a vulnerable pattern. The owner provides the v, r and s components of a signature, and the contract recovers the signer to check that it is the owner.
The signature is malleable.
An attacker can observe the owner's v, r and s, craft another signature that also recovers to the owner's address, and execute the owner's message a second time.
The result is a double spend. In the sketch below, the contract marks each signature as used, and the attacker's second signature is different bytes, so it passes that check.
contract Wallet {
address public owner;
mapping(bytes32 => bool) public usedSignatures;
function withdraw(address to, uint256 amount, uint8 v, bytes32 r, bytes32 s) external {
bytes32 sigId = keccak256(abi.encode(v, r, s));
require(!usedSignatures[sigId], "signature used");
bytes32 hash = keccak256(abi.encode(to, amount));
require(ecrecover(hash, v, r, s) == owner, "not owner");
usedSignatures[sigId] = true;
payable(to).transfer(amount);
}
}
So why can an attacker observe these values and create a second valid signature? To answer that, break down how message signing works in Ethereum, starting with the elliptic curve.
The Elliptic Curve
Ethereum uses secp256k1, a group of points that lie on the elliptic curve y² = x³ + 7, computed modulo a very large prime p.
For the basics, see what is secp256k1.
Two characteristics matter here. The prime p is what every coordinate is reduced modulo.
The value n is the size of the group, essentially the number of points on the curve.
How ECDSA Signing Works
ECDSA is the elliptic curve digital signature algorithm, and it is where v, r and s come from.
You hold a private key, e, that only you know, and a public key, E, that you can share with anybody.
Because of how the private key generates the public key on the curve, sharing E keeps e safe.
But a signature is made with your private key. So it needs a confounding random value, or an attacker could observe your signature and work backwards to your private key.
The temporary key and r
So each time you sign, you generate a cryptographically secure random temporary private key, k, just for this signature. Elliptic curve multiplication turns it into a temporary public key, K.
K is a point on the curve, with an x and a y value. Its x coordinate becomes r.
Computing s
With r defined, a formula gives the matching s. With z as the message hash, it is s = k-1(z + r · e) mod n.
Without getting stuck in the weeds: with the right s, elliptic curve addition yields the temporary public key K. The formula is that relation solved for s.
Where v comes in
Look back at r, the x coordinate of K. Draw a vertical line through that x on the curve, and you hit two points.
There are always two, because the equation uses y², so the curve is mirrored over the x axis. From r alone, it is ambiguous which point was used.
That is what v is for.
It makes verification much quicker: when you recover the signer, you do not have to check both candidate public keys, because v tells you which one to use.
The Malleability
Those two valid points with the same r are exactly what makes signatures malleable.
An attacker can easily compute the s value that works for the opposite temporary public key. That gives a second valid signature, which also recovers to the same signer.
Demonstrating it
You can perform the malleability yourself in a Node shell, with a package for elliptic curve arithmetic.
- Load the secp256k1 parameters, such as
n, the order of the group. - Define an arbitrary private key and derive its public key. It is a point with an
xand ay, both too large for plain numbers, so they are big numbers. - Define an arbitrary message, such as the BigInt
1n, and sign it with the private key. The signature object has anrand ans, but nov: it is not necessary for recovery, it only makes recovery more efficient. - Verify the message against the signature and the public key: it returns true. Change the message to
2nand it returns false, because the signature was not for that message. - Build a manipulated signature. Keep
r, and replaceswithn - s. Verify it against the original message: it also returns true.
Why n - s works
Without going too deep into the number theory: because secp256k1 is modular, one valid s is greater than n / 2 and works for one of the two points.
The other valid s is less than n / 2 and works for the other point.
Computing n - s always gives the value on the other side of n / 2.
Since everything is mod n, it does not matter which side the original s was on.
Back in the contract, the attacker observes the owner's earlier signature, submits their own tampered version that recovers to the owner's address, and triggers a second withdrawal.
The Fix
The most widely accepted fix is to restrict s to one half of the range.
With only one side of n / 2 accepted, each message has only one valid signature.
OpenZeppelin's ECDSA library enforces this.
It rejects any signature whose s is above n / 2, so when an attacker submits the flipped s from the other half, the transaction simply reverts.
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
// Reverts if s is in the upper half of the range
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "not owner");
For other signature and Solidity pitfalls that keep reaching audits, see sneaky Solidity bugs and common smart contract vulnerabilities.
How Guardian Helps
Guardian's smart contract audits send senior researchers after protocol logic and privileged flows, with exploit-driven review and proof-of-concept depth.
With Vanguard, two independent teams review the same scope and cross-check each other's findings.
Frequently Asked Questions
What is signature malleability?
It is the ability to take a valid ECDSA signature and produce a second, different signature that is also valid for the same message and signer. On secp256k1, replacing s with n - s does it.
Why are there two valid signatures for one message?
The r value is the x coordinate of a temporary public key, and every such x has two points on the curve. Each point has its own valid s: one above n / 2, one below.
What does the v value do in a signature?
v tells the verifier which of the two curve points with x coordinate r was used. Recovery works without it, but v makes it faster, because the verifier does not need to try both points.
How do you prevent signature malleability in Solidity?
Accept s from only one half of the range. OpenZeppelin's ECDSA library rejects any s above n / 2, so the flipped second signature reverts.
Sources
Check your signature handling with Guardian
Guardian researchers review every signature check and privileged flow before your code holds value.