From 7b9d6cb4f9131bf7555934273552e1cabd993f9e Mon Sep 17 00:00:00 2001 From: zer0dot Date: Thu, 22 Aug 2024 16:23:58 -0400 Subject: [PATCH 1/3] feat: replay-safe hash for 1271 SMA signatures --- src/account/SemiModularAccount.sol | 33 ++++++++++++++++++-- test/account/UpgradeableModularAccount.t.sol | 12 +++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/account/SemiModularAccount.sol b/src/account/SemiModularAccount.sol index f16d2363..4698aaca 100644 --- a/src/account/SemiModularAccount.sol +++ b/src/account/SemiModularAccount.sol @@ -26,6 +26,14 @@ contract SemiModularAccount is UpgradeableModularAccount { uint256 internal constant _SEMI_MODULAR_ACCOUNT_STORAGE_SLOT = 0x5b9dc9aa943f8fa2653ceceda5e3798f0686455280432166ba472eca0bc17a32; + // keccak256("EIP712Domain(uint256 chainId,address verifyingContract)") + bytes32 private constant _DOMAIN_SEPARATOR_TYPEHASH = + 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218; + + // keccak256("ReplaySafeHash(bytes32 hash)") + bytes32 private constant _REPLAY_SAFE_HASH_TYPEHASH = + 0x294a8735843d4afb4f017c76faf3b7731def145ed0025fc9b1d5ce30adf113ff; + ModuleEntity internal constant _FALLBACK_VALIDATION = ModuleEntity.wrap(bytes24(type(uint192).max)); uint256 internal constant _SIG_VALIDATION_PASSED = 0; @@ -88,6 +96,11 @@ contract SemiModularAccount is UpgradeableModularAccount { return "erc6900/reference-semi-modular-account/0.8.0"; } + function replaySafeHash(bytes32 hash) public view virtual returns (bytes32) { + return + MessageHashUtils.toTypedDataHash({domainSeparator: _domainSeparator(), structHash: _hashStruct(hash)}); + } + function _execUserOpValidation( ModuleEntity userOpValidationFunction, PackedUserOperation memory userOp, @@ -120,9 +133,9 @@ contract SemiModularAccount is UpgradeableModularAccount { if (msg.sender != fallbackSigner) { revert FallbackSignerMismatch(); } - return; + } else { + super._execRuntimeValidation(runtimeValidationFunction, callData, authorization); } - super._execRuntimeValidation(runtimeValidationFunction, callData, authorization); } function _exec1271Validation(ModuleEntity sigValidation, bytes32 hash, bytes calldata signature) @@ -134,7 +147,7 @@ contract SemiModularAccount is UpgradeableModularAccount { if (sigValidation.eq(_FALLBACK_VALIDATION)) { address fallbackSigner = _getFallbackSigner(); - if (SignatureChecker.isValidSignatureNow(fallbackSigner, hash, signature)) { + if (SignatureChecker.isValidSignatureNow(fallbackSigner, replaySafeHash(hash), signature)) { return _1271_MAGIC_VALUE; } return _1271_INVALID; @@ -176,6 +189,10 @@ contract SemiModularAccount is UpgradeableModularAccount { return address(uint160(bytes20(appendedData))); } + function _domainSeparator() internal view returns (bytes32) { + return keccak256(abi.encode(_DOMAIN_SEPARATOR_TYPEHASH, block.chainid, address(this))); + } + function _getSemiModularAccountStorage() internal pure returns (SemiModularAccountStorage storage) { SemiModularAccountStorage storage _storage; assembly ("memory-safe") { @@ -183,4 +200,14 @@ contract SemiModularAccount is UpgradeableModularAccount { } return _storage; } + + function _hashStruct(bytes32 hash) internal pure virtual returns (bytes32) { + bytes32 res; + assembly ("memory-safe") { + mstore(0x00, _REPLAY_SAFE_HASH_TYPEHASH) + mstore(0x20, hash) + res := keccak256(0, 0x40) + } + return res; + } } diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index a2ac85b0..205bdd4e 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -402,17 +402,15 @@ contract UpgradeableModularAccountTest is AccountTestBase { function test_isValidSignature() public { bytes32 message = keccak256("hello world"); + bytes32 replaySafeHash = vm.envOr("SMA_TEST", false) + ? SemiModularAccount(payable(account1)).replaySafeHash(message) + : singleSignerValidationModule.replaySafeHash(address(account1), message); + uint8 v; bytes32 r; bytes32 s; - if (vm.envOr("SMA_TEST", false)) { - // todo: implement replay-safe hashing for SMA - (v, r, s) = vm.sign(owner1Key, message); - } else { - bytes32 replaySafeHash = singleSignerValidationModule.replaySafeHash(address(account1), message); - (v, r, s) = vm.sign(owner1Key, replaySafeHash); - } + (v, r, s) = vm.sign(owner1Key, replaySafeHash); bytes memory signature = _encode1271Signature(_signerValidation, abi.encodePacked(r, s, v)); From 6faa4b605dad914be5decd3929ad7965f21d19af Mon Sep 17 00:00:00 2001 From: zer0dot Date: Thu, 22 Aug 2024 16:24:25 -0400 Subject: [PATCH 2/3] feat: slight optimization to hashStruct --- src/modules/ReplaySafeWrapper.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/ReplaySafeWrapper.sol b/src/modules/ReplaySafeWrapper.sol index 0ff9930b..37807027 100644 --- a/src/modules/ReplaySafeWrapper.sol +++ b/src/modules/ReplaySafeWrapper.sol @@ -26,7 +26,13 @@ abstract contract ReplaySafeWrapper is ModuleEIP712 { }); } - function _hashStruct(bytes32 hash) internal view virtual returns (bytes32) { - return keccak256(abi.encode(_REPLAY_SAFE_HASH_TYPEHASH, hash)); + function _hashStruct(bytes32 hash) internal pure virtual returns (bytes32) { + bytes32 res; + assembly ("memory-safe") { + mstore(0x00, _REPLAY_SAFE_HASH_TYPEHASH) + mstore(0x20, hash) + res := keccak256(0x00, 0x40) + } + return res; } } From df43ba75580eb7403bb9fd553d74f5c6463f6d05 Mon Sep 17 00:00:00 2001 From: zer0dot Date: Thu, 22 Aug 2024 17:38:39 -0400 Subject: [PATCH 3/3] refactor: inline variable declarations --- test/account/UpgradeableModularAccount.t.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/account/UpgradeableModularAccount.t.sol b/test/account/UpgradeableModularAccount.t.sol index 205bdd4e..c0713dca 100644 --- a/test/account/UpgradeableModularAccount.t.sol +++ b/test/account/UpgradeableModularAccount.t.sol @@ -406,11 +406,7 @@ contract UpgradeableModularAccountTest is AccountTestBase { ? SemiModularAccount(payable(account1)).replaySafeHash(message) : singleSignerValidationModule.replaySafeHash(address(account1), message); - uint8 v; - bytes32 r; - bytes32 s; - - (v, r, s) = vm.sign(owner1Key, replaySafeHash); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(owner1Key, replaySafeHash); bytes memory signature = _encode1271Signature(_signerValidation, abi.encodePacked(r, s, v));