diff --git a/src/Gateway.sol b/src/Gateway.sol index efba5c8..3974ff5 100644 --- a/src/Gateway.sol +++ b/src/Gateway.sol @@ -186,7 +186,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { // Register/Revoke TSS keys using shard TSS signature function updateKeys(Signature calldata signature, UpdateKeysMessage calldata message) external { // Check if the message was already executed to prevent replay attacks - bytes32 messageHash = message.eip712TypedHash(DOMAIN_SEPARATOR); + bytes32 messageHash = message.eip712hash(); require(_executedMessages[messageHash] == bytes32(0), "message already executed"); // Verify the signature and store the message hash @@ -287,7 +287,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { // Convert the `GmpMessage` into `GmpCallback`, which is a more efficient representation. // see `src/Primitives.sol` for more details. - GmpCallback memory callback = message.intoCallback(DOMAIN_SEPARATOR); + GmpCallback memory callback = message.intoCallback(); // Verify the TSS Schnorr Signature _verifySignature(signature, callback.eip712hash); @@ -348,7 +348,7 @@ contract Gateway is IGateway, IExecutor, IUpgradable, GatewayEIP712 { { GmpMessage memory message = GmpMessage(source, NETWORK_ID, destinationAddress, routeId, executionGasLimit, salt, data); - prevHash = message.eip712TypedHash(route.domainSeparator); + prevHash = message.eip712hash(); prevMessageHash = prevHash; payload = message.data; } diff --git a/src/Primitives.sol b/src/Primitives.sol index 13d00aa..15153f0 100644 --- a/src/Primitives.sol +++ b/src/Primitives.sol @@ -7,6 +7,13 @@ import {BranchlessMath} from "./utils/BranchlessMath.sol"; import {UFloatMath, UFloat9x56} from "./utils/Float9x56.sol"; import {NetworkID} from "./NetworkID.sol"; +/** + * @dev GMP message EIP-712 Type Hash. + * Declared as raw value to enable it to be used in inline assembly + * keccak256("GmpMessage(bytes32 source,uint16 srcNetwork,address dest,uint16 destNetwork,uint256 gasLimit,uint256 salt,bytes data)") + */ +uint256 constant GMP_VERSION = 0; + /** * @dev Maximum size of the GMP payload */ @@ -135,13 +142,6 @@ struct GmpCallback { * @dev EIP-712 utility functions for primitives */ library PrimitiveUtils { - /** - * @dev GMP message EIP-712 Type Hash. - * Declared as raw value to enable it to be used in inline assembly - * keccak256("GmpMessage(bytes32 source,uint16 srcNetwork,address dest,uint16 destNetwork,uint256 gasLimit,uint256 salt,bytes data)") - */ - bytes32 internal constant GMP_MESSAGE_TYPE_HASH = 0xeb1e0a6b8c4db87ab3beb15e5ae24e7c880703e1b9ee466077096eaeba83623b; - function toAddress(GmpSender sender) internal pure returns (address) { return address(uint160(uint256(GmpSender.unwrap(sender)))); } @@ -184,14 +184,6 @@ library PrimitiveUtils { ); } - function eip712TypedHash(UpdateKeysMessage memory message, bytes32 domainSeparator) - internal - pure - returns (bytes32) - { - return _computeTypedHash(domainSeparator, eip712hash(message)); - } - function eip712hash(GmpMessage memory message) internal pure returns (bytes32 id) { bytes memory data = message.data; /// @solidity memory-safe-assembly @@ -203,7 +195,7 @@ library PrimitiveUtils { let offset := sub(message, 32) let backup := mload(offset) { - mstore(offset, GMP_MESSAGE_TYPE_HASH) + mstore(offset, GMP_VERSION) { let offset2 := add(offset, 0xe0) let backup2 := mload(offset2) @@ -218,7 +210,7 @@ library PrimitiveUtils { type MessagePtr is uint256; - function memToCallback(GmpMessage memory message, bytes32 domainSeparator) + function memToCallback(GmpMessage memory message) internal view returns (GmpCallback memory callback) @@ -227,10 +219,10 @@ library PrimitiveUtils { assembly { ptr := message } - _intoCallback(ptr, domainSeparator, false, callback); + _intoCallback(ptr, false, callback); } - function intoCallback(GmpMessage calldata message, bytes32 domainSeparator) + function intoCallback(GmpMessage calldata message) internal view returns (GmpCallback memory callback) @@ -239,7 +231,7 @@ library PrimitiveUtils { assembly { ptr := message } - _intoCallback(ptr, domainSeparator, true, callback); + _intoCallback(ptr, true, callback); } /** @@ -256,17 +248,23 @@ library PrimitiveUtils { * create he `IGmpReceiver.onGmpReceived` callback, unfortunately this requires inline assembly. * * @param message GmpMessage from calldata to be encoded - * @param domainSeparator EIP-712 domain separator * @param callback `GmpCallback` struct */ - function _intoCallback(MessagePtr message, bytes32 domainSeparator, bool isCalldata, GmpCallback memory callback) + function _intoCallback(MessagePtr message, bool isCalldata, GmpCallback memory callback) private view { assembly ("memory-safe") { // | MEMORY OFFSET | RESERVED FIELD | - // | 0x00e0..0x0100 <- `callback.data` pointer - // | 0x0100..0x0120 <- `callback.data.length` field. + // | 0x0000..0x0020 <- `GmpCallback.eip712hash` pointer + // | 0x0020..0x0040 <- `GmpCallback.source` pointer + // | 0x0040..0x0060 <- `GmpCallback.srcNetwork` pointer + // | 0x0060..0x0080 <- `GmpCallback.dest` pointer + // | 0x0080..0x00a0 <- `GmpCallback.destNetwork` pointer + // | 0x00a0..0x00c0 <- `GmpCallback.gasLimit` pointer + // | 0x00c0..0x00e0 <- `GmpCallback.salt` pointer + // | 0x00e0..0x0100 <- `GmpCallback.callback.offset` pointer + // | 0x0100..0x0120 <- `GmpCallback.callback.length` field. // | 0x0120..0x0124 <- `onGmpReceived.selector` field (4 bytes). // | 0x0124..0x0144 <- `onGmpReceived.id` param. // | 0x0144..0x0164 <- `onGmpReceived.network` param. @@ -279,8 +277,8 @@ library PrimitiveUtils { // First need compute to `GmpMessage` EIP-712 Type Hash // ////////////////////////////////////////////////////////// - // Store the `GMP_MESSAGE_TYPE_HASH` in the first 32 bytes of the callback. - mstore(add(callback, 0x0000), GMP_MESSAGE_TYPE_HASH) // callback.eip712hash + // Store the `GMP_VERSION` in the first 32 bytes of the callback. + mstore(add(callback, 0x0000), GMP_VERSION) // callback.eip712hash // Then we copy all `GmpMessage` fields to memory, except the `data` field. let size @@ -328,16 +326,10 @@ library PrimitiveUtils { // the `GmpMessage` struct. mstore(add(callback, 0x00e0), messageHash) - // Compute `keccak256(abi.encode(GMP_MESSAGE_TYPE_HASH, message.source, ..., keccak256(message.data)))` + // Compute `keccak256(abi.encode(GMP_VERSION, message.source, ..., keccak256(message.data)))` messageHash := keccak256(callback, 0x0100) - // Compute the final EIP-712 Signature Hash - mstore(0, 0x1901) - mstore(0x20, domainSeparator) - mstore(0x40, messageHash) // this will be restored at the end of this function - messageHash := keccak256(0x1e, 0x42) // GMP Typed Hash - - // Replace the `GMP_MESSAGE_TYPE_HASH` by the `eip712hash`. + // Replace the `GMP_VERSION` by the `message_id`. mstore(callback, messageHash) // Replace the `eip712hash` by the `callback.data.offset`. @@ -373,24 +365,4 @@ library PrimitiveUtils { } } } - - function eip712TypedHash(GmpMessage memory message, bytes32 domainSeparator) - internal - pure - returns (bytes32 messageHash) - { - messageHash = eip712hash(message); - messageHash = _computeTypedHash(domainSeparator, messageHash); - } - - function _computeTypedHash(bytes32 domainSeparator, bytes32 messageHash) private pure returns (bytes32 r) { - /// @solidity memory-safe-assembly - assembly { - mstore(0, 0x1901000000000000000000000000000000000000000000000000000000000000) - mstore(0x02, domainSeparator) - mstore(0x22, messageHash) - r := keccak256(0, 0x42) - mstore(0x22, 0) - } - } } diff --git a/src/utils/GasUtils.sol b/src/utils/GasUtils.sol index 817126e..757fe40 100644 --- a/src/utils/GasUtils.sol +++ b/src/utils/GasUtils.sol @@ -20,12 +20,12 @@ library GasUtils { /** * @dev Base cost of the `IExecutor.execute` method. */ - uint256 internal constant EXECUTION_BASE_COST = EXECUTION_SELECTOR_OVERHEAD + 45593; + uint256 internal constant EXECUTION_BASE_COST = EXECUTION_SELECTOR_OVERHEAD + 45503; /** * @dev Base cost of the `IGateway.submitMessage` method. */ - uint256 internal constant SUBMIT_BASE_COST = 25818 - 20; + uint256 internal constant SUBMIT_BASE_COST = 25692; /** * @dev Compute the gas cost of memory expansion. @@ -194,7 +194,7 @@ library GasUtils { uint256 memoryExpansion = 0x60; // -- First GAS opcode - // all opcodes until message.intoCallback(DOMAIN_SEPARATOR) + // all opcodes until message.intoCallback() baseCost += 449; // -- message.intoCallback() -- diff --git a/test/Example.t.sol b/test/Example.t.sol index f58234c..a7de3db 100644 --- a/test/Example.t.sol +++ b/test/Example.t.sol @@ -114,7 +114,7 @@ contract ExampleTest is Test { }); // Expect `GmpCreated` to be emitted - bytes32 messageID = gmp.eip712TypedHash(dstGateway.DOMAIN_SEPARATOR()); + bytes32 messageID = gmp.eip712hash(); vm.expectEmit(true, true, true, true, address(srcGateway)); emit IGateway.GmpCreated( messageID, GmpSender.unwrap(gmp.source), gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.salt, gmp.data diff --git a/test/GasUtils.t.sol b/test/GasUtils.t.sol index bf70ca3..460646d 100644 --- a/test/GasUtils.t.sol +++ b/test/GasUtils.t.sol @@ -97,7 +97,7 @@ contract GasUtilsBase is Test { } else { domainSeparator = _dstDomainSeparator; } - uint256 hash = uint256(gmp.eip712TypedHash(domainSeparator)); + uint256 hash = uint256(gmp.eip712hash()); (uint256 e, uint256 s) = signer.signPrehashed(hash, nonce); return Signature({xCoord: signer.xCoord(), e: e, s: s}); } @@ -169,6 +169,9 @@ contract GasUtilsBase is Test { data: hex"00" }); Signature memory sig = sign(gmp); + sig.xCoord = type(uint256).max; + sig.e = type(uint256).max; + sig.s = type(uint256).max; // Check if `IExecutor.execute` match the expected base cost (uint256 baseCost, uint256 nonZeros, uint256 zeros) = mock.execute(sig, gmp); @@ -198,7 +201,7 @@ contract GasUtilsBase is Test { // Execute the GMP message { - bytes32 gmpId = gmp.eip712TypedHash(_dstDomainSeparator); + bytes32 gmpId = gmp.eip712hash(); vm.expectEmit(true, true, true, true); emit IExecutor.GmpExecuted(gmpId, gmp.source, gmp.dest, GmpStatus.SUCCESS, bytes32(uint256(gasLimit))); uint256 balanceBefore = ctx.from.balance; diff --git a/test/Gateway.t.sol b/test/Gateway.t.sol index 5ce9f6a..654a0e3 100644 --- a/test/Gateway.t.sol +++ b/test/Gateway.t.sol @@ -22,7 +22,8 @@ import { Network, GmpStatus, PrimitiveUtils, - GmpSender + GmpSender, + GMP_VERSION } from "../src/Primitives.sol"; contract SigUtilsTest is GatewayEIP712, Test { @@ -30,7 +31,7 @@ contract SigUtilsTest is GatewayEIP712, Test { constructor() GatewayEIP712(69, address(0)) {} - function testPayload() public view { + function testPayload() public pure { GmpMessage memory gmp = GmpMessage({ source: GmpSender.wrap(0x0), srcNetwork: 42, @@ -40,9 +41,18 @@ contract SigUtilsTest is GatewayEIP712, Test { salt: 0, data: "" }); - bytes32 typedHash = gmp.eip712TypedHash(DOMAIN_SEPARATOR); + bytes32 typedHash = gmp.eip712hash(); bytes32 expected = keccak256( - hex"19013e3afdf794f679fcbf97eba49dbe6b67cec6c7d029f1ad9a5e1a8ffefa8db2724ed044f24764343e77b5677d43585d5d6f1b7618eeddf59280858c68350af1cd" + abi.encode( + GMP_VERSION, + gmp.source, + gmp.srcNetwork, + gmp.dest, + gmp.destNetwork, + gmp.gasLimit, + gmp.salt, + keccak256(gmp.data) + ) ); assertEq(typedHash, expected); } @@ -188,14 +198,8 @@ contract GatewayBase is Test { } } - function sign(GmpMessage memory gmp) internal view returns (Signature memory) { - bytes32 domainSeparator; - if (gmp.destNetwork == SRC_NETWORK_ID) { - domainSeparator = _srcDomainSeparator; - } else { - domainSeparator = _dstDomainSeparator; - } - bytes32 hash = gmp.eip712TypedHash(domainSeparator); + function sign(GmpMessage memory gmp) internal pure returns (Signature memory) { + bytes32 hash = gmp.eip712hash(); SigningKey memory signer = TestUtils.createSigner(SECRET); (uint256 e, uint256 s) = signer.signPrehashed(hash, SIGNING_NONCE); return Signature({xCoord: signer.xCoord(), e: e, s: s}); @@ -468,7 +472,7 @@ contract GatewayBase is Test { uint256 snapshot = vm.snapshotState(); // Must work if the funds and gas limit are sufficient - bytes32 id = gmp.eip712TypedHash(_dstDomainSeparator); + bytes32 id = gmp.eip712hash(); vm.expectEmit(true, true, true, true); emit IGateway.GmpCreated( id, GmpSender.unwrap(gmp.source), gmp.dest, gmp.destNetwork, gmp.gasLimit, gmp.salt, gmp.data @@ -532,7 +536,7 @@ contract GatewayBase is Test { // Verify the GMP message status assertEq(uint256(status), uint256(GmpStatus.SUCCESS), "Unexpected GMP status"); - Gateway.GmpInfo memory info = gateway.gmpInfo(gmp.eip712TypedHash(_dstDomainSeparator)); + Gateway.GmpInfo memory info = gateway.gmpInfo(gmp.eip712hash()); assertEq( uint256(info.status), uint256(GmpStatus.SUCCESS), "GMP status stored doesn't match the returned status" ); @@ -649,7 +653,7 @@ contract GatewayBase is Test { salt: 0, data: abi.encodePacked(uint256(100_000)) }); - bytes32 id = gmp.eip712TypedHash(_dstDomainSeparator); + bytes32 id = gmp.eip712hash(); // Check the previous message hash assertEq(gateway.prevMessageHash(), bytes32(uint256(2 ** 256 - 1)), "wrong previous message hash"); @@ -691,7 +695,7 @@ contract GatewayBase is Test { // Now the second GMP message should have the salt equals to previous gmp hash gmp.salt = uint256(id); - id = gmp.eip712TypedHash(_dstDomainSeparator); + id = gmp.eip712hash(); // Expect event vm.expectEmit(true, true, true, true); diff --git a/test/GmpTestTools.sol b/test/GmpTestTools.sol index 0387568..61bf849 100644 --- a/test/GmpTestTools.sol +++ b/test/GmpTestTools.sol @@ -314,14 +314,13 @@ library GmpTestTools { private { switchNetwork(network); - bytes32 domainSeparator = computeDomainSeparator(network, address(gateway)); SigningKey memory signer = TestUtils.signerFromEntropy(secret); for (uint256 i = 0; i < gmpMessages.length; i++) { GmpMessage memory message = gmpMessages[i]; // Compute the message ID - bytes32 messageID = PrimitiveUtils.eip712TypedHash(message, domainSeparator); + bytes32 messageID = PrimitiveUtils.eip712hash(message); // Skip if the message is not intended for this network if (message.destNetwork != network) {