Migrate utils-cryptography to ethers (#4749)
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com> Co-authored-by: ernestognw <ernestognw@gmail.com>pull/4750/head
parent
e473bcf859
commit
9702b67ce1
@ -1,63 +0,0 @@ |
|||||||
function toEthSignedMessageHash(messageHex) { |
|
||||||
const messageBuffer = Buffer.from(messageHex.substring(2), 'hex'); |
|
||||||
const prefix = Buffer.from(`\u0019Ethereum Signed Message:\n${messageBuffer.length}`); |
|
||||||
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 |
|
||||||
* @param contract TruffleContract |
|
||||||
* @param signer address |
|
||||||
* @param redeemer address |
|
||||||
* @param methodName string |
|
||||||
* @param methodArgs any[] |
|
||||||
*/ |
|
||||||
const getSignFor = |
|
||||||
(contract, signer) => |
|
||||||
(redeemer, methodName, methodArgs = []) => { |
|
||||||
const parts = [contract.address, redeemer]; |
|
||||||
|
|
||||||
const REAL_SIGNATURE_SIZE = 2 * 65; // 65 bytes in hexadecimal string length
|
|
||||||
const PADDED_SIGNATURE_SIZE = 2 * 96; // 96 bytes in hexadecimal string length
|
|
||||||
const DUMMY_SIGNATURE = `0x${web3.utils.padLeft('', REAL_SIGNATURE_SIZE)}`; |
|
||||||
|
|
||||||
// if we have a method, add it to the parts that we're signing
|
|
||||||
if (methodName) { |
|
||||||
if (methodArgs.length > 0) { |
|
||||||
parts.push( |
|
||||||
contract.contract.methods[methodName](...methodArgs.concat([DUMMY_SIGNATURE])) |
|
||||||
.encodeABI() |
|
||||||
.slice(0, -1 * PADDED_SIGNATURE_SIZE), |
|
||||||
); |
|
||||||
} else { |
|
||||||
const abi = contract.abi.find(abi => abi.name === methodName); |
|
||||||
parts.push(abi.signature); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// return the signature of the "Ethereum Signed Message" hash of the hash of `parts`
|
|
||||||
const messageHex = web3.utils.soliditySha3(...parts); |
|
||||||
return web3.eth.sign(messageHex, signer); |
|
||||||
}; |
|
||||||
|
|
||||||
module.exports = { |
|
||||||
toEthSignedMessageHash, |
|
||||||
toDataWithIntendedValidatorHash, |
|
||||||
getSignFor, |
|
||||||
}; |
|
@ -1,55 +1,68 @@ |
|||||||
require('@openzeppelin/test-helpers'); |
const { ethers } = require('hardhat'); |
||||||
const { toEthSignedMessageHash, toDataWithIntendedValidatorHash } = require('../../helpers/sign'); |
|
||||||
const { domainSeparator, hashTypedData } = require('../../helpers/eip712'); |
|
||||||
|
|
||||||
const { expect } = require('chai'); |
const { expect } = require('chai'); |
||||||
|
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); |
||||||
|
|
||||||
const MessageHashUtils = artifacts.require('$MessageHashUtils'); |
const { domainSeparator, hashTypedData } = require('../../helpers/eip712'); |
||||||
|
|
||||||
contract('MessageHashUtils', function () { |
async function fixture() { |
||||||
beforeEach(async function () { |
const mock = await ethers.deployContract('$MessageHashUtils'); |
||||||
this.messageHashUtils = await MessageHashUtils.new(); |
return { mock }; |
||||||
|
} |
||||||
|
|
||||||
this.message = '0x' + Buffer.from('abcd').toString('hex'); |
describe('MessageHashUtils', function () { |
||||||
this.messageHash = web3.utils.sha3(this.message); |
beforeEach(async function () { |
||||||
this.verifyingAddress = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); |
Object.assign(this, await loadFixture(fixture)); |
||||||
}); |
}); |
||||||
|
|
||||||
context('toEthSignedMessageHash', function () { |
describe('toEthSignedMessageHash', function () { |
||||||
it('prefixes bytes32 data correctly', async function () { |
it('prefixes bytes32 data correctly', async function () { |
||||||
expect(await this.messageHashUtils.methods['$toEthSignedMessageHash(bytes32)'](this.messageHash)).to.equal( |
const message = ethers.randomBytes(32); |
||||||
toEthSignedMessageHash(this.messageHash), |
const expectedHash = ethers.hashMessage(message); |
||||||
); |
|
||||||
|
expect(await this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message)).to.equal(expectedHash); |
||||||
}); |
}); |
||||||
|
|
||||||
it('prefixes dynamic length data correctly', async function () { |
it('prefixes dynamic length data correctly', async function () { |
||||||
expect(await this.messageHashUtils.methods['$toEthSignedMessageHash(bytes)'](this.message)).to.equal( |
const message = ethers.randomBytes(128); |
||||||
toEthSignedMessageHash(this.message), |
const expectedHash = ethers.hashMessage(message); |
||||||
); |
|
||||||
|
expect(await this.mock.getFunction('$toEthSignedMessageHash(bytes)')(message)).to.equal(expectedHash); |
||||||
|
}); |
||||||
|
|
||||||
|
it('version match for bytes32', async function () { |
||||||
|
const message = ethers.randomBytes(32); |
||||||
|
const fixed = await this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message); |
||||||
|
const dynamic = await this.mock.getFunction('$toEthSignedMessageHash(bytes)')(message); |
||||||
|
|
||||||
|
expect(fixed).to.equal(dynamic); |
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
|
||||||
context('toDataWithIntendedValidatorHash', function () { |
describe('toDataWithIntendedValidatorHash', function () { |
||||||
it('returns the digest correctly', async function () { |
it('returns the digest correctly', async function () { |
||||||
expect( |
const verifier = ethers.Wallet.createRandom().address; |
||||||
await this.messageHashUtils.$toDataWithIntendedValidatorHash(this.verifyingAddress, this.message), |
const message = ethers.randomBytes(128); |
||||||
).to.equal(toDataWithIntendedValidatorHash(this.verifyingAddress, this.message)); |
const expectedHash = ethers.solidityPackedKeccak256( |
||||||
|
['string', 'address', 'bytes'], |
||||||
|
['\x19\x00', verifier, message], |
||||||
|
); |
||||||
|
|
||||||
|
expect(await this.mock.$toDataWithIntendedValidatorHash(verifier, message)).to.equal(expectedHash); |
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
|
||||||
context('toTypedDataHash', function () { |
describe('toTypedDataHash', function () { |
||||||
it('returns the digest correctly', async function () { |
it('returns the digest correctly', async function () { |
||||||
const domain = { |
const domain = { |
||||||
name: 'Test', |
name: 'Test', |
||||||
version: 1, |
version: '1', |
||||||
chainId: 1, |
chainId: 1n, |
||||||
verifyingContract: this.verifyingAddress, |
verifyingContract: ethers.Wallet.createRandom().address, |
||||||
}; |
}; |
||||||
const structhash = web3.utils.randomHex(32); |
const structhash = ethers.randomBytes(32); |
||||||
const expectedDomainSeparator = await domainSeparator(domain); |
const expectedHash = hashTypedData(domain, structhash); |
||||||
expect(await this.messageHashUtils.$toTypedDataHash(expectedDomainSeparator, structhash)).to.equal( |
|
||||||
hashTypedData(domain, structhash), |
expect(await this.mock.$toTypedDataHash(domainSeparator(domain), structhash)).to.equal(expectedHash); |
||||||
); |
|
||||||
}); |
}); |
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
Loading…
Reference in new issue