Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,15 @@ branch = release/v3.1
[submodule "hooks/caller/lib/kernel"]
path = hooks/caller/lib/kernel
url = https://github.com/zerodevapp/kernel
[submodule "validators/erc1271/lib/forge-std"]
path = validators/erc1271/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "validators/erc1271/lib/kernel"]
path = validators/erc1271/lib/kernel
url = https://github.com/zerodevapp/kernel
[submodule "validators/erc1271/lib/safe-smart-account"]
path = validators/erc1271/lib/safe-smart-account
url = https://github.com/safe-global/safe-smart-account
[submodule "lib/safe-smart-account"]
path = lib/safe-smart-account
url = https://github.com/safe-global/safe-smart-account
1 change: 1 addition & 0 deletions lib/safe-smart-account
Submodule safe-smart-account added at bf943f
43 changes: 43 additions & 0 deletions validators/erc1271/.github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Show Forge version
run: |
forge --version

- name: Run Forge fmt
run: |
forge fmt --check
id: fmt

- name: Run Forge build
run: |
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
14 changes: 14 additions & 0 deletions validators/erc1271/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
66 changes: 66 additions & 0 deletions validators/erc1271/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
8 changes: 8 additions & 0 deletions validators/erc1271/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.29"
via_ir = false

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions validators/erc1271/lib/forge-std
Submodule forge-std added at 77041d
1 change: 1 addition & 0 deletions validators/erc1271/lib/kernel
Submodule kernel added at cd697c
1 change: 1 addition & 0 deletions validators/erc1271/lib/safe-smart-account
Submodule safe-smart-account added at bf943f
1 change: 1 addition & 0 deletions validators/erc1271/lib/solady
Submodule solady added at b609a9
85 changes: 85 additions & 0 deletions validators/erc1271/src/ERC1271Validator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
pragma solidity ^0.8.0;

import {IValidator, IHook} from "kernel/src/interfaces/IERC7579Modules.sol";
import {MODULE_TYPE_VALIDATOR, MODULE_TYPE_HOOK} from "kernel/src/types/Constants.sol";
import {PackedUserOperation} from "kernel/src/interfaces/PackedUserOperation.sol";
import {
SIG_VALIDATION_FAILED_UINT,
SIG_VALIDATION_SUCCESS_UINT,
ERC1271_MAGICVALUE,
ERC1271_INVALID
} from "kernel/src/types/Constants.sol";
import {ECDSA} from "solady/utils/ECDSA.sol";
import {EIP712} from "solady/utils/EIP712.sol";

interface IERC1271 {
function isValidSignature(bytes32 hash, bytes calldata sig) external view returns (bytes4);
}

// keccak256("MessageHash(bytes32 hash)")
bytes32 constant MESSAGE_TYPE_HASH = 0xddbb42c14c926ce2b204d00ecc48d770e111c85fe954c1bbbb4a7f6f4b2fbbb9;

contract ERC1271Validator is IValidator, EIP712 {
mapping(address account => address verifier) public verifier;

function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
name = "ERC1271Validator";
version = "0.0.1";
}

function onInstall(bytes calldata _data) external payable override {
require(_data.length == 20, "invalid input data");
verifier[msg.sender] = address(bytes20(_data[0:20]));
}

function onUninstall(bytes calldata) external payable override {
delete verifier[msg.sender];
}

function isModuleType(uint256 typeID) external view override returns (bool) {
return typeID == MODULE_TYPE_VALIDATOR;
}

function isInitialized(address smartAccount) external view override returns (bool) {
return _isInitialized(smartAccount);
}

function _isInitialized(address smartAccount) internal view returns (bool) {
return verifier[smartAccount] != address(0);
}

function validateUserOp(PackedUserOperation calldata _userOp, bytes32 _userOpHash)
external
payable
override
returns (uint256)
{
return _verifySignature(msg.sender, _userOpHash, _userOp.signature);
}

function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata data)
external
view
returns (bytes4)
{
return _verifySignature(msg.sender, hash, data) == SIG_VALIDATION_SUCCESS_UINT
? ERC1271_MAGICVALUE
: ERC1271_INVALID;
}

function _verifySignature(address account, bytes32 hash, bytes calldata signature) private view returns (uint256) {
address _verifier = verifier[account];

bytes32 wrappedHash = _toMessageTypedDataHash(hash);
try IERC1271(_verifier).isValidSignature(wrappedHash, signature) returns (bytes4 result) {
return result == ERC1271_MAGICVALUE ? SIG_VALIDATION_SUCCESS_UINT : SIG_VALIDATION_FAILED_UINT;
} catch {
return SIG_VALIDATION_FAILED_UINT;
}
}

function _toMessageTypedDataHash(bytes32 hash) internal view returns (bytes32) {
bytes32 structHash = keccak256(abi.encode(MESSAGE_TYPE_HASH, hash));
return _hashTypedData(structHash);
}
}
69 changes: 69 additions & 0 deletions validators/erc1271/test/ERC1271Validator.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
pragma solidity ^0.8.0;

import {ERC1271Validator} from "../src/ERC1271Validator.sol";
import {MockCallee, KernelTestBase} from "kernel/test/base/KernelTestBase.sol";
import {PackedUserOperation} from "kernel/test/base/KernelTestBase.sol";
import {ValidatorLib} from "kernel/test/base/KernelTestBase.sol";
import {IHook, IValidator} from "kernel/test/base/KernelTestBase.sol";
import {ValidatorLib, ValidationId, ValidationMode, ValidationType} from "kernel/test/base/KernelTestBase.sol";
import {VALIDATION_MODE_ENABLE, VALIDATION_TYPE_VALIDATOR} from "kernel/test/base/KernelTestBase.sol";

import {SafeProxyFactory} from "safe-smart-account/contracts/proxies/SafeProxyFactory.sol";
import {Safe} from "safe-smart-account/contracts/Safe.sol";
import {CompatibilityFallbackHandler} from "safe-smart-account/contracts/handler/CompatibilityFallbackHandler.sol";

contract WebAuthnValidatorTest is KernelTestBase {
ERC1271Validator erc1271Validator;
SafeProxyFactory proxyFactory;
Safe safeSingleton;
CompatibilityFallbackHandler handler;

Safe ownerSafe;
address owner;
uint256 ownerKey;

function _setRootValidationConfig() internal override {
(owner, ownerKey) = makeAddrAndKey("Owner");
erc1271Validator = new ERC1271Validator();
proxyFactory = new SafeProxyFactory();
safeSingleton = new Safe();
handler = new CompatibilityFallbackHandler();

address[] memory owners = new address[](1);
owners[0] = owner;

ownerSafe = Safe(payable(address(proxyFactory.createProxyWithNonce(
address(safeSingleton),
abi.encodeWithSelector(
Safe.setup.selector, owners, 1, address(0), hex"", address(handler), address(0), 0, address(0)
),
0
))));
rootValidation = ValidatorLib.validatorToIdentifier(IValidator(address(erc1271Validator)));
rootValidationConfig =
RootValidationConfig({hook: IHook(address(0)), hookData: hex"", validatorData: abi.encodePacked(ownerSafe)});
}

function _rootSignDigest(bytes32 digest, bool success) internal override returns (bytes memory data) {
if (success) {
bytes memory encoded = handler.encodeMessageDataForSafe(ownerSafe, abi.encode(digest));
bytes32 messageHash = keccak256(encoded);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, messageHash);
return abi.encodePacked(r, s, v);
} else {
digest = keccak256(abi.encodePacked(digest));
bytes memory encoded = handler.encodeMessageDataForSafe(ownerSafe, abi.encode(digest));
bytes32 messageHash = keccak256(encoded);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, messageHash);
return abi.encodePacked(r, s, v);
}
}

function _rootSignUserOp(PackedUserOperation memory op, bytes32 userOpHash, bool success)
internal
override
returns (bytes memory)
{
return _rootSignDigest(userOpHash, success);
}
}