Skip to content

Commit

Permalink
Fix ECDSA signature malleability (#3610)
Browse files Browse the repository at this point in the history
(cherry picked from commit d693d89)
  • Loading branch information
frangio committed Aug 10, 2022
1 parent 64e4820 commit e1878ac
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 30 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog

## 4.7.3

### Breaking changes

* `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)`.

## 4.7.2 (2022-07-27)

* `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))
Expand Down
14 changes: 0 additions & 14 deletions contracts/utils/cryptography/ECDSA.sol
Expand Up @@ -55,9 +55,6 @@ library ECDSA {
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
// Check the signature length
// - case 65: r,s,v signature (standard)
// - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
if (signature.length == 65) {
bytes32 r;
bytes32 s;
Expand All @@ -71,17 +68,6 @@ library ECDSA {
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else if (signature.length == 64) {
bytes32 r;
bytes32 vs;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
vs := mload(add(signature, 0x40))
}
return tryRecover(hash, r, vs);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
Expand Down
26 changes: 10 additions & 16 deletions test/utils/cryptography/ECDSA.test.js
Expand Up @@ -22,16 +22,6 @@ function to2098Format (signature) {
return web3.utils.bytesToHex(short);
}

function from2098Format (signature) {
const short = web3.utils.hexToBytes(signature);
if (short.length !== 64) {
throw new Error('invalid signature length (expected short format)');
}
short.push((short[32] >> 7) + 27);
short[32] &= (1 << 7) - 1; // zero out the first bit of 1 the 32nd byte
return web3.utils.bytesToHex(short);
}

function split (signature) {
const raw = web3.utils.hexToBytes(signature);
switch (raw.length) {
Expand Down Expand Up @@ -144,11 +134,13 @@ contract('ECDSA', function (accounts) {
);
});

it('works with short EIP2098 format', async function () {
it('rejects short EIP2098 format', async function () {
const version = '1b'; // 27 = 1b.
const signature = signatureWithoutVersion + version;
expect(await this.ecdsa.recover(TEST_MESSAGE, to2098Format(signature))).to.equal(signer);
expect(await this.ecdsa.recover(TEST_MESSAGE, from2098Format(to2098Format(signature)))).to.equal(signer);
await expectRevert(
this.ecdsa.recover(TEST_MESSAGE, to2098Format(signature)),
'ECDSA: invalid signature length',
);
});
});

Expand Down Expand Up @@ -187,11 +179,13 @@ contract('ECDSA', function (accounts) {
);
});

it('works with short EIP2098 format', async function () {
it('rejects short EIP2098 format', async function () {
const version = '1c'; // 27 = 1b.
const signature = signatureWithoutVersion + version;
expect(await this.ecdsa.recover(TEST_MESSAGE, to2098Format(signature))).to.equal(signer);
expect(await this.ecdsa.recover(TEST_MESSAGE, from2098Format(to2098Format(signature)))).to.equal(signer);
await expectRevert(
this.ecdsa.recover(TEST_MESSAGE, to2098Format(signature)),
'ECDSA: invalid signature length',
);
});
});

Expand Down

1 comment on commit e1878ac

@TimDaub
Copy link
Contributor

@TimDaub TimDaub commented on e1878ac Jan 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change makes the SignatureChecker which uses

(address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature);
incompatible with EIP-2098. @frangio was this intentional? Or could the SignatureChecker be made compatible again?

Please sign in to comment.