Skip to content

Commit

Permalink
Fix message id
Browse files Browse the repository at this point in the history
  • Loading branch information
Lohann committed Dec 16, 2024
1 parent 7fa8dac commit 2f75822
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 81 deletions.
6 changes: 3 additions & 3 deletions src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down
80 changes: 26 additions & 54 deletions src/Primitives.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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))));
}
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -239,7 +231,7 @@ library PrimitiveUtils {
assembly {
ptr := message
}
_intoCallback(ptr, domainSeparator, true, callback);
_intoCallback(ptr, true, callback);
}

/**
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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)
}
}
}
6 changes: 3 additions & 3 deletions src/utils/GasUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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() --
Expand Down
2 changes: 1 addition & 1 deletion test/Example.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions test/GasUtils.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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});
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
36 changes: 20 additions & 16 deletions test/Gateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ import {
Network,
GmpStatus,
PrimitiveUtils,
GmpSender
GmpSender,
GMP_VERSION
} from "../src/Primitives.sol";

contract SigUtilsTest is GatewayEIP712, Test {
using PrimitiveUtils for GmpMessage;

constructor() GatewayEIP712(69, address(0)) {}

function testPayload() public view {
function testPayload() public pure {
GmpMessage memory gmp = GmpMessage({
source: GmpSender.wrap(0x0),
srcNetwork: 42,
Expand All @@ -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);
}
Expand Down Expand Up @@ -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});
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions test/GmpTestTools.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 2f75822

Please sign in to comment.