ソースを参照

`MessageHashUtils`: Add `toDataWithIntendedValidatorHash(address, bytes32)` (#5081)

Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
StackOverflowExcept1on 7 ヶ月 前
コミット
a4b0d89900

+ 5 - 0
.changeset/quiet-shrimps-kiss.md

@@ -0,0 +1,5 @@
+---
+"openzeppelin-solidity": patch
+---
+
+`MessageHashUtils`: Add `toDataWithIntendedValidatorHash(address, bytes32)`.

+ 15 - 0
contracts/utils/cryptography/MessageHashUtils.sol

@@ -63,6 +63,21 @@ library MessageHashUtils {
         return keccak256(abi.encodePacked(hex"19_00", validator, data));
     }
 
+    /**
+     * @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.
+     */
+    function toDataWithIntendedValidatorHash(
+        address validator,
+        bytes32 messageHash
+    ) internal pure returns (bytes32 digest) {
+        assembly ("memory-safe") {
+            mstore(0x00, hex"19_00")
+            mstore(0x02, shl(96, validator))
+            mstore(0x16, messageHash)
+            digest := keccak256(0x00, 0x36)
+        }
+    }
+
     /**
      * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).
      *

+ 33 - 0
test/utils/cryptography/MessageHashUtils.t.sol

@@ -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)))
+        }
+    }
+}

+ 34 - 5
test/utils/cryptography/MessageHashUtils.test.js

@@ -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);
     });
   });
 });