From 3e722aaf9712f6a15ce20726077331b5e3897ba2 Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Sat, 10 May 2025 09:43:06 +0200 Subject: [PATCH 1/2] Add Cross Chain Replay Protection --- script/BatchCallAndSponsor.s.sol | 2 +- src/BatchCallAndSponsor.sol | 3 ++- test/BatchCallAndSponsor.t.sol | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/script/BatchCallAndSponsor.s.sol b/script/BatchCallAndSponsor.s.sol index ca983fe..461cb80 100644 --- a/script/BatchCallAndSponsor.s.sol +++ b/script/BatchCallAndSponsor.s.sol @@ -97,7 +97,7 @@ contract BatchCallAndSponsorScript is Script { for (uint256 i = 0; i < calls.length; i++) { encodedCalls = abi.encodePacked(encodedCalls, calls[i].to, calls[i].value, calls[i].data); } - bytes32 digest = keccak256(abi.encodePacked(BatchCallAndSponsor(ALICE_ADDRESS).nonce(), encodedCalls)); + bytes32 digest = keccak256(abi.encodePacked(block.chainid, BatchCallAndSponsor(ALICE_ADDRESS).nonce(), encodedCalls)); (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, MessageHashUtils.toEthSignedMessageHash(digest)); bytes memory signature = abi.encodePacked(r, s, v); diff --git a/src/BatchCallAndSponsor.sol b/src/BatchCallAndSponsor.sol index b4196a4..b3206b2 100644 --- a/src/BatchCallAndSponsor.sol +++ b/src/BatchCallAndSponsor.sol @@ -18,6 +18,7 @@ import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; * 2. Directly by the smart account: When the account itself (i.e. address(this)) calls the function, no signature is required. * * Replay protection is achieved by using a nonce that is included in the signed message. + * Cross chain replay protection is achieved by using the chainid in the signed message. */ contract BatchCallAndSponsor { using ECDSA for bytes32; @@ -51,7 +52,7 @@ contract BatchCallAndSponsor { for (uint256 i = 0; i < calls.length; i++) { encodedCalls = abi.encodePacked(encodedCalls, calls[i].to, calls[i].value, calls[i].data); } - bytes32 digest = keccak256(abi.encodePacked(nonce, encodedCalls)); + bytes32 digest = keccak256(abi.encodePacked(block.chainid, nonce, encodedCalls)); bytes32 ethSignedMessageHash = MessageHashUtils.toEthSignedMessageHash(digest); diff --git a/test/BatchCallAndSponsor.t.sol b/test/BatchCallAndSponsor.t.sol index cc07cc2..2ca66d9 100644 --- a/test/BatchCallAndSponsor.t.sol +++ b/test/BatchCallAndSponsor.t.sol @@ -97,7 +97,8 @@ contract BatchCallAndSponsorTest is Test { encodedCalls = abi.encodePacked(encodedCalls, calls[i].to, calls[i].value, calls[i].data); } - bytes32 digest = keccak256(abi.encodePacked(BatchCallAndSponsor(ALICE_ADDRESS).nonce(), encodedCalls)); + bytes32 digest = + keccak256(abi.encodePacked(block.chainid, BatchCallAndSponsor(ALICE_ADDRESS).nonce(), encodedCalls)); (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, MessageHashUtils.toEthSignedMessageHash(digest)); bytes memory signature = abi.encodePacked(r, s, v); @@ -171,7 +172,7 @@ contract BatchCallAndSponsorTest is Test { vm.attachDelegation(signedDelegation); uint256 nonceBefore = BatchCallAndSponsor(ALICE_ADDRESS).nonce(); - bytes32 digest = keccak256(abi.encodePacked(nonceBefore, encodedCalls)); + bytes32 digest = keccak256(abi.encodePacked(block.chainid, nonceBefore, encodedCalls)); (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, MessageHashUtils.toEthSignedMessageHash(digest)); bytes memory signature = abi.encodePacked(r, s, v); From 3a7efd7cce6bebb3acdf8de559ba13bccab7fddf Mon Sep 17 00:00:00 2001 From: Benjamin Smith Date: Sat, 10 May 2025 09:45:21 +0200 Subject: [PATCH 2/2] forge fmt --- script/BatchCallAndSponsor.s.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/BatchCallAndSponsor.s.sol b/script/BatchCallAndSponsor.s.sol index 461cb80..a21492c 100644 --- a/script/BatchCallAndSponsor.s.sol +++ b/script/BatchCallAndSponsor.s.sol @@ -97,7 +97,8 @@ contract BatchCallAndSponsorScript is Script { for (uint256 i = 0; i < calls.length; i++) { encodedCalls = abi.encodePacked(encodedCalls, calls[i].to, calls[i].value, calls[i].data); } - bytes32 digest = keccak256(abi.encodePacked(block.chainid, BatchCallAndSponsor(ALICE_ADDRESS).nonce(), encodedCalls)); + bytes32 digest = + keccak256(abi.encodePacked(block.chainid, BatchCallAndSponsor(ALICE_ADDRESS).nonce(), encodedCalls)); (uint8 v, bytes32 r, bytes32 s) = vm.sign(ALICE_PK, MessageHashUtils.toEthSignedMessageHash(digest)); bytes memory signature = abi.encodePacked(r, s, v);