Improve ECDSA tests and docs (#2619)
This commit is contained in:
@ -22,6 +22,10 @@ library ECDSA {
|
|||||||
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
|
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
|
||||||
* this is by receiving a hash of the original message (which may otherwise
|
* this is by receiving a hash of the original message (which may otherwise
|
||||||
* be too long), and then calling {toEthSignedMessageHash} on it.
|
* be too long), and then calling {toEthSignedMessageHash} on it.
|
||||||
|
*
|
||||||
|
* Documentation for signature generation:
|
||||||
|
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
|
||||||
|
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
|
||||||
*/
|
*/
|
||||||
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
|
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
|
||||||
// Divide the signature in r, s and v variables
|
// Divide the signature in r, s and v variables
|
||||||
|
|||||||
@ -4,23 +4,6 @@ function toEthSignedMessageHash (messageHex) {
|
|||||||
return web3.utils.sha3(Buffer.concat([prefix, messageBuffer]));
|
return web3.utils.sha3(Buffer.concat([prefix, messageBuffer]));
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixSignature (signature) {
|
|
||||||
// in geth its always 27/28, in ganache its 0/1. Change to 27/28 to prevent
|
|
||||||
// signature malleability if version is 0/1
|
|
||||||
// see https://github.com/ethereum/go-ethereum/blob/v1.8.23/internal/ethapi/api.go#L465
|
|
||||||
let v = parseInt(signature.slice(130, 132), 16);
|
|
||||||
if (v < 27) {
|
|
||||||
v += 27;
|
|
||||||
}
|
|
||||||
const vHex = v.toString(16);
|
|
||||||
return signature.slice(0, 130) + vHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// signs message in node (ganache auto-applies "Ethereum Signed Message" prefix)
|
|
||||||
async function signMessage (signer, messageHex = '0x') {
|
|
||||||
return fixSignature(await web3.eth.sign(messageHex, signer));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a signer between a contract and a signer for a voucher of method, args, and redeemer
|
* 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
|
* Note that `method` is the web3 method, not the truffle-contract method
|
||||||
@ -55,12 +38,10 @@ const getSignFor = (contract, signer) => (redeemer, methodName, methodArgs = [])
|
|||||||
|
|
||||||
// return the signature of the "Ethereum Signed Message" hash of the hash of `parts`
|
// return the signature of the "Ethereum Signed Message" hash of the hash of `parts`
|
||||||
const messageHex = web3.utils.soliditySha3(...parts);
|
const messageHex = web3.utils.soliditySha3(...parts);
|
||||||
return signMessage(signer, messageHex);
|
return web3.eth.sign(messageHex, signer);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
signMessage,
|
|
||||||
toEthSignedMessageHash,
|
toEthSignedMessageHash,
|
||||||
fixSignature,
|
|
||||||
getSignFor,
|
getSignFor,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||||
const { toEthSignedMessageHash, fixSignature } = require('../../helpers/sign');
|
const { toEthSignedMessageHash } = require('../../helpers/sign');
|
||||||
|
|
||||||
const { expect } = require('chai');
|
const { expect } = require('chai');
|
||||||
|
|
||||||
@ -46,6 +46,30 @@ contract('ECDSA', function (accounts) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
context('recover with valid signature', function () {
|
context('recover with valid signature', function () {
|
||||||
|
context('using web3.eth.sign', function () {
|
||||||
|
it('returns signer address with correct signature', async function () {
|
||||||
|
// Create the signature
|
||||||
|
const signature = await web3.eth.sign(TEST_MESSAGE, other);
|
||||||
|
|
||||||
|
// Recover the signer address from the generated message and signature.
|
||||||
|
expect(await this.ecdsa.recover(
|
||||||
|
toEthSignedMessageHash(TEST_MESSAGE),
|
||||||
|
signature,
|
||||||
|
)).to.equal(other);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a different address', async function () {
|
||||||
|
const signature = await web3.eth.sign(TEST_MESSAGE, other);
|
||||||
|
expect(await this.ecdsa.recover(WRONG_MESSAGE, signature)).to.not.equal(other);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reverts with invalid signature', async function () {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const signature = '0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c';
|
||||||
|
await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
context('with v0 signature', function () {
|
context('with v0 signature', function () {
|
||||||
// Signature generated outside ganache with method web3.eth.sign(signer, message)
|
// Signature generated outside ganache with method web3.eth.sign(signer, message)
|
||||||
const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c';
|
const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c';
|
||||||
@ -120,30 +144,6 @@ contract('ECDSA', function (accounts) {
|
|||||||
|
|
||||||
await expectRevert(this.ecdsa.recover(message, highSSignature), 'ECDSA: invalid signature \'s\' value');
|
await expectRevert(this.ecdsa.recover(message, highSSignature), 'ECDSA: invalid signature \'s\' value');
|
||||||
});
|
});
|
||||||
|
|
||||||
context('using web3.eth.sign', function () {
|
|
||||||
it('returns signer address with correct signature', async function () {
|
|
||||||
// Create the signature
|
|
||||||
const signature = fixSignature(await web3.eth.sign(TEST_MESSAGE, other));
|
|
||||||
|
|
||||||
// Recover the signer address from the generated message and signature.
|
|
||||||
expect(await this.ecdsa.recover(
|
|
||||||
toEthSignedMessageHash(TEST_MESSAGE),
|
|
||||||
signature,
|
|
||||||
)).to.equal(other);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns a different address', async function () {
|
|
||||||
const signature = fixSignature(await web3.eth.sign(TEST_MESSAGE, other));
|
|
||||||
expect(await this.ecdsa.recover(WRONG_MESSAGE, signature)).to.not.equal(other);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('reverts with invalid signature', async function () {
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const signature = '0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c';
|
|
||||||
await expectRevert(this.ecdsa.recover(TEST_MESSAGE, signature), 'ECDSA: invalid signature');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context('toEthSignedMessage', function () {
|
context('toEthSignedMessage', function () {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
const { toEthSignedMessageHash, fixSignature } = require('../../helpers/sign');
|
const { toEthSignedMessageHash } = require('../../helpers/sign');
|
||||||
|
|
||||||
const { expect } = require('chai');
|
const { expect } = require('chai');
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ contract('SignatureChecker (ERC1271)', function (accounts) {
|
|||||||
before('deploying', async function () {
|
before('deploying', async function () {
|
||||||
this.signaturechecker = await SignatureCheckerMock.new();
|
this.signaturechecker = await SignatureCheckerMock.new();
|
||||||
this.wallet = await ERC1271WalletMock.new(signer);
|
this.wallet = await ERC1271WalletMock.new(signer);
|
||||||
this.signature = fixSignature(await web3.eth.sign(TEST_MESSAGE, signer));
|
this.signature = await web3.eth.sign(TEST_MESSAGE, signer);
|
||||||
});
|
});
|
||||||
|
|
||||||
context('EOA account', function () {
|
context('EOA account', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user