Skip to content

Commit

Permalink
Dev (#47)
Browse files Browse the repository at this point in the history
* Added new ERC1271 signature verification logic in Kernel v0.2.3 (#43)

* Added 1271 wrapper

* Update kernel version to 0.2.3

* use kernel name and version from constants in tests

* added delegatecall support (#44)

* session key validator fixed for batch scenario

* test: fuzz testing for batched options include array

* fix: warning removed, forge fmt (#46)

---------

Co-authored-by: David Eiber <[email protected]>
  • Loading branch information
leekt and de33 authored Nov 15, 2023
1 parent 807b75a commit 90fa72e
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 86 deletions.
60 changes: 49 additions & 11 deletions src/Kernel.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ contract Kernel is EIP712, Compatibility, KernelStorage {
}
}

/// @notice Executes a function call to an external contract with delegatecall
/// @param to The address of the target contract
/// @param data The call data to be sent
function executeDelegateCall(address to, bytes memory data) external payable {
if (msg.sender != address(entryPoint) && msg.sender != address(this) && !_checkCaller()) {
revert NotAuthorizedCaller();
}
assembly {
let success := delegatecall(gas(), to, add(data, 0x20), mload(data), 0, 0)
returndatacopy(0, 0, returndatasize())
switch success
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}

/// @notice Executes a function call to an external contract batched
/// @param calls The calls to be executed, in order
/// @dev operation deprecated param, use executeBatch for batched transaction
Expand Down Expand Up @@ -235,29 +251,51 @@ contract Kernel is EIP712, Compatibility, KernelStorage {
}
}

function validateSignature(bytes32 hash, bytes calldata signature) public view returns(ValidationData) {
function validateSignature(bytes32 hash, bytes calldata signature) public view returns (ValidationData) {
return _validateSignature(hash, signature);
}

function _domainSeparator() internal view override returns (bytes32) {
// Obtain the name and version from the _domainNameAndVersion function.
(string memory _name, string memory _version) = _domainNameAndVersion();
bytes32 nameHash = keccak256(bytes(_name));
bytes32 versionHash = keccak256(bytes(_version));

// Use the proxy address for the EIP-712 domain separator.
address proxyAddress = address(this);

// Construct the domain separator with name, version, chainId, and proxy address.
bytes32 typeHash =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, proxyAddress));
}

/// @notice Checks if a signature is valid
/// @dev This function checks if a signature is valid based on the hash of the data signed.
/// @param hash The hash of the data that was signed
/// @param signature The signature to be validated
/// @return The magic value 0x1626ba7e if the signature is valid, otherwise returns 0xffffffff.
function isValidSignature(bytes32 hash, bytes calldata signature) public view returns (bytes4) {
ValidationData validationData = validateSignature(hash, signature);
// Include the proxy address in the domain separator
bytes32 domainSeparator = _domainSeparator();

// Recreate the signed message hash with the correct domain separator
bytes32 signedMessageHash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, hash));

ValidationData validationData = validateSignature(signedMessageHash, signature);
(ValidAfter validAfter, ValidUntil validUntil, address result) = parseValidationData(validationData);
if (ValidAfter.unwrap(validAfter) > block.timestamp) {
return 0xffffffff;
}
if (ValidUntil.unwrap(validUntil) < block.timestamp) {
return 0xffffffff;
}
if (result != address(0)) {

// Check if the signature is valid within the specified time frame and the result is successful
if (
ValidAfter.unwrap(validAfter) <= block.timestamp && ValidUntil.unwrap(validUntil) >= block.timestamp
&& result == address(0)
) {
// If all checks pass, return the ERC1271 magic value for a valid signature
return 0x1626ba7e;
} else {
// If any check fails, return the failure magic value
return 0xffffffff;
}

return 0x1626ba7e;
}

function _checkCaller() internal view returns (bool) {
Expand Down
2 changes: 1 addition & 1 deletion src/common/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {ValidationData} from "./Types.sol";

// constants for kernel metadata
string constant KERNEL_NAME = "Kernel";
string constant KERNEL_VERSION = "0.2.2";
string constant KERNEL_VERSION = "0.2.3";

// ERC4337 constants
uint256 constant SIG_VALIDATION_FAILED_UINT = 1;
Expand Down
47 changes: 19 additions & 28 deletions src/validator/SessionKeyValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@ contract SessionKeyValidator is IKernelValidator {
}

function invalidateNonce(uint128 nonce) public {
require(
nonce > nonces[msg.sender].invalidNonce && nonce <= nonces[msg.sender].lastNonce,
"SessionKeyValidator: invalid nonce"
);
require(nonce > nonces[msg.sender].invalidNonce, "SessionKeyValidator: invalid nonce");
nonces[msg.sender].invalidNonce = nonce;
nonces[msg.sender].lastNonce = nonce;
}

function disable(bytes calldata _data) external payable {
Expand Down Expand Up @@ -113,7 +111,14 @@ contract SessionKeyValidator is IKernelValidator {
function _getPermissions(bytes calldata _sig) internal pure returns (Permission[] calldata permissions) {
assembly {
permissions.offset := add(add(_sig.offset, 0x20), calldataload(_sig.offset))
permissions.length := calldataload(permissions.offset)
permissions.length := calldataload(add(_sig.offset, calldataload(_sig.offset)))
}
}

function _getProofs(bytes calldata _sig) internal pure returns (bytes32[][] calldata proofs) {
assembly {
proofs.length := calldataload(add(_sig.offset, calldataload(add(_sig.offset, 0x20))))
proofs.offset := add(add(_sig.offset, 0x20), calldataload(add(_sig.offset, 0x20)))
}
}

Expand Down Expand Up @@ -152,8 +157,7 @@ contract SessionKeyValidator is IKernelValidator {
if (bytes4(callData[0:4]) == Kernel.execute.selector) {
(Permission calldata permission, bytes32[] calldata merkleProof) = _getPermission(userOp.signature[85:]);
require(
address(bytes20(userOp.callData[16:36])) == permission.target,
"SessionKeyValidator: target mismatch"
address(bytes20(userOp.callData[16:36])) == permission.target, "SessionKeyValidator: target mismatch"
);
require(
uint256(bytes32(userOp.callData[36:68])) <= permission.valueLimit,
Expand All @@ -178,10 +182,9 @@ contract SessionKeyValidator is IKernelValidator {
return _verifyUserOpHash(sessionKey, validAfter, session.validUntil);
} else if (bytes4(callData[0:4]) == Kernel.executeBatch.selector) {
Permission[] calldata permissions = _getPermissions(userOp.signature[85:]);
(, bytes32[] memory merkleProof, bool[] memory flags, uint256[] memory index) =
abi.decode(userOp.signature[85:], (Permission[], bytes32[], bool[], uint256[]));
(bytes32[] memory leaves, ValidAfter validAfter) = _verifyParams(sessionKey, callData, permissions, index);
if (!MerkleProofLib.verifyMultiProof(merkleProof, session.merkleRoot, leaves, flags)) {
bytes32[][] calldata merkleProof = _getProofs(userOp.signature[85:]);
(ValidAfter validAfter, bool verifyFailed) = _verifyParams(sessionKey, callData, permissions, merkleProof);
if (verifyFailed) {
return SIG_VALIDATION_FAILED;
}
return _verifyUserOpHash(sessionKey, validAfter, session.validUntil);
Expand Down Expand Up @@ -224,43 +227,31 @@ contract SessionKeyValidator is IKernelValidator {
address sessionKey,
bytes calldata callData,
Permission[] calldata _permissions,
uint256[] memory index
) internal returns (bytes32[] memory leaves, ValidAfter maxValidAfter) {
bytes32[][] calldata _merkleProof
) internal returns (ValidAfter maxValidAfter, bool verifyFailed) {
Call[] calldata calls;
assembly {
calls.offset := add(add(callData.offset, 0x24), calldataload(add(callData.offset, 4)))
calls.length := calldataload(add(add(callData.offset, 4), calldataload(add(callData.offset, 4))))
}
uint256 i = 0;
leaves = _generateLeaves(index);
SessionData storage session = sessionData[sessionKey][msg.sender];
maxValidAfter = session.validAfter;
for (i = 0; i < calls.length; i++) {
Call calldata call = calls[i];
Permission calldata permission = _permissions[i];
require(
call.to == permission.target, "SessionKeyValidator: target mismatch"
);
require(call.to == permission.target, "SessionKeyValidator: target mismatch");
require(uint256(bytes32(call.value)) <= permission.valueLimit, "SessionKeyValidator: value limit exceeded");
require(verifyPermission(call.data, permission), "SessionKeyValidator: permission verification failed");
ValidAfter validAfter =
_updateValidAfter(permission, keccak256(abi.encodePacked(session.nonce, permission.index)));
if (ValidAfter.unwrap(validAfter) > ValidAfter.unwrap(maxValidAfter)) {
maxValidAfter = validAfter;
}
leaves[index[i]] = keccak256(abi.encode(permission));
}
}

function _generateLeaves(uint256[] memory _indexes) internal pure returns (bytes32[] memory leaves) {
uint256 maxIndex;
uint256 i = 0;
for (i = 0; i < _indexes.length; i++) {
if (_indexes[i] > maxIndex) {
maxIndex = _indexes[i];
if (!MerkleProofLib.verify(_merkleProof[i], session.merkleRoot, keccak256(abi.encode(permission)))) {
return (maxValidAfter, true);
}
}
leaves = new bytes32[](maxIndex + 1);
}

function verifyPermission(bytes calldata data, Permission calldata permission) internal pure returns (bool) {
Expand Down
10 changes: 6 additions & 4 deletions src/validator/WeightedECDSAValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
proposal.status = ProposalStatus.Rejected;
}

function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 missingFunds)
function validateUserOp(UserOperation calldata userOp, bytes32, uint256)
external
payable
returns (ValidationData validationData)
Expand All @@ -149,7 +149,9 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
for (uint256 i = 0; i < sigCount; i++) {
// last sig is for userOpHash verification
signer = ECDSA.recover(
_hashTypedData(keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), callDataAndNonceHash))),
_hashTypedData(
keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), callDataAndNonceHash))
),
sig[i * 65:(i + 1) * 65]
);
vote = voteStatus[callDataAndNonceHash][signer][msg.sender];
Expand All @@ -173,11 +175,11 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
}
}

function validCaller(address, bytes calldata) external view override returns (bool) {
function validCaller(address, bytes calldata) external pure override returns (bool) {
return false;
}

function validateSignature(bytes32 hash, bytes calldata signature) external view returns (ValidationData) {
function validateSignature(bytes32, bytes calldata) external pure returns (ValidationData) {
return SIG_VALIDATION_FAILED;
}
}
3 changes: 1 addition & 2 deletions test/foundry/KernelLiteECDSA.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ contract KernelECDSATest is KernelTestBase {
function test_transfer_ownership() external {
address newOwner = makeAddr("new owner");
UserOperation memory op = entryPoint.fillUserOp(
address(kernel),
abi.encodeWithSelector(KernelLiteECDSA.transferOwnership.selector, newOwner)
address(kernel), abi.encodeWithSelector(KernelLiteECDSA.transferOwnership.selector, newOwner)
);
op.signature = signUserOp(op);
UserOperation[] memory ops = new UserOperation[](1);
Expand Down
33 changes: 29 additions & 4 deletions test/foundry/KernelTestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {IKernelValidator} from "src/interfaces/IKernelValidator.sol";

import {Call, ExecutionDetail} from "src/common/Structs.sol";
import {ValidationData, ValidUntil, ValidAfter} from "src/common/Types.sol";
import {KERNEL_VERSION, KERNEL_NAME} from "src/common/Constants.sol";

import {ERC4337Utils} from "test/foundry/utils/ERC4337Utils.sol";
import {Test} from "forge-std/Test.sol";
Expand Down Expand Up @@ -97,7 +98,24 @@ abstract contract KernelTestBase is Test {
}
}

function test_external_call_execute_delegatecall_success() external {
address[] memory validCallers = getOwners();
for (uint256 i = 0; i < validCallers.length; i++) {
vm.prank(validCallers[i]);
kernel.executeDelegateCall(validCallers[i], "");
}
}

function test_external_call_execute_delegatecall_fail() external {
address[] memory validCallers = getOwners();
for (uint256 i = 0; i < validCallers.length; i++) {
vm.prank(address(uint160(validCallers[i]) + 1));
vm.expectRevert();
kernel.executeDelegateCall(validCallers[i], "");
}
}

function test_external_call_execute_delegatecall_option_fail() external {
address[] memory validCallers = getOwners();
for (uint256 i = 0; i < validCallers.length; i++) {
vm.prank(validCallers[i]);
Expand Down Expand Up @@ -143,8 +161,8 @@ abstract contract KernelTestBase is Test {
(bytes1 fields, string memory name, string memory version,, address verifyingContract, bytes32 salt,) =
kernel.eip712Domain();
assertEq(fields, bytes1(hex"0f"));
assertEq(name, "Kernel");
assertEq(version, "0.2.2");
assertEq(name, KERNEL_NAME);
assertEq(version, KERNEL_VERSION);
assertEq(verifyingContract, address(kernel));
assertEq(salt, bytes32(0));
}
Expand Down Expand Up @@ -177,9 +195,16 @@ abstract contract KernelTestBase is Test {
}

function test_validate_signature() external {
Kernel kernel2 = Kernel(payable(factory.createAccount(address(kernelImpl), getInitializeData(), 3)));
bytes32 hash = keccak256(abi.encodePacked("hello world"));
bytes memory sig = signHash(hash);
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01", ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)), hash
)
);
bytes memory sig = signHash(digest);
assertEq(kernel.isValidSignature(hash, sig), Kernel.isValidSignature.selector);
assertEq(kernel2.isValidSignature(hash, sig), bytes4(0xffffffff));
}

function test_fail_validate_wrongsignature() external {
Expand Down Expand Up @@ -471,7 +496,7 @@ abstract contract KernelTestBase is Test {
return keccak256(
abi.encodePacked(
"\x19\x01",
ERC4337Utils._buildDomainSeparator("Kernel", "0.2.2", address(kernel)),
ERC4337Utils._buildDomainSeparator(KERNEL_NAME, KERNEL_VERSION, address(kernel)),
ERC4337Utils.getStructHash(sig, validUntil, validAfter, validator, executor, enableData)
)
);
Expand Down
5 changes: 4 additions & 1 deletion test/foundry/utils/Merkle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function _getRoot(bytes32[] memory data) pure returns (bytes32) {
return data[0];
}

function _getProof(bytes32[] memory data, uint256 nodeIndex) pure returns (bytes32[] memory) {
function _getProof(bytes32[] memory data, uint256 nodeIndex, bool wrongProof) pure returns (bytes32[] memory) {
require(data.length > 1);

bytes32[] memory result = new bytes32[](64);
Expand All @@ -33,6 +33,9 @@ function _getProof(bytes32[] memory data, uint256 nodeIndex) pure returns (bytes
assembly {
mstore(result, pos)
}
if (wrongProof) {
result[0] = result[0] ^ bytes32(uint256(0x01));
}

return result;
}
Expand Down
Loading

0 comments on commit 90fa72e

Please sign in to comment.