Skip to content

Commit d693d89

Browse files
authored
Fix ECDSA signature malleability (#3610)
1 parent ff16696 commit d693d89

File tree

3 files changed

+16
-30
lines changed

3 files changed

+16
-30
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818

1919
ERC-721 integrators that interpret contract state from events should make sure that they implement the clearing of approval that is implicit in every transfer according to the EIP. Previous versions of OpenZeppellin Contracts emitted an explicit `Approval` event even though it was not required by the specification, and this is no longer the case.
2020

21+
## 4.7.3
22+
23+
### Breaking changes
24+
25+
* `ECDSA`: `recover(bytes32,bytes)` and `tryRecover(bytes32,bytes)` no longer accept compact signatures to prevent malleability. Compact signature support remains available using `recover(bytes32,bytes32,bytes32)` and `tryRecover(bytes32,bytes32,bytes32)`.
26+
2127
## 4.7.2
2228

2329
* `LibArbitrumL2`, `CrossChainEnabledArbitrumL2`: Fixed detection of cross-chain calls for EOAs. Previously, calls from EOAs would be classified as cross-chain calls. ([#3578](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3578))

contracts/utils/cryptography/ECDSA.sol

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@ library ECDSA {
5555
* _Available since v4.3._
5656
*/
5757
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
58-
// Check the signature length
59-
// - case 65: r,s,v signature (standard)
60-
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
6158
if (signature.length == 65) {
6259
bytes32 r;
6360
bytes32 s;
@@ -71,17 +68,6 @@ library ECDSA {
7168
v := byte(0, mload(add(signature, 0x60)))
7269
}
7370
return tryRecover(hash, v, r, s);
74-
} else if (signature.length == 64) {
75-
bytes32 r;
76-
bytes32 vs;
77-
// ecrecover takes the signature parameters, and the only way to get them
78-
// currently is to use assembly.
79-
/// @solidity memory-safe-assembly
80-
assembly {
81-
r := mload(add(signature, 0x20))
82-
vs := mload(add(signature, 0x40))
83-
}
84-
return tryRecover(hash, r, vs);
8571
} else {
8672
return (address(0), RecoverError.InvalidSignatureLength);
8773
}

test/utils/cryptography/ECDSA.test.js

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,6 @@ function to2098Format (signature) {
2222
return web3.utils.bytesToHex(short);
2323
}
2424

25-
function from2098Format (signature) {
26-
const short = web3.utils.hexToBytes(signature);
27-
if (short.length !== 64) {
28-
throw new Error('invalid signature length (expected short format)');
29-
}
30-
short.push((short[32] >> 7) + 27);
31-
short[32] &= (1 << 7) - 1; // zero out the first bit of 1 the 32nd byte
32-
return web3.utils.bytesToHex(short);
33-
}
34-
3525
function split (signature) {
3626
const raw = web3.utils.hexToBytes(signature);
3727
switch (raw.length) {
@@ -144,11 +134,13 @@ contract('ECDSA', function (accounts) {
144134
);
145135
});
146136

147-
it('works with short EIP2098 format', async function () {
137+
it('rejects short EIP2098 format', async function () {
148138
const version = '1b'; // 27 = 1b.
149139
const signature = signatureWithoutVersion + version;
150-
expect(await this.ecdsa.recover(TEST_MESSAGE, to2098Format(signature))).to.equal(signer);
151-
expect(await this.ecdsa.recover(TEST_MESSAGE, from2098Format(to2098Format(signature)))).to.equal(signer);
140+
await expectRevert(
141+
this.ecdsa.recover(TEST_MESSAGE, to2098Format(signature)),
142+
'ECDSA: invalid signature length',
143+
);
152144
});
153145
});
154146

@@ -187,11 +179,13 @@ contract('ECDSA', function (accounts) {
187179
);
188180
});
189181

190-
it('works with short EIP2098 format', async function () {
182+
it('rejects short EIP2098 format', async function () {
191183
const version = '1c'; // 27 = 1b.
192184
const signature = signatureWithoutVersion + version;
193-
expect(await this.ecdsa.recover(TEST_MESSAGE, to2098Format(signature))).to.equal(signer);
194-
expect(await this.ecdsa.recover(TEST_MESSAGE, from2098Format(to2098Format(signature)))).to.equal(signer);
185+
await expectRevert(
186+
this.ecdsa.recover(TEST_MESSAGE, to2098Format(signature)),
187+
'ECDSA: invalid signature length',
188+
);
195189
});
196190
});
197191

0 commit comments

Comments
 (0)