From 53235263a377612b546a5c1922e1876689eba525 Mon Sep 17 00:00:00 2001 From: Yamen Merhi Date: Wed, 22 Feb 2023 16:22:50 +0200 Subject: [PATCH] Implement `0x00` version of EIP-191 in ECDSA Library (#4063) --- .changeset/small-cars-appear.md | 5 +++++ contracts/utils/cryptography/ECDSA.sol | 10 ++++++++++ test/helpers/sign.js | 16 ++++++++++++++++ test/utils/cryptography/ECDSA.test.js | 11 ++++++++++- 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .changeset/small-cars-appear.md diff --git a/.changeset/small-cars-appear.md b/.changeset/small-cars-appear.md new file mode 100644 index 000000000..0263bcd18 --- /dev/null +++ b/.changeset/small-cars-appear.md @@ -0,0 +1,5 @@ +--- +'openzeppelin-solidity': patch +--- + +`ECDSA`: Add a function `toDataWithIntendedValidatorHash` that encodes data with version 0x00 following EIP-191. diff --git a/contracts/utils/cryptography/ECDSA.sol b/contracts/utils/cryptography/ECDSA.sol index a499b546b..77279eb4f 100644 --- a/contracts/utils/cryptography/ECDSA.sol +++ b/contracts/utils/cryptography/ECDSA.sol @@ -204,4 +204,14 @@ library ECDSA { data := keccak256(ptr, 0x42) } } + + /** + * @dev Returns an Ethereum Signed Data with intended validator, created from a + * `validator` and `data` according to the version 0 of EIP-191. + * + * See {recover}. + */ + function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19\x00", validator, data)); + } } diff --git a/test/helpers/sign.js b/test/helpers/sign.js index 417ef591d..d537116bb 100644 --- a/test/helpers/sign.js +++ b/test/helpers/sign.js @@ -4,6 +4,21 @@ function toEthSignedMessageHash(messageHex) { return web3.utils.sha3(Buffer.concat([prefix, messageBuffer])); } +/** + * Create a signed data with intended validator according to the version 0 of EIP-191 + * @param validatorAddress The address of the validator + * @param dataHex The data to be concatenated with the prefix and signed + */ +function toDataWithIntendedValidatorHash(validatorAddress, dataHex) { + const validatorBuffer = Buffer.from(web3.utils.hexToBytes(validatorAddress)); + const dataBuffer = Buffer.from(web3.utils.hexToBytes(dataHex)); + const preambleBuffer = Buffer.from('\x19'); + const versionBuffer = Buffer.from('\x00'); + const ethMessage = Buffer.concat([preambleBuffer, versionBuffer, validatorBuffer, dataBuffer]); + + return web3.utils.sha3(ethMessage); +} + /** * Create a signer between a contract and a signer for a voucher of method, args, and redeemer * Note that `method` is the web3 method, not the truffle-contract method @@ -43,5 +58,6 @@ const getSignFor = module.exports = { toEthSignedMessageHash, + toDataWithIntendedValidatorHash, getSignFor, }; diff --git a/test/utils/cryptography/ECDSA.test.js b/test/utils/cryptography/ECDSA.test.js index 3b19cde60..ae737086b 100644 --- a/test/utils/cryptography/ECDSA.test.js +++ b/test/utils/cryptography/ECDSA.test.js @@ -1,5 +1,5 @@ const { expectRevert } = require('@openzeppelin/test-helpers'); -const { toEthSignedMessageHash } = require('../../helpers/sign'); +const { toEthSignedMessageHash, toDataWithIntendedValidatorHash } = require('../../helpers/sign'); const { expect } = require('chai'); @@ -8,6 +8,7 @@ const ECDSA = artifacts.require('$ECDSA'); const TEST_MESSAGE = web3.utils.sha3('OpenZeppelin'); const WRONG_MESSAGE = web3.utils.sha3('Nope'); const NON_HASH_MESSAGE = '0x' + Buffer.from('abcd').toString('hex'); +const RANDOM_ADDRESS = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); function to2098Format(signature) { const long = web3.utils.hexToBytes(signature); @@ -248,4 +249,12 @@ contract('ECDSA', function (accounts) { ); }); }); + + context('toDataWithIntendedValidatorHash', function () { + it('returns the hash correctly', async function () { + expect( + await this.ecdsa.methods['$toDataWithIntendedValidatorHash(address,bytes)'](RANDOM_ADDRESS, NON_HASH_MESSAGE), + ).to.equal(toDataWithIntendedValidatorHash(RANDOM_ADDRESS, NON_HASH_MESSAGE)); + }); + }); });