Skip to content

Commit a050a1a

Browse files
Support for new for EIP-1271 verification (#572)
* update 1271 in Account.sol * add natspec and extend to AccountExtension.sol * remove unsued deps * update test * remove unnecessary encodeMessage --------- Co-authored-by: WhiteOakKong <aadam.debevec@outlook.com>
1 parent e511612 commit a050a1a

File tree

3 files changed

+61
-8
lines changed

3 files changed

+61
-8
lines changed

contracts/prebuilts/account/non-upgradeable/Account.sol

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ contract Account is AccountCore, ContractMetadata, ERC1271, ERC721Holder, ERC115
3333
using ECDSA for bytes32;
3434
using EnumerableSet for EnumerableSet.AddressSet;
3535

36+
bytes32 private constant MSG_TYPEHASH = keccak256("AccountMessage(bytes message)");
37+
3638
/*///////////////////////////////////////////////////////////////
3739
Constructor, Initializer, Modifiers
3840
//////////////////////////////////////////////////////////////*/
@@ -61,14 +63,15 @@ contract Account is AccountCore, ContractMetadata, ERC1271, ERC721Holder, ERC115
6163
}
6264

6365
/// @notice See EIP-1271
64-
function isValidSignature(bytes32 _hash, bytes memory _signature)
66+
function isValidSignature(bytes32 _message, bytes memory _signature)
6567
public
6668
view
6769
virtual
6870
override
6971
returns (bytes4 magicValue)
7072
{
71-
address signer = _hash.recover(_signature);
73+
bytes32 messageHash = getMessageHash(abi.encode(_message));
74+
address signer = messageHash.recover(_signature);
7275

7376
if (isAdmin(signer)) {
7477
return MAGICVALUE;
@@ -87,6 +90,16 @@ contract Account is AccountCore, ContractMetadata, ERC1271, ERC721Holder, ERC115
8790
}
8891
}
8992

93+
/**
94+
* @notice Returns the hash of message that should be signed for EIP1271 verification.
95+
* @param message Message to be hashed i.e. `keccak256(abi.encode(data))`
96+
* @return Hashed message
97+
*/
98+
function getMessageHash(bytes memory message) public view returns (bytes32) {
99+
bytes32 messageHash = keccak256(abi.encode(MSG_TYPEHASH, keccak256(message)));
100+
return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), messageHash));
101+
}
102+
90103
/*///////////////////////////////////////////////////////////////
91104
External functions
92105
//////////////////////////////////////////////////////////////*/

contracts/prebuilts/account/utils/AccountExtension.sol

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ contract AccountExtension is ContractMetadata, ERC1271, AccountPermissions, ERC7
3232
using ECDSA for bytes32;
3333
using EnumerableSet for EnumerableSet.AddressSet;
3434

35+
bytes32 private constant MSG_TYPEHASH = keccak256("AccountMessage(bytes message)");
36+
3537
/*///////////////////////////////////////////////////////////////
3638
Constructor, Initializer, Modifiers
3739
//////////////////////////////////////////////////////////////*/
@@ -63,14 +65,15 @@ contract AccountExtension is ContractMetadata, ERC1271, AccountPermissions, ERC7
6365
}
6466

6567
/// @notice See EIP-1271
66-
function isValidSignature(bytes32 _hash, bytes memory _signature)
68+
function isValidSignature(bytes32 _message, bytes memory _signature)
6769
public
6870
view
6971
virtual
7072
override
7173
returns (bytes4 magicValue)
7274
{
73-
address signer = _hash.recover(_signature);
75+
bytes32 messageHash = getMessageHash(abi.encode(_message));
76+
address signer = messageHash.recover(_signature);
7477

7578
if (isAdmin(signer)) {
7679
return MAGICVALUE;
@@ -89,6 +92,16 @@ contract AccountExtension is ContractMetadata, ERC1271, AccountPermissions, ERC7
8992
}
9093
}
9194

95+
/**
96+
* @notice Returns the hash of message that should be signed for EIP1271 verification.
97+
* @param message Message to be hashed i.e. `keccak256(abi.encode(data))`
98+
* @return Hashed message
99+
*/
100+
function getMessageHash(bytes memory message) public view returns (bytes32) {
101+
bytes32 messageHash = keccak256(abi.encode(MSG_TYPEHASH, keccak256(message)));
102+
return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), messageHash));
103+
}
104+
92105
/*///////////////////////////////////////////////////////////////
93106
External functions
94107
//////////////////////////////////////////////////////////////*/

src/test/smart-wallet/AccountVulnPOC.t.sol

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { TWProxy } from "contracts/infra/TWProxy.sol";
1010

1111
// Target
1212
import { IAccountPermissions } from "contracts/extension/interface/IAccountPermissions.sol";
13-
import { AccountFactory, Account } from "contracts/prebuilts/account/non-upgradeable/AccountFactory.sol";
13+
import { AccountFactory, Account as SimpleAccount } from "contracts/prebuilts/account/non-upgradeable/AccountFactory.sol";
1414

1515
library GPv2EIP1271 {
1616
bytes4 internal constant MAGICVALUE = 0x1626ba7e;
@@ -253,6 +253,8 @@ contract SimpleAccountVulnPOCTest is BaseTest {
253253
/*//////////////////////////////////////////////////////////
254254
Setup
255255
//////////////////////////////////////////////////////////////*/
256+
address account = accountFactory.getAddress(accountAdmin, bytes(""));
257+
256258
address[] memory approvedTargets = new address[](1);
257259
approvedTargets[0] = address(0x123); // allowing accountSigner permissions for some random contract, consider it as 0 address here
258260

@@ -270,7 +272,6 @@ contract SimpleAccountVulnPOCTest is BaseTest {
270272

271273
vm.prank(accountAdmin);
272274
bytes memory sig = _signSignerPermissionRequest(permissionsReq);
273-
address account = accountFactory.getAddress(accountAdmin, bytes(""));
274275
IAccountPermissions(payable(account)).setPermissionsForSigner(permissionsReq, sig);
275276

276277
// As expected, Account Signer is not be able to call setNum on numberContract since it doesnt have numberContract as approved target
@@ -292,14 +293,40 @@ contract SimpleAccountVulnPOCTest is BaseTest {
292293
Attack
293294
//////////////////////////////////////////////////////////////*/
294295

295-
//However they can bypass this by using signature verification on number contract instead
296+
// However they can bypass this by using signature verification on number contract instead
296297
vm.prank(accountSigner);
297298
bytes32 digest = keccak256(abi.encode(42));
298-
(uint8 v, bytes32 r, bytes32 s) = vm.sign(accountSignerPKey, digest);
299+
bytes32 toSign = SimpleAccount(payable(account)).getMessageHash(abi.encode(digest));
300+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(accountSignerPKey, toSign);
299301
bytes memory signature = abi.encodePacked(r, s, v);
300302

301303
vm.expectRevert("Account: caller not approved target.");
302304
numberContract.setNumBySignature(account, 42, signature);
303305
assertEq(numberContract.num(), 0);
306+
307+
// Signer can perform transaction if target is approved.
308+
address[] memory newApprovedTargets = new address[](2);
309+
newApprovedTargets[0] = address(0x123); // allowing accountSigner permissions for some random contract, consider it as 0 address here
310+
newApprovedTargets[1] = address(numberContract);
311+
312+
IAccountPermissions.SignerPermissionRequest memory updatedPermissionsReq = IAccountPermissions
313+
.SignerPermissionRequest(
314+
accountSigner,
315+
0,
316+
newApprovedTargets,
317+
1 ether,
318+
0,
319+
type(uint128).max,
320+
0,
321+
type(uint128).max,
322+
bytes32("another UID")
323+
);
324+
325+
vm.prank(accountAdmin);
326+
bytes memory sig2 = _signSignerPermissionRequest(updatedPermissionsReq);
327+
IAccountPermissions(payable(account)).setPermissionsForSigner(updatedPermissionsReq, sig2);
328+
329+
numberContract.setNumBySignature(account, 42, signature);
330+
assertEq(numberContract.num(), 42);
304331
}
305332
}

0 commit comments

Comments
 (0)