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 { toEthSignedMessageHash, toDataWithIntendedValidatorHash } = require('../../helpers/sign'); |
||||
const { domainSeparator, hashTypedData } = require('../../helpers/eip712'); |
||||
|
||||
const { ethers } = require('hardhat'); |
||||
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 () { |
||||
beforeEach(async function () { |
||||
this.messageHashUtils = await MessageHashUtils.new(); |
||||
async function fixture() { |
||||
const mock = await ethers.deployContract('$MessageHashUtils'); |
||||
return { mock }; |
||||
} |
||||
|
||||
this.message = '0x' + Buffer.from('abcd').toString('hex'); |
||||
this.messageHash = web3.utils.sha3(this.message); |
||||
this.verifyingAddress = web3.utils.toChecksumAddress(web3.utils.randomHex(20)); |
||||
describe('MessageHashUtils', function () { |
||||
beforeEach(async function () { |
||||
Object.assign(this, await loadFixture(fixture)); |
||||
}); |
||||
|
||||
context('toEthSignedMessageHash', function () { |
||||
describe('toEthSignedMessageHash', function () { |
||||
it('prefixes bytes32 data correctly', async function () { |
||||
expect(await this.messageHashUtils.methods['$toEthSignedMessageHash(bytes32)'](this.messageHash)).to.equal( |
||||
toEthSignedMessageHash(this.messageHash), |
||||
); |
||||
const message = ethers.randomBytes(32); |
||||
const expectedHash = ethers.hashMessage(message); |
||||
|
||||
expect(await this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message)).to.equal(expectedHash); |
||||
}); |
||||
|
||||
it('prefixes dynamic length data correctly', async function () { |
||||
expect(await this.messageHashUtils.methods['$toEthSignedMessageHash(bytes)'](this.message)).to.equal( |
||||
toEthSignedMessageHash(this.message), |
||||
); |
||||
const message = ethers.randomBytes(128); |
||||
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 () { |
||||
expect( |
||||
await this.messageHashUtils.$toDataWithIntendedValidatorHash(this.verifyingAddress, this.message), |
||||
).to.equal(toDataWithIntendedValidatorHash(this.verifyingAddress, this.message)); |
||||
const verifier = ethers.Wallet.createRandom().address; |
||||
const message = ethers.randomBytes(128); |
||||
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 () { |
||||
const domain = { |
||||
name: 'Test', |
||||
version: 1, |
||||
chainId: 1, |
||||
verifyingContract: this.verifyingAddress, |
||||
version: '1', |
||||
chainId: 1n, |
||||
verifyingContract: ethers.Wallet.createRandom().address, |
||||
}; |
||||
const structhash = web3.utils.randomHex(32); |
||||
const expectedDomainSeparator = await domainSeparator(domain); |
||||
expect(await this.messageHashUtils.$toTypedDataHash(expectedDomainSeparator, structhash)).to.equal( |
||||
hashTypedData(domain, structhash), |
||||
); |
||||
const structhash = ethers.randomBytes(32); |
||||
const expectedHash = hashTypedData(domain, structhash); |
||||
|
||||
expect(await this.mock.$toTypedDataHash(domainSeparator(domain), structhash)).to.equal(expectedHash); |
||||
}); |
||||
}); |
||||
}); |
||||
|
Loading…
Reference in new issue