dexorder
This commit is contained in:
213
lib_openzeppelin_contracts/test/utils/cryptography/ECDSA.test.js
Normal file
213
lib_openzeppelin_contracts/test/utils/cryptography/ECDSA.test.js
Normal file
@@ -0,0 +1,213 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const TEST_MESSAGE = ethers.id('OpenZeppelin');
|
||||
const WRONG_MESSAGE = ethers.id('Nope');
|
||||
const NON_HASH_MESSAGE = '0xabcd';
|
||||
|
||||
async function fixture() {
|
||||
const [signer] = await ethers.getSigners();
|
||||
const mock = await ethers.deployContract('$ECDSA');
|
||||
return { signer, mock };
|
||||
}
|
||||
|
||||
describe('ECDSA', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('recover with invalid signature', function () {
|
||||
it('with short signature', async function () {
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, '0x1234'))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
|
||||
.withArgs(2);
|
||||
});
|
||||
|
||||
it('with long signature', async function () {
|
||||
await expect(
|
||||
// eslint-disable-next-line max-len
|
||||
this.mock.$recover(
|
||||
TEST_MESSAGE,
|
||||
'0x01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789',
|
||||
),
|
||||
)
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
|
||||
.withArgs(85);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recover with valid signature', function () {
|
||||
describe('using <signer>.sign', function () {
|
||||
it('returns signer address with correct signature', async function () {
|
||||
// Create the signature
|
||||
const signature = await this.signer.signMessage(TEST_MESSAGE);
|
||||
|
||||
// Recover the signer address from the generated message and signature.
|
||||
expect(await this.mock.$recover(ethers.hashMessage(TEST_MESSAGE), signature)).to.equal(this.signer);
|
||||
});
|
||||
|
||||
it('returns signer address with correct signature for arbitrary length message', async function () {
|
||||
// Create the signature
|
||||
const signature = await this.signer.signMessage(NON_HASH_MESSAGE);
|
||||
|
||||
// Recover the signer address from the generated message and signature.
|
||||
expect(await this.mock.$recover(ethers.hashMessage(NON_HASH_MESSAGE), signature)).to.equal(this.signer);
|
||||
});
|
||||
|
||||
it('returns a different address', async function () {
|
||||
const signature = await this.signer.signMessage(TEST_MESSAGE);
|
||||
expect(await this.mock.$recover(WRONG_MESSAGE, signature)).to.not.be.equal(this.signer);
|
||||
});
|
||||
|
||||
it('reverts with invalid signature', async function () {
|
||||
// eslint-disable-next-line max-len
|
||||
const signature =
|
||||
'0x332ce75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e01c';
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'ECDSAInvalidSignature',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with v=27 signature', function () {
|
||||
const signer = '0x2cc1166f6212628A0deEf2B33BEFB2187D35b86c';
|
||||
// eslint-disable-next-line max-len
|
||||
const signatureWithoutV =
|
||||
'0x5d99b6f7f6d1f73d1a26497f2b1c89b24c0993913f86e9a2d02cd69887d9c94f3c880358579d811b21dd1b7fd9bb01c1d81d10e69f0384e675c32b39643be892';
|
||||
|
||||
it('works with correct v value', async function () {
|
||||
const v = '0x1b'; // 27 = 1b.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.equal(signer);
|
||||
|
||||
const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
|
||||
expect(await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)).to.equal(
|
||||
signer,
|
||||
);
|
||||
|
||||
expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.equal(signer);
|
||||
});
|
||||
|
||||
it('rejects incorrect v value', async function () {
|
||||
const v = '0x1c'; // 28 = 1c.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.not.equal(signer);
|
||||
|
||||
const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
|
||||
expect(
|
||||
await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s),
|
||||
).to.not.equal(signer);
|
||||
|
||||
expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.not.equal(
|
||||
signer,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts wrong v values', async function () {
|
||||
for (const v of ['0x00', '0x01']) {
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'ECDSAInvalidSignature',
|
||||
);
|
||||
|
||||
const { r, s } = ethers.Signature.from(signature);
|
||||
await expect(
|
||||
this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s),
|
||||
).to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignature');
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects short EIP2098 format', async function () {
|
||||
const v = '0x1b'; // 27 = 1b.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
|
||||
const { compactSerialized } = ethers.Signature.from(signature);
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, compactSerialized))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
|
||||
.withArgs(64);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with v=28 signature', function () {
|
||||
const signer = '0x1E318623aB09Fe6de3C9b8672098464Aeda9100E';
|
||||
// eslint-disable-next-line max-len
|
||||
const signatureWithoutV =
|
||||
'0x331fe75a821c982f9127538858900d87d3ec1f9f737338ad67cad133fa48feff48e6fa0c18abc62e42820f05943e47af3e9fbe306ce74d64094bdf1691ee53e0';
|
||||
|
||||
it('works with correct v value', async function () {
|
||||
const v = '0x1c'; // 28 = 1c.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.equal(signer);
|
||||
|
||||
const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
|
||||
expect(await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s)).to.equal(
|
||||
signer,
|
||||
);
|
||||
|
||||
expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.equal(signer);
|
||||
});
|
||||
|
||||
it('rejects incorrect v value', async function () {
|
||||
const v = '0x1b'; // 27 = 1b.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
expect(await this.mock.$recover(TEST_MESSAGE, signature)).to.not.equal(signer);
|
||||
|
||||
const { r, s, yParityAndS: vs } = ethers.Signature.from(signature);
|
||||
expect(
|
||||
await this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s),
|
||||
).to.not.equal(signer);
|
||||
|
||||
expect(await this.mock.getFunction('$recover(bytes32,bytes32,bytes32)')(TEST_MESSAGE, r, vs)).to.not.equal(
|
||||
signer,
|
||||
);
|
||||
});
|
||||
|
||||
it('reverts invalid v values', async function () {
|
||||
for (const v of ['0x00', '0x01']) {
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, signature)).to.be.revertedWithCustomError(
|
||||
this.mock,
|
||||
'ECDSAInvalidSignature',
|
||||
);
|
||||
|
||||
const { r, s } = ethers.Signature.from(signature);
|
||||
await expect(
|
||||
this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s),
|
||||
).to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignature');
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects short EIP2098 format', async function () {
|
||||
const v = '0x1b'; // 28 = 1b.
|
||||
const signature = ethers.concat([signatureWithoutV, v]);
|
||||
|
||||
const { compactSerialized } = ethers.Signature.from(signature);
|
||||
await expect(this.mock.$recover(TEST_MESSAGE, compactSerialized))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureLength')
|
||||
.withArgs(64);
|
||||
});
|
||||
});
|
||||
|
||||
it('reverts with high-s value signature', async function () {
|
||||
const message = '0xb94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9';
|
||||
// eslint-disable-next-line max-len
|
||||
const highSSignature =
|
||||
'0xe742ff452d41413616a5bf43fe15dd88294e983d3d36206c2712f39083d638bde0a0fc89be718fbc1033e1d30d78be1c68081562ed2e97af876f286f3453231d1b';
|
||||
|
||||
const r = ethers.dataSlice(highSSignature, 0, 32);
|
||||
const s = ethers.dataSlice(highSSignature, 32, 64);
|
||||
const v = ethers.dataSlice(highSSignature, 64, 65);
|
||||
|
||||
await expect(this.mock.$recover(message, highSSignature))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS')
|
||||
.withArgs(s);
|
||||
await expect(this.mock.getFunction('$recover(bytes32,uint8,bytes32,bytes32)')(TEST_MESSAGE, v, r, s))
|
||||
.to.be.revertedWithCustomError(this.mock, 'ECDSAInvalidSignatureS')
|
||||
.withArgs(s);
|
||||
expect(() => ethers.Signature.from(highSSignature)).to.throw('non-canonical s');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,105 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { getDomain, domainSeparator, hashTypedData } = require('../../helpers/eip712');
|
||||
const { formatType } = require('../../helpers/eip712-types');
|
||||
|
||||
const LENGTHS = {
|
||||
short: ['A Name', '1'],
|
||||
long: ['A'.repeat(40), 'B'.repeat(40)],
|
||||
};
|
||||
|
||||
const fixture = async () => {
|
||||
const [from, to] = await ethers.getSigners();
|
||||
|
||||
const lengths = {};
|
||||
for (const [shortOrLong, [name, version]] of Object.entries(LENGTHS)) {
|
||||
lengths[shortOrLong] = { name, version };
|
||||
lengths[shortOrLong].eip712 = await ethers.deployContract('$EIP712Verifier', [name, version]);
|
||||
lengths[shortOrLong].domain = {
|
||||
name,
|
||||
version,
|
||||
chainId: await ethers.provider.getNetwork().then(({ chainId }) => chainId),
|
||||
verifyingContract: lengths[shortOrLong].eip712.target,
|
||||
};
|
||||
}
|
||||
|
||||
return { from, to, lengths };
|
||||
};
|
||||
|
||||
describe('EIP712', function () {
|
||||
for (const [shortOrLong, [name, version]] of Object.entries(LENGTHS)) {
|
||||
describe(`with ${shortOrLong} name and version`, function () {
|
||||
beforeEach('deploying', async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
Object.assign(this, this.lengths[shortOrLong]);
|
||||
});
|
||||
|
||||
describe('domain separator', function () {
|
||||
it('is internally available', async function () {
|
||||
const expected = await domainSeparator(this.domain);
|
||||
|
||||
expect(await this.eip712.$_domainSeparatorV4()).to.equal(expected);
|
||||
});
|
||||
|
||||
it("can be rebuilt using EIP-5267's eip712Domain", async function () {
|
||||
const rebuildDomain = await getDomain(this.eip712);
|
||||
expect(rebuildDomain).to.be.deep.equal(this.domain);
|
||||
});
|
||||
|
||||
if (shortOrLong === 'short') {
|
||||
// Long strings are in storage, and the proxy will not be properly initialized unless
|
||||
// the upgradeable contract variant is used and the initializer is invoked.
|
||||
|
||||
it('adjusts when behind proxy', async function () {
|
||||
const factory = await ethers.deployContract('$Clones');
|
||||
|
||||
const clone = await factory
|
||||
.$clone(this.eip712)
|
||||
.then(tx => tx.wait())
|
||||
.then(receipt => receipt.logs.find(ev => ev.fragment.name == 'return$clone_address').args.instance)
|
||||
.then(address => ethers.getContractAt('$EIP712Verifier', address));
|
||||
|
||||
const expectedDomain = { ...this.domain, verifyingContract: clone.target };
|
||||
expect(await getDomain(clone)).to.be.deep.equal(expectedDomain);
|
||||
|
||||
const expectedSeparator = await domainSeparator(expectedDomain);
|
||||
expect(await clone.$_domainSeparatorV4()).to.equal(expectedSeparator);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('hash digest', async function () {
|
||||
const structhash = ethers.hexlify(ethers.randomBytes(32));
|
||||
expect(await this.eip712.$_hashTypedDataV4(structhash)).to.equal(hashTypedData(this.domain, structhash));
|
||||
});
|
||||
|
||||
it('digest', async function () {
|
||||
const types = {
|
||||
Mail: formatType({
|
||||
to: 'address',
|
||||
contents: 'string',
|
||||
}),
|
||||
};
|
||||
|
||||
const message = {
|
||||
to: this.to.address,
|
||||
contents: 'very interesting',
|
||||
};
|
||||
|
||||
const signature = await this.from.signTypedData(this.domain, types, message);
|
||||
|
||||
await expect(this.eip712.verify(signature, this.from.address, message.to, message.contents)).to.not.be.reverted;
|
||||
});
|
||||
|
||||
it('name', async function () {
|
||||
expect(await this.eip712.$_EIP712Name()).to.equal(name);
|
||||
});
|
||||
|
||||
it('version', async function () {
|
||||
expect(await this.eip712.$_EIP712Version()).to.equal(version);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,173 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
const { StandardMerkleTree } = require('@openzeppelin/merkle-tree');
|
||||
|
||||
const toElements = str => str.split('').map(e => [e]);
|
||||
const hashPair = (a, b) => ethers.keccak256(Buffer.concat([a, b].sort(Buffer.compare)));
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$MerkleProof');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('MerkleProof', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('verify', function () {
|
||||
it('returns true for a valid Merkle proof', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(
|
||||
toElements('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='),
|
||||
['string'],
|
||||
);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const hash = merkleTree.leafHash(['A']);
|
||||
const proof = merkleTree.getProof(['A']);
|
||||
|
||||
expect(await this.mock.$verify(proof, root, hash)).to.be.true;
|
||||
expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.true;
|
||||
|
||||
// For demonstration, it is also possible to create valid proofs for certain 64-byte values *not* in elements:
|
||||
const noSuchLeaf = hashPair(
|
||||
ethers.toBeArray(merkleTree.leafHash(['A'])),
|
||||
ethers.toBeArray(merkleTree.leafHash(['B'])),
|
||||
);
|
||||
expect(await this.mock.$verify(proof.slice(1), root, noSuchLeaf)).to.be.true;
|
||||
expect(await this.mock.$verifyCalldata(proof.slice(1), root, noSuchLeaf)).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false for an invalid Merkle proof', async function () {
|
||||
const correctMerkleTree = StandardMerkleTree.of(toElements('abc'), ['string']);
|
||||
const otherMerkleTree = StandardMerkleTree.of(toElements('def'), ['string']);
|
||||
|
||||
const root = correctMerkleTree.root;
|
||||
const hash = correctMerkleTree.leafHash(['a']);
|
||||
const proof = otherMerkleTree.getProof(['d']);
|
||||
|
||||
expect(await this.mock.$verify(proof, root, hash)).to.be.false;
|
||||
expect(await this.mock.$verifyCalldata(proof, root, hash)).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false for a Merkle proof of invalid length', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abc'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const hash = merkleTree.leafHash(['a']);
|
||||
const proof = merkleTree.getProof(['a']);
|
||||
const badProof = proof.slice(0, -1);
|
||||
|
||||
expect(await this.mock.$verify(badProof, root, hash)).to.be.false;
|
||||
expect(await this.mock.$verifyCalldata(badProof, root, hash)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiProofVerify', function () {
|
||||
it('returns true for a valid Merkle multi proof', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('bdf'));
|
||||
const hashes = leaves.map(e => merkleTree.leafHash(e));
|
||||
|
||||
expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true;
|
||||
expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false for an invalid Merkle multi proof', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcdef'), ['string']);
|
||||
const otherMerkleTree = StandardMerkleTree.of(toElements('ghi'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const { proof, proofFlags, leaves } = otherMerkleTree.getMultiProof(toElements('ghi'));
|
||||
const hashes = leaves.map(e => merkleTree.leafHash(e));
|
||||
|
||||
expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.false;
|
||||
expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.false;
|
||||
});
|
||||
|
||||
it('revert with invalid multi proof #1', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const hashA = merkleTree.leafHash(['a']);
|
||||
const hashB = merkleTree.leafHash(['b']);
|
||||
const hashCD = hashPair(
|
||||
ethers.toBeArray(merkleTree.leafHash(['c'])),
|
||||
ethers.toBeArray(merkleTree.leafHash(['d'])),
|
||||
);
|
||||
const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree)
|
||||
const fill = ethers.randomBytes(32);
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]),
|
||||
).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false], root, [hashA, hashE]),
|
||||
).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
|
||||
});
|
||||
|
||||
it('revert with invalid multi proof #2', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const hashA = merkleTree.leafHash(['a']);
|
||||
const hashB = merkleTree.leafHash(['b']);
|
||||
const hashCD = hashPair(
|
||||
ethers.toBeArray(merkleTree.leafHash(['c'])),
|
||||
ethers.toBeArray(merkleTree.leafHash(['d'])),
|
||||
);
|
||||
const hashE = merkleTree.leafHash(['e']); // incorrect (not part of the tree)
|
||||
const fill = ethers.randomBytes(32);
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerify([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]),
|
||||
).to.be.revertedWithPanic(0x32);
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerifyCalldata([hashB, fill, hashCD], [false, false, false, false], root, [hashE, hashA]),
|
||||
).to.be.revertedWithPanic(0x32);
|
||||
});
|
||||
|
||||
it('limit case: works for tree containing a single leaf', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('a'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
const { proof, proofFlags, leaves } = merkleTree.getMultiProof(toElements('a'));
|
||||
const hashes = leaves.map(e => merkleTree.leafHash(e));
|
||||
|
||||
expect(await this.mock.$multiProofVerify(proof, proofFlags, root, hashes)).to.be.true;
|
||||
expect(await this.mock.$multiProofVerifyCalldata(proof, proofFlags, root, hashes)).to.be.true;
|
||||
});
|
||||
|
||||
it('limit case: can prove empty leaves', async function () {
|
||||
const merkleTree = StandardMerkleTree.of(toElements('abcd'), ['string']);
|
||||
|
||||
const root = merkleTree.root;
|
||||
expect(await this.mock.$multiProofVerify([root], [], root, [])).to.be.true;
|
||||
expect(await this.mock.$multiProofVerifyCalldata([root], [], root, [])).to.be.true;
|
||||
});
|
||||
|
||||
it('reverts processing manipulated proofs with a zero-value node at depth 1', async function () {
|
||||
// Create a merkle tree that contains a zero leaf at depth 1
|
||||
const leave = ethers.id('real leaf');
|
||||
const root = hashPair(ethers.toBeArray(leave), Buffer.alloc(32, 0));
|
||||
|
||||
// Now we can pass any **malicious** fake leaves as valid!
|
||||
const maliciousLeaves = ['malicious', 'leaves'].map(ethers.id).map(ethers.toBeArray).sort(Buffer.compare);
|
||||
const maliciousProof = [leave, leave];
|
||||
const maliciousProofFlags = [true, true, false];
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerify(maliciousProof, maliciousProofFlags, root, maliciousLeaves),
|
||||
).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
|
||||
|
||||
await expect(
|
||||
this.mock.$multiProofVerifyCalldata(maliciousProof, maliciousProofFlags, root, maliciousLeaves),
|
||||
).to.be.revertedWithCustomError(this.mock, 'MerkleProofInvalidMultiproof');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const { domainSeparator, hashTypedData } = require('../../helpers/eip712');
|
||||
|
||||
async function fixture() {
|
||||
const mock = await ethers.deployContract('$MessageHashUtils');
|
||||
return { mock };
|
||||
}
|
||||
|
||||
describe('MessageHashUtils', function () {
|
||||
beforeEach(async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('toEthSignedMessageHash', function () {
|
||||
it('prefixes bytes32 data correctly', async function () {
|
||||
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 () {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toDataWithIntendedValidatorHash', function () {
|
||||
it('returns the digest correctly', async function () {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toTypedDataHash', function () {
|
||||
it('returns the digest correctly', async function () {
|
||||
const domain = {
|
||||
name: 'Test',
|
||||
version: '1',
|
||||
chainId: 1n,
|
||||
verifyingContract: ethers.Wallet.createRandom().address,
|
||||
};
|
||||
const structhash = ethers.randomBytes(32);
|
||||
const expectedHash = hashTypedData(domain, structhash);
|
||||
|
||||
expect(await this.mock.$toTypedDataHash(domainSeparator(domain), structhash)).to.equal(expectedHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const TEST_MESSAGE = ethers.id('OpenZeppelin');
|
||||
const TEST_MESSAGE_HASH = ethers.hashMessage(TEST_MESSAGE);
|
||||
|
||||
const WRONG_MESSAGE = ethers.id('Nope');
|
||||
const WRONG_MESSAGE_HASH = ethers.hashMessage(WRONG_MESSAGE);
|
||||
|
||||
async function fixture() {
|
||||
const [signer, other] = await ethers.getSigners();
|
||||
const mock = await ethers.deployContract('$SignatureChecker');
|
||||
const wallet = await ethers.deployContract('ERC1271WalletMock', [signer]);
|
||||
const malicious = await ethers.deployContract('ERC1271MaliciousMock');
|
||||
const signature = await signer.signMessage(TEST_MESSAGE);
|
||||
|
||||
return { signer, other, mock, wallet, malicious, signature };
|
||||
}
|
||||
|
||||
describe('SignatureChecker (ERC1271)', function () {
|
||||
before('deploying', async function () {
|
||||
Object.assign(this, await loadFixture(fixture));
|
||||
});
|
||||
|
||||
describe('EOA account', function () {
|
||||
it('with matching signer and signature', async function () {
|
||||
expect(await this.mock.$isValidSignatureNow(this.signer, TEST_MESSAGE_HASH, this.signature)).to.be.true;
|
||||
});
|
||||
|
||||
it('with invalid signer', async function () {
|
||||
expect(await this.mock.$isValidSignatureNow(this.other, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
|
||||
it('with invalid signature', async function () {
|
||||
expect(await this.mock.$isValidSignatureNow(this.signer, WRONG_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERC1271 wallet', function () {
|
||||
for (const fn of ['isValidERC1271SignatureNow', 'isValidSignatureNow']) {
|
||||
describe(fn, function () {
|
||||
it('with matching signer and signature', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.wallet, TEST_MESSAGE_HASH, this.signature)).to.be.true;
|
||||
});
|
||||
|
||||
it('with invalid signer', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.mock, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
|
||||
it('with invalid signature', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.wallet, WRONG_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
|
||||
it('with malicious wallet', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.malicious, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user