Update docs
This commit is contained in:
33
test/utils/cryptography/MessageHashUtils.t.sol
Normal file
33
test/utils/cryptography/MessageHashUtils.t.sol
Normal file
@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
||||
|
||||
contract MessageHashUtilsTest is Test {
|
||||
function testToDataWithIntendedValidatorHash(address validator, bytes memory data) external pure {
|
||||
assertEq(
|
||||
MessageHashUtils.toDataWithIntendedValidatorHash(validator, data),
|
||||
MessageHashUtils.toDataWithIntendedValidatorHash(_dirty(validator), data)
|
||||
);
|
||||
}
|
||||
|
||||
function testToDataWithIntendedValidatorHash(address validator, bytes32 messageHash) external pure {
|
||||
assertEq(
|
||||
MessageHashUtils.toDataWithIntendedValidatorHash(validator, messageHash),
|
||||
MessageHashUtils.toDataWithIntendedValidatorHash(_dirty(validator), messageHash)
|
||||
);
|
||||
|
||||
assertEq(
|
||||
MessageHashUtils.toDataWithIntendedValidatorHash(validator, messageHash),
|
||||
MessageHashUtils.toDataWithIntendedValidatorHash(validator, abi.encodePacked(messageHash))
|
||||
);
|
||||
}
|
||||
|
||||
function _dirty(address input) private pure returns (address output) {
|
||||
assembly ("memory-safe") {
|
||||
output := or(input, shl(160, not(0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,14 +19,16 @@ describe('MessageHashUtils', function () {
|
||||
const message = ethers.randomBytes(32);
|
||||
const expectedHash = ethers.hashMessage(message);
|
||||
|
||||
expect(await this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message)).to.equal(expectedHash);
|
||||
await expect(this.mock.getFunction('$toEthSignedMessageHash(bytes32)')(message)).to.eventually.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);
|
||||
await expect(this.mock.getFunction('$toEthSignedMessageHash(bytes)')(message)).to.eventually.equal(expectedHash);
|
||||
});
|
||||
|
||||
it('version match for bytes32', async function () {
|
||||
@ -39,7 +41,20 @@ describe('MessageHashUtils', function () {
|
||||
});
|
||||
|
||||
describe('toDataWithIntendedValidatorHash', function () {
|
||||
it('returns the digest correctly', async function () {
|
||||
it('returns the digest of `bytes32 messageHash` correctly', async function () {
|
||||
const verifier = ethers.Wallet.createRandom().address;
|
||||
const message = ethers.randomBytes(32);
|
||||
const expectedHash = ethers.solidityPackedKeccak256(
|
||||
['string', 'address', 'bytes32'],
|
||||
['\x19\x00', verifier, message],
|
||||
);
|
||||
|
||||
await expect(
|
||||
this.mock.getFunction('$toDataWithIntendedValidatorHash(address,bytes32)')(verifier, message),
|
||||
).to.eventually.equal(expectedHash);
|
||||
});
|
||||
|
||||
it('returns the digest of `bytes memory message` correctly', async function () {
|
||||
const verifier = ethers.Wallet.createRandom().address;
|
||||
const message = ethers.randomBytes(128);
|
||||
const expectedHash = ethers.solidityPackedKeccak256(
|
||||
@ -47,7 +62,21 @@ describe('MessageHashUtils', function () {
|
||||
['\x19\x00', verifier, message],
|
||||
);
|
||||
|
||||
expect(await this.mock.$toDataWithIntendedValidatorHash(verifier, message)).to.equal(expectedHash);
|
||||
await expect(
|
||||
this.mock.getFunction('$toDataWithIntendedValidatorHash(address,bytes)')(verifier, message),
|
||||
).to.eventually.equal(expectedHash);
|
||||
});
|
||||
|
||||
it('version match for bytes32', async function () {
|
||||
const verifier = ethers.Wallet.createRandom().address;
|
||||
const message = ethers.randomBytes(32);
|
||||
const fixed = await this.mock.getFunction('$toDataWithIntendedValidatorHash(address,bytes)')(verifier, message);
|
||||
const dynamic = await this.mock.getFunction('$toDataWithIntendedValidatorHash(address,bytes32)')(
|
||||
verifier,
|
||||
message,
|
||||
);
|
||||
|
||||
expect(fixed).to.equal(dynamic);
|
||||
});
|
||||
});
|
||||
|
||||
@ -62,7 +91,7 @@ describe('MessageHashUtils', function () {
|
||||
const structhash = ethers.randomBytes(32);
|
||||
const expectedHash = hashTypedData(domain, structhash);
|
||||
|
||||
expect(await this.mock.$toTypedDataHash(domainSeparator(domain), structhash)).to.equal(expectedHash);
|
||||
await expect(this.mock.$toTypedDataHash(domainSeparator(domain), structhash)).to.eventually.equal(expectedHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -6,10 +6,11 @@ import {Test} from "forge-std/Test.sol";
|
||||
|
||||
import {P256} from "@openzeppelin/contracts/utils/cryptography/P256.sol";
|
||||
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
|
||||
import {Errors} from "@openzeppelin/contracts/utils/Errors.sol";
|
||||
|
||||
contract P256Test is Test {
|
||||
/// forge-config: default.fuzz.runs = 512
|
||||
function testVerify(bytes32 digest, uint256 seed) public {
|
||||
function testVerify(bytes32 digest, uint256 seed) public view {
|
||||
uint256 privateKey = _asPrivateKey(seed);
|
||||
|
||||
(uint256 x, uint256 y) = vm.publicKeyP256(privateKey);
|
||||
@ -20,7 +21,7 @@ contract P256Test is Test {
|
||||
}
|
||||
|
||||
/// forge-config: default.fuzz.runs = 512
|
||||
function testRecover(bytes32 digest, uint256 seed) public {
|
||||
function testRecover(bytes32 digest, uint256 seed) public view {
|
||||
uint256 privateKey = _asPrivateKey(seed);
|
||||
|
||||
(uint256 x, uint256 y) = vm.publicKeyP256(privateKey);
|
||||
@ -31,6 +32,22 @@ contract P256Test is Test {
|
||||
assertTrue((qx0 == bytes32(x) && qy0 == bytes32(y)) || (qx1 == bytes32(x) && qy1 == bytes32(y)));
|
||||
}
|
||||
|
||||
function testVerifyNativeUnsupportedRIP7212(bytes32 digest, uint256 seed) public {
|
||||
// By default, the precompile at address 0x100 is not supported.
|
||||
|
||||
uint256 privateKey = _asPrivateKey(seed);
|
||||
|
||||
(uint256 x, uint256 y) = vm.publicKeyP256(privateKey);
|
||||
(bytes32 r, bytes32 s) = vm.signP256(privateKey, digest);
|
||||
s = _ensureLowerS(s);
|
||||
|
||||
(bool success, bytes memory returndata) = address(this).call(
|
||||
abi.encodeCall(P256Test.verifyNative, (digest, r, s, bytes32(x), bytes32(y)))
|
||||
);
|
||||
assertFalse(success);
|
||||
assertEq(returndata, abi.encodeWithSelector(Errors.MissingPrecompile.selector, address(0x100)));
|
||||
}
|
||||
|
||||
function _asPrivateKey(uint256 seed) private pure returns (uint256) {
|
||||
return bound(seed, 1, P256.N - 1);
|
||||
}
|
||||
@ -41,4 +58,9 @@ contract P256Test is Test {
|
||||
return _s > P256.N / 2 ? bytes32(P256.N - _s) : s;
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/foundry-rs/foundry/issues/10237
|
||||
function verifyNative(bytes32 digest, bytes32 r, bytes32 s, bytes32 x, bytes32 y) external view {
|
||||
P256.verifyNative(digest, r, s, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,26 +44,52 @@ describe('P256', function () {
|
||||
});
|
||||
|
||||
it('verify valid signature', async function () {
|
||||
expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.true;
|
||||
expect(await this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.be.true;
|
||||
await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey))
|
||||
.to.be.revertedWithCustomError(this.mock, 'MissingPrecompile')
|
||||
.withArgs('0x0000000000000000000000000000000000000100');
|
||||
await expect(this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be.true;
|
||||
await expect(this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be
|
||||
.true;
|
||||
await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be
|
||||
.true;
|
||||
});
|
||||
|
||||
it('verify improper signature', async function () {
|
||||
const signature = this.signature;
|
||||
this.signature[0] = ethers.toBeHex(N, 0x20); // r = N
|
||||
await expect(this.mock.$verify(this.messageHash, ...signature, ...this.publicKey)).to.eventually.be.false;
|
||||
await expect(this.mock.$verifySolidity(this.messageHash, ...signature, ...this.publicKey)).to.eventually.be.false;
|
||||
await expect(this.mock.$verifyNative(this.messageHash, ...signature, ...this.publicKey)).to.eventually.be.false;
|
||||
});
|
||||
|
||||
it('recover public key', async function () {
|
||||
expect(await this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.deep.equal(
|
||||
await expect(this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.eventually.deep.equal(
|
||||
this.publicKey,
|
||||
);
|
||||
});
|
||||
|
||||
it('recovers (0,0) for invalid recovery bit', async function () {
|
||||
await expect(this.mock.$recovery(this.messageHash, 2, ...this.signature)).to.eventually.deep.equal([
|
||||
ethers.ZeroHash,
|
||||
ethers.ZeroHash,
|
||||
]);
|
||||
});
|
||||
|
||||
it('recovers (0,0) for improper signature', async function () {
|
||||
const signature = this.signature;
|
||||
this.signature[0] = ethers.toBeHex(N, 0x20); // r = N
|
||||
await expect(this.mock.$recovery(this.messageHash, this.recovery, ...signature)).to.eventually.deep.equal([
|
||||
ethers.ZeroHash,
|
||||
ethers.ZeroHash,
|
||||
]);
|
||||
});
|
||||
|
||||
it('reject signature with flipped public key coordinates ([x,y] >> [y,x])', async function () {
|
||||
// flip public key
|
||||
this.publicKey.reverse();
|
||||
|
||||
expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false;
|
||||
expect(await this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false;
|
||||
expect(await this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false; // Flipped public key is not in the curve
|
||||
await expect(this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be.false;
|
||||
await expect(this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be
|
||||
.false;
|
||||
await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be
|
||||
.false;
|
||||
});
|
||||
|
||||
it('reject signature with flipped signature values ([r,s] >> [s,r])', async function () {
|
||||
@ -81,42 +107,42 @@ describe('P256', function () {
|
||||
];
|
||||
|
||||
// Make sure it works
|
||||
expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.true;
|
||||
await expect(this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be.true;
|
||||
|
||||
// Flip signature
|
||||
this.signature.reverse();
|
||||
|
||||
expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false;
|
||||
expect(await this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false;
|
||||
await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey))
|
||||
.to.be.revertedWithCustomError(this.mock, 'MissingPrecompile')
|
||||
.withArgs('0x0000000000000000000000000000000000000100');
|
||||
expect(await this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.not.deep.equal(
|
||||
this.publicKey,
|
||||
);
|
||||
await expect(this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be.false;
|
||||
await expect(this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be
|
||||
.false;
|
||||
await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be
|
||||
.false;
|
||||
await expect(
|
||||
this.mock.$recovery(this.messageHash, this.recovery, ...this.signature),
|
||||
).to.eventually.not.deep.equal(this.publicKey);
|
||||
});
|
||||
|
||||
it('reject signature with invalid message hash', async function () {
|
||||
// random message hash
|
||||
this.messageHash = ethers.hexlify(ethers.randomBytes(32));
|
||||
|
||||
expect(await this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false;
|
||||
expect(await this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.be.false;
|
||||
await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey))
|
||||
.to.be.revertedWithCustomError(this.mock, 'MissingPrecompile')
|
||||
.withArgs('0x0000000000000000000000000000000000000100');
|
||||
expect(await this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.not.deep.equal(
|
||||
this.publicKey,
|
||||
);
|
||||
await expect(this.mock.$verify(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be.false;
|
||||
await expect(this.mock.$verifySolidity(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be
|
||||
.false;
|
||||
await expect(this.mock.$verifyNative(this.messageHash, ...this.signature, ...this.publicKey)).to.eventually.be
|
||||
.false;
|
||||
await expect(
|
||||
this.mock.$recovery(this.messageHash, this.recovery, ...this.signature),
|
||||
).to.eventually.not.deep.equal(this.publicKey);
|
||||
});
|
||||
|
||||
it('fail to recover signature with invalid recovery bit', async function () {
|
||||
// flip recovery bit
|
||||
this.recovery = 1 - this.recovery;
|
||||
|
||||
expect(await this.mock.$recovery(this.messageHash, this.recovery, ...this.signature)).to.not.deep.equal(
|
||||
this.publicKey,
|
||||
);
|
||||
await expect(
|
||||
this.mock.$recovery(this.messageHash, this.recovery, ...this.signature),
|
||||
).to.eventually.not.deep.equal(this.publicKey);
|
||||
});
|
||||
});
|
||||
|
||||
@ -148,7 +174,7 @@ describe('P256', function () {
|
||||
const messageHash = ethers.sha256('0x' + msg);
|
||||
|
||||
// check verify
|
||||
expect(await this.mock.$verify(messageHash, r, s, x, y)).to.equal(result == 'valid');
|
||||
await expect(this.mock.$verify(messageHash, r, s, x, y)).to.eventually.equal(result == 'valid');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ const { ethers } = require('hardhat');
|
||||
const { expect } = require('chai');
|
||||
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
|
||||
|
||||
const precompile = require('../../helpers/precompiles');
|
||||
|
||||
const TEST_MESSAGE = ethers.id('OpenZeppelin');
|
||||
const TEST_MESSAGE_HASH = ethers.hashMessage(TEST_MESSAGE);
|
||||
|
||||
@ -25,15 +27,18 @@ describe('SignatureChecker (ERC1271)', function () {
|
||||
|
||||
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;
|
||||
await expect(this.mock.$isValidSignatureNow(this.signer, TEST_MESSAGE_HASH, this.signature)).to.eventually.be
|
||||
.true;
|
||||
});
|
||||
|
||||
it('with invalid signer', async function () {
|
||||
expect(await this.mock.$isValidSignatureNow(this.other, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
await expect(this.mock.$isValidSignatureNow(this.other, TEST_MESSAGE_HASH, this.signature)).to.eventually.be
|
||||
.false;
|
||||
});
|
||||
|
||||
it('with invalid signature', async function () {
|
||||
expect(await this.mock.$isValidSignatureNow(this.signer, WRONG_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
await expect(this.mock.$isValidSignatureNow(this.signer, WRONG_MESSAGE_HASH, this.signature)).to.eventually.be
|
||||
.false;
|
||||
});
|
||||
});
|
||||
|
||||
@ -41,19 +46,28 @@ describe('SignatureChecker (ERC1271)', 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;
|
||||
await expect(this.mock.getFunction(`$${fn}`)(this.wallet, TEST_MESSAGE_HASH, this.signature)).to.eventually.be
|
||||
.true;
|
||||
});
|
||||
|
||||
it('with invalid signer', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.mock, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
await expect(this.mock.getFunction(`$${fn}`)(this.mock, TEST_MESSAGE_HASH, this.signature)).to.eventually.be
|
||||
.false;
|
||||
});
|
||||
|
||||
it('with identity precompile', async function () {
|
||||
await expect(this.mock.getFunction(`$${fn}`)(precompile.identity, TEST_MESSAGE_HASH, this.signature)).to
|
||||
.eventually.be.false;
|
||||
});
|
||||
|
||||
it('with invalid signature', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.wallet, WRONG_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
await expect(this.mock.getFunction(`$${fn}`)(this.wallet, WRONG_MESSAGE_HASH, this.signature)).to.eventually
|
||||
.be.false;
|
||||
});
|
||||
|
||||
it('with malicious wallet', async function () {
|
||||
expect(await this.mock.getFunction(`$${fn}`)(this.malicious, TEST_MESSAGE_HASH, this.signature)).to.be.false;
|
||||
await expect(this.mock.getFunction(`$${fn}`)(this.malicious, TEST_MESSAGE_HASH, this.signature)).to.eventually
|
||||
.be.false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user