Skip to content

Commit

Permalink
Feat/weighted ecdsa (zerodevapp#74)
Browse files Browse the repository at this point in the history
* updated dependencies

* updates solady to latest

* weighted ecdsa to valdiate the userOp for the last sig

* removed toEthSignedMessage

* test done
  • Loading branch information
leekt authored Jan 31, 2024
1 parent f4b7e65 commit ea3f566
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 17 deletions.
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
[submodule "lib/I4337"]
path = lib/I4337
url = https://github.com/leekt/I4337
[submodule "lib/FreshCryptoLib"]
path = lib/FreshCryptoLib
url = https://github.com/rdubois-crypto/FreshCryptoLib
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
2 changes: 1 addition & 1 deletion lib/forge-std
2 changes: 1 addition & 1 deletion lib/solady
Submodule solady updated 53 files
+575 −531 .gas-snapshot
+3 −2 .github/workflows/ci-all-via-ir.yml
+1 −1 .github/workflows/ci-wake.yml
+7 −6 .github/workflows/ci.yml
+0 −4 .gitmodules
+3 −1 README.md
+1 −1 foundry.toml
+7 −7 js/solady.js
+0 −1 lib/ds-test
+1 −1 package.json
+2 −0 src/Milady.sol
+26 −1 src/accounts/ERC4337.sol
+1 −1 src/accounts/ERC4337Factory.sol
+1 −1 src/accounts/ERC6551.sol
+1 −1 src/accounts/ERC6551Proxy.sol
+4 −1 src/tokens/ERC4626.sol
+4 −4 src/tokens/ERC6909.sol
+55 −33 src/tokens/ERC721.sol
+7 −11 src/utils/DateTimeLib.sol
+0 −1 src/utils/ERC1967Factory.sol
+138 −88 src/utils/FixedPointMathLib.sol
+185 −0 src/utils/Initializable.sol
+3 −3 src/utils/JSONParserLib.sol
+17 −22 src/utils/LibBit.sol
+107 −6 src/utils/LibClone.sol
+25 −0 src/utils/LibPRNG.sol
+18 −9 src/utils/LibString.sol
+19 −20 src/utils/LibZip.sol
+82 −9 src/utils/MetadataReaderLib.sol
+87 −28 src/utils/MinHeapLib.sol
+71 −58 src/utils/RedBlackTreeLib.sol
+55 −0 src/utils/ReentrancyGuard.sol
+7 −0 test/ERC4337.t.sol
+3 −3 test/ERC6909.t.sol
+29 −0 test/ERC721.t.sol
+99 −16 test/FixedPointMathLib.t.sol
+159 −0 test/Initializable.t.sol
+29 −1 test/JSONParserLib.t.sol
+22 −0 test/LibBit.t.sol
+50 −1 test/LibClone.t.sol
+33 −0 test/LibPRNG.t.sol
+5 −0 test/LibString.t.sol
+61 −28 test/MetadataReaderLib.t.sol
+157 −8 test/MinHeapLib.t.sol
+88 −32 test/RedBlackTree.t.sol
+86 −0 test/ReentrancyGuard.t.sol
+29 −1 test/utils/TestPlus.sol
+1 −1 test/utils/forge-std/Test.sol
+517 −0 test/utils/forge-std/ds-test/test.sol
+2 −2 test/utils/mocks/MockERC6909.sol
+11 −0 test/utils/mocks/MockERC721.sol
+80 −0 test/utils/mocks/MockInitializable.sol
+136 −0 test/utils/mocks/MockReentrancyGuard.sol
32 changes: 21 additions & 11 deletions src/validator/WeightedECDSAValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
proposal.status = ProposalStatus.Rejected;
}

function validateUserOp(UserOperation calldata userOp, bytes32, uint256)
function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256)
external
payable
returns (ValidationData validationData)
Expand All @@ -186,10 +186,10 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
bytes calldata sig = userOp.signature;
// parse sig with 65 bytes
uint256 sigCount = sig.length / 65;
require(sigCount > 0, "No sig");
address signer;
VoteStorage storage vote;
for (uint256 i = 0; i < sigCount && !passed; i++) {
// last sig is for userOpHash verification
for (uint256 i = 0; i < sigCount - 1 && !passed; i++) {
signer = ECDSA.recover(
_hashTypedData(
keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), callDataAndNonceHash))
Expand All @@ -206,18 +206,28 @@ contract WeightedECDSAValidator is EIP712, IKernelValidator {
passed = true;
}
}
if (passed) {
validationData = packValidationData(ValidAfter.wrap(0), ValidUntil.wrap(0));
// userOpHash verification for the last sig
signer = ECDSA.recover(ECDSA.toEthSignedMessageHash(userOpHash), sig[sig.length - 65:]);
vote = voteStatus[callDataAndNonceHash][signer][msg.sender];
if (vote.status == VoteStatus.NA) {
vote.status = VoteStatus.Approved;
totalWeight += guardian[signer][msg.sender].weight;
if (totalWeight >= threshold) {
passed = true;
}
}
if (passed && guardian[signer][msg.sender].weight != 0) {
proposal.status = ProposalStatus.Executed;
} else {
validationData = SIG_VALIDATION_FAILED;
return packValidationData(ValidAfter.wrap(0), ValidUntil.wrap(0));
}
} else if (proposal.status == ProposalStatus.Approved || passed) {
validationData = packValidationData(proposal.validAfter, ValidUntil.wrap(0));
proposal.status = ProposalStatus.Executed;
} else {
return SIG_VALIDATION_FAILED;
address signer = ECDSA.recover(ECDSA.toEthSignedMessageHash(userOpHash), userOp.signature);
if (guardian[signer][msg.sender].weight != 0) {
proposal.status = ProposalStatus.Executed;
return packValidationData(ValidAfter.wrap(0), ValidUntil.wrap(0));
}
}
return SIG_VALIDATION_FAILED;
}

function getApproval(address kernel, bytes32 hash) public view returns (uint256 approvals, bool passed) {
Expand Down
158 changes: 158 additions & 0 deletions test/foundry/validator/WeightedECDSAValidator.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
pragma solidity ^0.8.0;

import {IEntryPoint} from "I4337/interfaces/IEntryPoint.sol";
import "src/Kernel.sol";
import "src/validator/WeightedECDSAValidator.sol";
// test artifacts
// test utils
import "forge-std/Test.sol";
import {ERC4337Utils} from "src/utils/ERC4337Utils.sol";
import {KernelTestBase} from "src/utils/KernelTestBase.sol";
import {TestExecutor} from "src/mock/TestExecutor.sol";
import {TestValidator} from "src/mock/TestValidator.sol";
import {IKernel} from "src/interfaces/IKernel.sol";

using ERC4337Utils for IEntryPoint;

contract KernelWeightedECDSATest is KernelTestBase {
address[] public owners;
uint256[] public ownerKeys;
uint24[] public weights;
uint24 public threshold;
uint48 public delay;

function setUp() public virtual {
_initialize();
defaultValidator = new WeightedECDSAValidator();
owners = new address[](3);
ownerKeys = new uint256[](3);
(owners[0], ownerKeys[0]) = makeAddrAndKey("owner0");
(owners[1], ownerKeys[1]) = makeAddrAndKey("owner1");
(owners[2], ownerKeys[2]) = makeAddrAndKey("owner2");
weights = [uint24(1), uint24(2), uint24(3)];
threshold = 3;
delay = 0;
_setAddress();
_setExecutionDetail();
}

function test_ignore() external {}

function _setExecutionDetail() internal virtual override {
executionDetail.executor = address(new TestExecutor());
executionSig = TestExecutor.doNothing.selector;
executionDetail.validator = new TestValidator();
}

function getEnableData() internal view virtual override returns (bytes memory) {
return "";
}

function getValidatorSignature(UserOperation memory) internal view virtual override returns (bytes memory) {
return "";
}

function getOwners() internal view override returns (address[] memory) {
return owners;
}

function getInitializeData() internal view override returns (bytes memory) {
bytes memory data = abi.encode(owners, weights, threshold, delay);
return abi.encodeWithSelector(KernelStorage.initialize.selector, defaultValidator, data);
}

function test_external_call_execute_success() external override {
vm.skip(true);
}

function test_external_call_default() external override {
vm.skip(true);
}

function test_external_call_execute_delegatecall_success() external override {
vm.skip(true);
}

function test_external_call_batch_execute_success() external override {
vm.skip(true);
}

function signUserOp(UserOperation memory op) internal view override returns (bytes memory) {
bytes32 calldataAndNonceHash = keccak256(abi.encode(op.sender, op.callData, op.nonce));

bytes32 digest = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("WeightedECDSAValidator"),
keccak256("0.0.2"),
block.chainid,
address(defaultValidator)
)
);

bytes32 structHash =
keccak256(abi.encode(keccak256("Approve(bytes32 callDataAndNonceHash)"), calldataAndNonceHash));
assembly {
// Compute the digest.
mstore(0x00, 0x1901000000000000) // Store "\x19\x01".
mstore(0x1a, digest) // Store the domain separator.
mstore(0x3a, structHash) // Store the struct hash.
digest := keccak256(0x18, 0x42)
// Restore the part of the free memory slot that was overwritten.
mstore(0x3a, 0)
}

(uint8 v0, bytes32 r0, bytes32 s0) = vm.sign(ownerKeys[0], digest);
(uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerKeys[1], digest);
bytes memory opSig = entryPoint.signUserOpHash(vm, ownerKeys[2], op);
return abi.encodePacked(bytes4(0x00000000), r0, s0, v0, r1, s1, v1, opSig);
}

function getWrongSignature(UserOperation memory op) internal view override returns (bytes memory) {
return abi.encodePacked(bytes4(0x00000000), entryPoint.signUserOpHash(vm, ownerKeys[0], op));
}

function signHash(bytes32 hash) internal view override returns (bytes memory) {
(uint8 v0, bytes32 r0, bytes32 s0) = vm.sign(ownerKeys[0], hash);
(uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerKeys[1], hash);
(uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(ownerKeys[2], hash);
return abi.encodePacked(r0, s0, v0, r1, s1, v1, r2, s2, v2);
}

function getWrongSignature(bytes32 hash) internal view override returns (bytes memory) {
(uint8 v0, bytes32 r0, bytes32 s0) = vm.sign(ownerKeys[0], hash);
(uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(ownerKeys[1] + 1, hash);
(uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(ownerKeys[2] + 1, hash);
return abi.encodePacked(r0, s0, v0, r1, s1, v1, r2, s2, v2);
}

function test_default_validator_enable() external override {
//UserOperation memory op = buildUserOperation(
// abi.encodeWithSelector(
// IKernel.execute.selector,
// address(defaultValidator),
// 0,
// abi.encodeWithSelector(ECDSAValidator.enable.selector, abi.encodePacked(address(0xdeadbeef))),
// Operation.Call
// )
//);
//performUserOperationWithSig(op);
//(address owner) = ECDSAValidator(address(defaultValidator)).ecdsaValidatorStorage(address(kernel));
//assertEq(owner, address(0xdeadbeef), "owner should be 0xdeadbeef");
}

function test_default_validator_disable() external override {
//UserOperation memory op = buildUserOperation(
// abi.encodeWithSelector(
// IKernel.execute.selector,
// address(defaultValidator),
// 0,
// abi.encodeWithSelector(ECDSAValidator.disable.selector, ""),
// Operation.Call
// )
//);
//performUserOperationWithSig(op);
//(address owner) = ECDSAValidator(address(defaultValidator)).ecdsaValidatorStorage(address(kernel));
//assertEq(owner, address(0), "owner should be 0");
}
}

0 comments on commit ea3f566

Please sign in to comment.