├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── actions └── recovery │ ├── .github │ └── workflows │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ └── RecoveryAction.sol ├── hooks ├── caller │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── src │ │ └── CallerHook.sol │ └── test │ │ ├── CallerHookTest.sol │ │ └── mock │ │ ├── MockAction.sol │ │ └── MockValidator.sol ├── onlyEntrypoint │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ │ └── OnlyEntryPointHook.sol └── spendlingLimits │ ├── .github │ └── workflows │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ └── src │ └── SpendingLimit.sol ├── policies ├── call-policy │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ ├── src │ │ └── CallPolicy.sol │ └── test │ │ └── TestEncode.t.sol ├── gas │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ │ └── GasPolicy.sol ├── ratelimit │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ ├── src │ │ ├── RateLimitPolicy.sol │ │ └── RateLimitPolicyReset.sol │ └── test │ │ └── RateLimitReset.t.sol ├── signature-caller │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ │ └── SignaturePolicy.sol ├── sudo │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ │ └── SudoPolicy.sol └── timestamp │ ├── .github │ └── workflows │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ └── TimestampPolicy.sol ├── signers ├── anysigner │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ │ └── AnySigner.sol ├── ecdsa │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ │ └── ECDSASigner.sol └── webauthn │ ├── .github │ └── workflows │ │ └── test.yml │ ├── .gitignore │ ├── README.md │ ├── foundry.toml │ ├── remappings.txt │ └── src │ ├── Base64URL.sol │ ├── P256.sol │ ├── WebAuthn.sol │ └── WebAuthnSigner.sol └── validators └── webauthn ├── .github └── workflows │ └── test.yml ├── .gitignore ├── README.md ├── foundry.toml ├── remappings.txt ├── src ├── Base64URL.sol ├── P256.sol ├── P256Verifier.sol ├── WebAuthn.sol └── WebAuthnValidator.sol └── test └── WebAuthnValidator.t.sol /.gitignore: -------------------------------------------------------------------------------- 1 | **/log 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "validators/webauthn/lib/forge-std"] 2 | path = validators/webauthn/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "signers/ecdsa/lib/forge-std"] 5 | path = signers/ecdsa/lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | [submodule "validators/webauthn/lib/openzeppelin-contracts"] 8 | path = validators/webauthn/lib/openzeppelin-contracts 9 | url = https://github.com/openzeppelin/openzeppelin-contracts 10 | [submodule "signers/ecdsa/lib/kernel_v3"] 11 | path = signers/ecdsa/lib/kernel_v3 12 | url = https://github.com/zerodevapp/kernel 13 | branch = release/v3.1 14 | [submodule "signers/ecdsa/lib/solady"] 15 | path = signers/ecdsa/lib/solady 16 | url = https://github.com/vectorized/solady 17 | [submodule "policies/signature-caller/lib/forge-std"] 18 | path = policies/signature-caller/lib/forge-std 19 | url = https://github.com/foundry-rs/forge-std 20 | [submodule "policies/signature-caller/lib/kernel_v3"] 21 | path = policies/signature-caller/lib/kernel_v3 22 | url = https://github.com/zerodevapp/kernel 23 | branch = release/v3.1 24 | [submodule "policies/call-policy/lib/forge-std"] 25 | path = policies/call-policy/lib/forge-std 26 | url = https://github.com/foundry-rs/forge-std 27 | [submodule "policies/call-policy/lib/kernel_v3"] 28 | path = policies/call-policy/lib/kernel_v3 29 | url = https://github.com/zerodevapp/kernel 30 | branch = release/v3.1 31 | [submodule "policies/ratelimit/lib/forge-std"] 32 | path = policies/ratelimit/lib/forge-std 33 | url = https://github.com/foundry-rs/forge-std 34 | [submodule "policies/gas/lib/forge-std"] 35 | path = policies/gas/lib/forge-std 36 | url = https://github.com/foundry-rs/forge-std 37 | [submodule "policies/gas/lib/kernel_v3"] 38 | path = policies/gas/lib/kernel_v3 39 | url = https://github.com/zerodevapp/kernel 40 | branch = release/v3.1 41 | [submodule "policies/ratelimit/lib/kernel_v3"] 42 | path = policies/ratelimit/lib/kernel_v3 43 | url = https://github.com/zerodevapp/kernel 44 | branch = release/v3.1 45 | [submodule "policies/timestamp/lib/forge-std"] 46 | path = policies/timestamp/lib/forge-std 47 | url = https://github.com/foundry-rs/forge-std 48 | [submodule "policies/timestamp/lib/kernel_v3"] 49 | path = policies/timestamp/lib/kernel_v3 50 | url = https://github.com/zerodevapp/kernel 51 | branch = release/v3.1 52 | [submodule "signers/webauthn/lib/forge-std"] 53 | path = signers/webauthn/lib/forge-std 54 | url = https://github.com/foundry-rs/forge-std 55 | [submodule "signers/webauthn/lib/openzeppelin-contracts"] 56 | path = signers/webauthn/lib/openzeppelin-contracts 57 | url = https://github.com/openzeppelin/openzeppelin-contracts 58 | [submodule "policies/sudo/lib/forge-std"] 59 | path = policies/sudo/lib/forge-std 60 | url = https://github.com/foundry-rs/forge-std 61 | [submodule "policies/sudo/lib/kernel_v3"] 62 | path = policies/sudo/lib/kernel_v3 63 | url = https://github.com/zerodevapp/kernel 64 | branch = release/v3.1 65 | [submodule "actions/recovery/lib/forge-std"] 66 | path = actions/recovery/lib/forge-std 67 | url = https://github.com/foundry-rs/forge-std 68 | [submodule "actions/recovery/lib/kernel_v3"] 69 | path = actions/recovery/lib/kernel_v3 70 | url = https://github.com/zerodevapp/kernel 71 | branch = release/v3.1 72 | [submodule "hooks/onlyEntrypoint/lib/forge-std"] 73 | path = hooks/onlyEntrypoint/lib/forge-std 74 | url = https://github.com/foundry-rs/forge-std 75 | [submodule "hooks/onlyEntrypoint/lib/kernel_v3"] 76 | path = hooks/onlyEntrypoint/lib/kernel_v3 77 | url = https://github.com/zerodevapp/kernel 78 | branch = release/v3.1 79 | [submodule "signers/webauthn/lib/kernel"] 80 | path = signers/webauthn/lib/kernel 81 | url = https://github.com/zerodevapp/kernel 82 | branch = release/v3.1 83 | [submodule "hooks/spendlingLimits/lib/forge-std"] 84 | path = hooks/spendlingLimits/lib/forge-std 85 | url = https://github.com/foundry-rs/forge-std 86 | [submodule "hooks/spendlingLimits/lib/kernel"] 87 | path = hooks/spendlingLimits/lib/kernel 88 | url = https://github.com/zerodevapp/kernel 89 | branch = release/v3.1 90 | [submodule "hooks/spendlingLimits/lib/solady"] 91 | path = hooks/spendlingLimits/lib/solady 92 | url = https://github.com/vectorized/solady 93 | [submodule "hooks/caller/lib/forge-std"] 94 | path = hooks/caller/lib/forge-std 95 | url = https://github.com/foundry-rs/forge-std 96 | [submodule "hooks/caller/lib/kernel"] 97 | path = hooks/caller/lib/kernel 98 | url = https://github.com/zerodevapp/kernel 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ZeroDev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kernel 7579 plugins 2 | 3 | ## Addresses 4 | 5 | - ECDSA Signer : 0x6A6F069E2a08c2468e7724Ab3250CdBFBA14D4FF 6 | - Webauthn signer : 0x8AA55d4BfAE101609078681A69B5bc3181516612 7 | - Call policy : 0x9a52283276a0ec8740df50bf01b28a80d880eaf2 8 | - Call policy (Deprecated) : 0xe4Fec84B7B002273ecC86baa65a831ddB92d30a8 9 | - Gas policy : 0xaeFC5AbC67FfD258abD0A3E54f65E70326F84b23 10 | - RateLimit policy : 0xf63d4139B25c836334edD76641356c6b74C86873 11 | - Signature policy : 0xF6A936c88D97E6fad13b98d2FD731Ff17eeD591d 12 | - Sudo policy : 0x67b436caD8a6D025DF6C82C5BB43fbF11fC5B9B7 13 | - Timestamp policy : 0xB9f8f524bE6EcD8C945b1b87f9ae5C192FdCE20F 14 | - Webauthn Validator : 0xD990393C670dCcE8b4d8F858FB98c9912dBFAa06 15 | - Recovery Action : 0xe884C2868CC82c16177eC73a93f7D9E6F3A5DC6E 16 | - Only EntryPoint Hook : 0xb230f0A1C7C95fa11001647383c8C7a8F316b900 17 | -------------------------------------------------------------------------------- /actions/recovery/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /actions/recovery/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /actions/recovery/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /actions/recovery/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /actions/recovery/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | solady/=lib/kernel_v3/lib/solady/src/ 5 | -------------------------------------------------------------------------------- /actions/recovery/src/RecoveryAction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {IValidator} from "kernel/interfaces/IERC7579Modules.sol"; 4 | 5 | contract RecoveryAction { 6 | function doRecovery(address _validator, bytes calldata _data) external { 7 | IValidator(_validator).onUninstall(hex""); 8 | IValidator(_validator).onInstall(_data); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /hooks/caller/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | 26 | - name: Show Forge version 27 | run: | 28 | forge --version 29 | 30 | - name: Run Forge fmt 31 | run: | 32 | forge fmt --check 33 | id: fmt 34 | 35 | - name: Run Forge build 36 | run: | 37 | forge build --sizes 38 | id: build 39 | 40 | - name: Run Forge tests 41 | run: | 42 | forge test -vvv 43 | id: test 44 | -------------------------------------------------------------------------------- /hooks/caller/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /hooks/caller/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /hooks/caller/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /hooks/caller/src/CallerHook.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import {IHook} from "kernel/src/interfaces/IERC7579Modules.sol"; 4 | import {MODULE_TYPE_HOOK} from "kernel/src/types/Constants.sol"; 5 | 6 | contract CallerHook is IHook { 7 | mapping(address => bool) public installed; 8 | mapping(address caller => mapping(address account => bool allowed)) public allowed; 9 | 10 | function onInstall(bytes calldata data) external payable { 11 | installed[msg.sender] = true; 12 | address[] memory accounts = abi.decode(data, (address[])); 13 | for (uint256 i = 0; i < accounts.length; i++) { 14 | allowed[accounts[i]][msg.sender] = true; 15 | } 16 | } 17 | 18 | function onUninstall(bytes calldata) external payable { 19 | installed[msg.sender] = false; 20 | } 21 | 22 | function isModuleType(uint256 typeID) external pure override returns (bool) { 23 | return typeID == MODULE_TYPE_HOOK; 24 | } 25 | 26 | function isInitialized(address smartAccount) external view override returns (bool) { 27 | return installed[smartAccount]; 28 | } 29 | 30 | function preCheck(address msgSender, uint256, bytes calldata) external payable override returns (bytes memory) { 31 | require(allowed[msgSender][msg.sender], "not allowed"); 32 | return hex""; 33 | } 34 | 35 | function postCheck(bytes calldata) external payable override {} 36 | } 37 | -------------------------------------------------------------------------------- /hooks/caller/test/CallerHookTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import {CallerHook} from "../src/CallerHook.sol"; 4 | import {Test} from "forge-std/Test.sol"; 5 | import {MockAction} from "./mock/MockAction.sol"; 6 | import {Kernel} from "kernel/src/Kernel.sol"; 7 | import {IEntryPoint} from "kernel/src/interfaces/IEntryPoint.sol"; 8 | import {MockValidator} from "./mock/MockValidator.sol"; 9 | import {ValidatorLib, ValidationId} from "kernel/src/utils/ValidationTypeLib.sol"; 10 | import {KernelFactory} from "kernel/src/factory/KernelFactory.sol"; 11 | address constant ENTRYPOINT_0_7_ADDR = 0x0000000071727De22E5E9d8BAf0edAc6f37da032; 12 | contract CallerHookTest is Test { 13 | CallerHook public callerHook; 14 | address public account1 = makeAddr("account1"); 15 | address public account2 = makeAddr("account2"); 16 | MockAction public mockAction; 17 | address[] public accounts = [account1, account2]; 18 | MockValidator public mockValidator; 19 | ValidationId rootValidation; 20 | bytes[] public initConfig; 21 | function setUp() public { 22 | callerHook = new CallerHook(); 23 | mockAction = new MockAction(); 24 | mockValidator = new MockValidator(); 25 | rootValidation = ValidatorLib.validatorToIdentifier(mockValidator); 26 | initConfig = new bytes[](0); 27 | } 28 | 29 | function test_install() public { 30 | callerHook.onInstall(abi.encode(accounts)); 31 | assertTrue(callerHook.installed(address(this))); 32 | assertTrue(callerHook.allowed(account1, address(this))); 33 | assertTrue(callerHook.allowed(account2, address(this))); 34 | } 35 | 36 | function test_hook() public { 37 | address template = address(new Kernel(IEntryPoint(ENTRYPOINT_0_7_ADDR))); 38 | KernelFactory factory = new KernelFactory(address(template)); 39 | Kernel kernel = Kernel(payable(factory.createAccount(initData(), bytes32(0)))); 40 | address(kernel).call(abi.encodeWithSelector(Kernel.installModule.selector, 3, address(mockAction), abi.encodePacked( 41 | MockAction.doSomething.selector, 42 | address(callerHook), 43 | abi.encode(hex"ff", abi.encodePacked(bytes1(0xff), abi.encode(accounts))) 44 | ) 45 | )); 46 | 47 | vm.prank(account1); 48 | MockAction(address(kernel)).doSomething(); 49 | } 50 | 51 | function initData() internal view returns (bytes memory) { 52 | return abi.encodeWithSelector( 53 | Kernel.initialize.selector, 54 | rootValidation, 55 | address(0), 56 | hex"", 57 | hex"", 58 | initConfig 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /hooks/caller/test/mock/MockAction.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | contract MockAction { 4 | event Log(address indexed sender); 5 | 6 | function doSomething() external { 7 | // do something 8 | emit Log(msg.sender); 9 | } 10 | } -------------------------------------------------------------------------------- /hooks/caller/test/mock/MockValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "kernel/src/interfaces/IERC7579Modules.sol"; 6 | 7 | contract MockValidator is IValidator, IHook { 8 | mapping(address => bool) public initialized; 9 | bool public success; 10 | uint256 public count; 11 | 12 | mapping(address => bytes) public validatorData; 13 | mapping(bytes32 => bool) public validSig; 14 | 15 | bool public isHook; 16 | 17 | function setHook(bool _isHook) external { 18 | isHook = _isHook; 19 | } 20 | 21 | function sudoSetSuccess(bool _success) external { 22 | success = _success; 23 | } 24 | 25 | function sudoSetValidSig(bytes calldata sig) external { 26 | validSig[keccak256(sig)] = true; 27 | } 28 | 29 | function onInstall(bytes calldata data) external payable { 30 | initialized[msg.sender] = true; 31 | validatorData[msg.sender] = data; 32 | } 33 | 34 | function onUninstall(bytes calldata data) external payable { 35 | initialized[msg.sender] = false; 36 | validatorData[msg.sender] = data; 37 | } 38 | 39 | function isModuleType(uint256 typeID) external pure returns (bool) { 40 | return typeID == 1 || typeID == 4; 41 | } 42 | 43 | /** 44 | * @dev Returns if the module was already initialized for a provided smartaccount 45 | */ 46 | function isInitialized(address smartAccount) external view returns (bool) { 47 | return initialized[smartAccount]; 48 | } 49 | 50 | function validateUserOp(PackedUserOperation calldata, bytes32) external payable returns (uint256) { 51 | count++; 52 | 53 | if (success) { 54 | return 0; 55 | } else { 56 | return 1; 57 | } 58 | } 59 | 60 | function isValidSignatureWithSender(address, bytes32, bytes calldata sig) external view returns (bytes4) { 61 | if (validSig[keccak256(sig)] == true) { 62 | return 0x1626ba7e; 63 | } else { 64 | return 0xffffffff; 65 | } 66 | } 67 | 68 | function preCheck(address msgSender, uint256 value, bytes calldata msgData) 69 | external 70 | payable 71 | returns (bytes memory hookData) 72 | { 73 | return hex""; 74 | } 75 | 76 | function postCheck(bytes calldata hookData) external payable { 77 | return; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /hooks/onlyEntrypoint/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /hooks/onlyEntrypoint/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /hooks/onlyEntrypoint/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /hooks/onlyEntrypoint/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /hooks/onlyEntrypoint/remappings.txt: -------------------------------------------------------------------------------- 1 | ExcessivelySafeCall/=lib/kernel_v3/lib/ExcessivelySafeCall/src/ 2 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 3 | forge-std/=lib/forge-std/src/ 4 | kernel/=lib/kernel_v3/ 5 | solady/=lib/kernel_v3/lib/solady/src/ 6 | -------------------------------------------------------------------------------- /hooks/onlyEntrypoint/src/OnlyEntryPointHook.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {IHook} from "kernel/src/interfaces/IERC7579Modules.sol"; 4 | import {MODULE_TYPE_HOOK} from "kernel/src/types/Constants.sol"; 5 | 6 | address constant ENTRYPOINT_0_7 = 0x0000000071727De22E5E9d8BAf0edAc6f37da032; 7 | 8 | contract OnlyEntryPointHook is IHook { 9 | mapping(address => bool) public installed; 10 | 11 | function onInstall(bytes calldata) external payable { 12 | installed[msg.sender] = true; 13 | } 14 | 15 | function onUninstall(bytes calldata) external payable { 16 | installed[msg.sender] = false; 17 | } 18 | 19 | function isModuleType(uint256 typeID) external pure override returns (bool) { 20 | return typeID == MODULE_TYPE_HOOK; 21 | } 22 | 23 | function isInitialized(address smartAccount) external view override returns (bool) { 24 | return installed[smartAccount]; 25 | } 26 | 27 | function preCheck(address msgSender, uint256, bytes calldata) external payable override returns (bytes memory) { 28 | require(msgSender == ENTRYPOINT_0_7, "only entrypoint"); 29 | } 30 | 31 | function postCheck(bytes calldata, bool, bytes calldata) external payable override {} 32 | } 33 | -------------------------------------------------------------------------------- /hooks/spendlingLimits/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /hooks/spendlingLimits/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /hooks/spendlingLimits/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /hooks/spendlingLimits/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /hooks/spendlingLimits/src/SpendingLimit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {IHook} from "kernel/src/interfaces/IERC7579Modules.sol"; 4 | import {MODULE_TYPE_HOOK} from "kernel/src/types/Constants.sol"; 5 | import {ERC20} from "solady/tokens/ERC20.sol"; 6 | 7 | address constant ENTRYPOINT_0_7 = 0x0000000071727De22E5E9d8BAf0edAc6f37da032; 8 | 9 | struct SpendingLimitData { 10 | address token; 11 | uint256 allowance; 12 | } 13 | 14 | contract SpendingLimit is IHook { 15 | mapping(address account => uint256) public listLength; 16 | mapping(uint256 idx => mapping(address => SpendingLimitData)) public spendingLimit; 17 | 18 | error ExceedsAllowance(); 19 | 20 | function onInstall(bytes calldata data) external payable { 21 | require(listLength[msg.sender] == 0, "already initialized"); 22 | bytes[] calldata arr = _parseCalldataArrayBytes(data); 23 | for (uint256 i = 0; i < arr.length; i++) { 24 | spendingLimit[i][msg.sender] = 25 | SpendingLimitData({token: address(bytes20(arr[i][0:20])), allowance: uint256(bytes32(arr[i][20:52]))}); 26 | } 27 | listLength[msg.sender] = arr.length; 28 | } 29 | 30 | function onUninstall(bytes calldata) external payable { 31 | require(listLength[msg.sender] > 0, "not initialized"); 32 | uint256 length = listLength[msg.sender]; 33 | for (uint256 i = 0; i < length; i++) { 34 | delete spendingLimit[i][msg.sender]; 35 | } 36 | delete listLength[msg.sender]; 37 | } 38 | 39 | function isModuleType(uint256 typeID) external pure override returns (bool) { 40 | return typeID == MODULE_TYPE_HOOK; 41 | } 42 | 43 | function isInitialized(address smartAccount) external view override returns (bool) { 44 | return listLength[smartAccount] != 0; 45 | } 46 | 47 | function preCheck(address, uint256, bytes calldata) external payable override returns (bytes memory) { 48 | uint256 length = listLength[msg.sender]; 49 | uint256[] memory balances = new uint256[](length); 50 | for (uint256 i = 0; i < length; i++) { 51 | SpendingLimitData memory data = spendingLimit[i][msg.sender]; 52 | if (data.token == address(0)) { 53 | balances[i] = msg.sender.balance; 54 | } else { 55 | balances[i] = ERC20(data.token).balanceOf(msg.sender); 56 | } 57 | } 58 | return abi.encode(balances); 59 | } 60 | 61 | function postCheck(bytes calldata context, bool, bytes calldata) external payable override { 62 | uint256 length = listLength[msg.sender]; 63 | uint256[] memory preBalance = abi.decode(context, (uint256[])); 64 | for (uint256 i = 0; i < length; i++) { 65 | SpendingLimitData storage data = spendingLimit[i][msg.sender]; 66 | uint256 balance; 67 | if (data.token == address(0)) { 68 | balance = msg.sender.balance; 69 | } else { 70 | balance = ERC20(data.token).balanceOf(msg.sender); 71 | } 72 | // if balance increased, skip the allowance check 73 | if (balance > preBalance[i]) { 74 | continue; 75 | } 76 | uint256 used = preBalance[i] - balance; 77 | if (data.allowance < used) { 78 | revert ExceedsAllowance(); 79 | } 80 | data.allowance -= used; 81 | } 82 | } 83 | 84 | function _parseCalldataArrayBytes(bytes calldata _data) internal pure returns (bytes[] calldata arr) { 85 | assembly { 86 | arr.offset := add(add(_data.offset, 0x20), calldataload(_data.offset)) 87 | arr.length := calldataload(sub(arr.offset, 0x20)) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /policies/call-policy/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /policies/call-policy/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /policies/call-policy/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /policies/call-policy/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /policies/call-policy/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | solady/=lib/kernel_v3/lib/solady/src/ 5 | -------------------------------------------------------------------------------- /policies/call-policy/src/CallPolicy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "kernel/sdk/moduleBase/PolicyBase.sol"; 4 | import "kernel/utils/ExecLib.sol"; 5 | import {IERC7579Account} from "kernel/interfaces/IERC7579Account.sol"; 6 | 7 | struct Permission { 8 | CallType callType; // calltype can be CALLTYPE_SINGLE/CALLTYPE_DELEGATECALL 9 | address target; 10 | bytes4 selector; 11 | uint256 valueLimit; 12 | ParamRule[] rules; 13 | } 14 | 15 | struct ParamRule { 16 | ParamCondition condition; 17 | uint64 offset; 18 | bytes32[] params; 19 | } 20 | 21 | enum ParamCondition { 22 | EQUAL, 23 | GREATER_THAN, 24 | LESS_THAN, 25 | GREATER_THAN_OR_EQUAL, 26 | LESS_THAN_OR_EQUAL, 27 | NOT_EQUAL, 28 | ONE_OF 29 | } 30 | 31 | enum Status { 32 | NA, 33 | Live, 34 | Deprecated 35 | } 36 | 37 | contract CallPolicy is PolicyBase { 38 | error InvalidCallType(); 39 | error InvalidCallData(); 40 | error CallViolatesParamRule(); 41 | error CallViolatesValueRule(); 42 | 43 | mapping(address => uint256) public usedIds; 44 | mapping(bytes32 id => mapping(address => Status)) public status; 45 | //mapping(bytes32 id => mapping(bytes32 permissionHash => mapping(address => bytes))) public encodedPermissions; 46 | 47 | function isInitialized(address wallet) external view override returns (bool) { 48 | return usedIds[wallet] > 0; 49 | } 50 | 51 | function setPermission(bytes32 _id, bytes32 _permissionHash, address _owner, bytes memory _permission) internal { 52 | bytes32 slot = keccak256(bytes.concat(bytes32(uint256(uint160(_owner))), keccak256(bytes.concat(_id,_permissionHash)))); 53 | 54 | uint256 length = _permission.length; 55 | assembly { 56 | // store length on first slot, as usual 57 | sstore(slot, length) 58 | for { let cursor := 0x20 } lt(cursor, add(length, 0x20)) { cursor := add(cursor, 0x20) } { 59 | sstore(add(slot, div(cursor, 0x20)), mload(add(_permission, cursor))) // slot + cursor/32 60 | } 61 | } 62 | } 63 | 64 | function encodedPermissions(bytes32 _id, bytes32 _permissionHash, address _owner) public view returns(bytes memory encoded) { 65 | bytes32 slot = keccak256(bytes.concat(bytes32(uint256(uint160(_owner))), keccak256(bytes.concat(_id,_permissionHash)))); 66 | uint256 length; 67 | assembly { 68 | encoded := mload(0x40) 69 | length := sload(slot) 70 | mstore(encoded, length) 71 | for { let cursor := 0x20 } lt(cursor, add(length, 0x20)) { cursor := add(cursor, 0x20) } { 72 | mstore(add(encoded, cursor), sload(add(slot, div(cursor, 0x20)))) 73 | } 74 | mstore(0x40, add(encoded, add(length, 0x20))) 75 | } 76 | } 77 | 78 | function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp) 79 | external 80 | payable 81 | override 82 | returns (uint256) 83 | { 84 | require(bytes4(userOp.callData[0:4]) == IERC7579Account.execute.selector); 85 | ExecMode mode = ExecMode.wrap(bytes32(userOp.callData[4:36])); 86 | (CallType callType, ExecType execType,,) = ExecLib.decode(mode); 87 | bytes calldata executionCallData = userOp.callData; // Cache calldata here 88 | assembly { 89 | executionCallData.offset := 90 | add(add(executionCallData.offset, 0x24), calldataload(add(executionCallData.offset, 0x24))) 91 | executionCallData.length := calldataload(sub(executionCallData.offset, 0x20)) 92 | } 93 | if (callType == CALLTYPE_SINGLE) { 94 | (address target, uint256 value, bytes calldata callData) = ExecLib.decodeSingle(executionCallData); 95 | bool permissionPass = _checkPermission(msg.sender, id, CALLTYPE_SINGLE, target, callData, value); 96 | if (!permissionPass) { 97 | revert CallViolatesParamRule(); 98 | } 99 | } else if (callType == CALLTYPE_BATCH) { 100 | Execution[] calldata exec = ExecLib.decodeBatch(executionCallData); 101 | for (uint256 i = 0; i < exec.length; i++) { 102 | bool permissionPass = 103 | _checkPermission(msg.sender, id, CALLTYPE_SINGLE, exec[i].target, exec[i].callData, exec[i].value); 104 | if (!permissionPass) { 105 | revert CallViolatesParamRule(); 106 | } 107 | } 108 | } else if (callType == CALLTYPE_DELEGATECALL) { 109 | address target = address(bytes20(executionCallData[0:20])); 110 | bytes calldata callData = executionCallData[20:]; 111 | bool permissionPass = _checkPermission(msg.sender, id, CALLTYPE_DELEGATECALL, target, callData, 0); 112 | if (!permissionPass) { 113 | revert CallViolatesParamRule(); 114 | } 115 | } else { 116 | revert InvalidCallType(); 117 | } 118 | } 119 | 120 | function _checkPermission( 121 | address wallet, 122 | bytes32 id, 123 | CallType callType, 124 | address target, 125 | bytes calldata data, 126 | uint256 value 127 | ) internal returns (bool) { 128 | bytes4 _data = data.length == 0 ? bytes4(0x0) : bytes4(data[0:4]); 129 | bytes32 permissionHash = keccak256(abi.encodePacked(callType, target, _data)); 130 | bytes memory encodedPermission = encodedPermissions(id, permissionHash, wallet); 131 | 132 | // try to find the permission with zero address which means ANY target address 133 | // e.g. allow to call `approve` function of `ANY` ERC20 token contracts 134 | if (encodedPermission.length == 0) { 135 | bytes32 permissionHashWithZeroAddress = keccak256(abi.encodePacked(callType, address(0), _data)); 136 | encodedPermission = encodedPermissions(id,permissionHashWithZeroAddress,wallet); 137 | } 138 | 139 | // if still no permission found, then the call is not allowed 140 | if (encodedPermission.length == 0) { 141 | revert InvalidCallData(); 142 | } 143 | 144 | (uint256 allowedValue, ParamRule[] memory rules) = abi.decode(encodedPermission, (uint256, ParamRule[])); 145 | 146 | if (value > allowedValue) { 147 | revert CallViolatesValueRule(); 148 | } 149 | for (uint256 i = 0; i < rules.length; i++) { 150 | ParamRule memory rule = rules[i]; 151 | bytes32 param = bytes32(data[4 + rule.offset:4 + rule.offset + 32]); 152 | // only ONE_OF condition can have multiple params 153 | if (rule.condition == ParamCondition.EQUAL && param != rule.params[0]) { 154 | return false; 155 | } else if (rule.condition == ParamCondition.GREATER_THAN && param <= rule.params[0]) { 156 | return false; 157 | } else if (rule.condition == ParamCondition.LESS_THAN && param >= rule.params[0]) { 158 | return false; 159 | } else if (rule.condition == ParamCondition.GREATER_THAN_OR_EQUAL && param < rule.params[0]) { 160 | return false; 161 | } else if (rule.condition == ParamCondition.LESS_THAN_OR_EQUAL && param > rule.params[0]) { 162 | return false; 163 | } else if (rule.condition == ParamCondition.NOT_EQUAL && param == rule.params[0]) { 164 | return false; 165 | } else if (rule.condition == ParamCondition.ONE_OF) { 166 | bool oneOfStatus = false; 167 | for (uint256 j = 0; j < rule.params.length; j++) { 168 | if (param == rule.params[j]) { 169 | oneOfStatus = true; 170 | break; 171 | } 172 | } 173 | if (!oneOfStatus) { 174 | return false; 175 | } 176 | } 177 | } 178 | return true; 179 | } 180 | 181 | function checkSignaturePolicy(bytes32 id, address sender, bytes32 hash, bytes calldata sig) 182 | external 183 | view 184 | override 185 | returns (uint256) 186 | { 187 | require(status[id][msg.sender] == Status.Live); 188 | return 0; 189 | } 190 | 191 | function _parsePermission(bytes calldata _sig) internal pure returns (Permission[] calldata permissions) { 192 | assembly { 193 | permissions.offset := add(add(_sig.offset, 32), calldataload(_sig.offset)) 194 | permissions.length := calldataload(sub(permissions.offset, 32)) 195 | } 196 | } 197 | 198 | function _policyOninstall(bytes32 id, bytes calldata _data) internal override { 199 | require(status[id][msg.sender] == Status.NA); 200 | Permission[] calldata permissions = _parsePermission(_data); 201 | for (uint256 i = 0; i < permissions.length; i++) { 202 | // check if the permissionHash is unique 203 | bytes32 permissionHash = 204 | keccak256(abi.encodePacked(permissions[i].callType, permissions[i].target, permissions[i].selector)); 205 | require(encodedPermissions(id, permissionHash, msg.sender).length == 0, "duplicate permissionHash"); 206 | 207 | // check if the params length is correct 208 | for (uint256 j = 0; j < permissions[i].rules.length; j++) { 209 | if (permissions[i].rules[j].condition != ParamCondition.ONE_OF) { 210 | require(permissions[i].rules[j].params.length == 1, "only OneOf condition can have multiple params"); 211 | } 212 | } 213 | 214 | setPermission( 215 | id,permissionHash,msg.sender, 216 | abi.encode(permissions[i].valueLimit, permissions[i].rules) 217 | ); 218 | } 219 | status[id][msg.sender] = Status.Live; 220 | usedIds[msg.sender]++; 221 | } 222 | 223 | function _policyOnUninstall(bytes32 id, bytes calldata _data) internal override { 224 | require(status[id][msg.sender] == Status.Live); 225 | //delete encodedPermissions[id][msg.sender]; 226 | status[id][msg.sender] = Status.Deprecated; 227 | usedIds[msg.sender]--; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /policies/call-policy/test/TestEncode.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {Test} from "forge-std/Test.sol"; 4 | import "forge-std/console.sol"; 5 | 6 | import "src/CallPolicy.sol"; 7 | 8 | contract MockCallPolicy is CallPolicy { 9 | 10 | function sudoSetPolicy(bytes32 _id, bytes32 _permissionHash, address _owner, bytes memory _permission) external { 11 | setPermission(_id, _permissionHash, _owner, _permission); 12 | } 13 | } 14 | 15 | contract TestEncoding is Test { 16 | 17 | MockCallPolicy mock; 18 | 19 | function setUp() external { 20 | mock = new MockCallPolicy(); 21 | } 22 | 23 | function testEncoding(bytes memory tb) external { 24 | address owner = makeAddr("Owner"); 25 | 26 | mock.sudoSetPolicy(bytes32(uint256(0x12345678)), bytes32(uint256(0x987654321)), owner, tb); 27 | 28 | bytes memory result = mock.encodedPermissions(bytes32(uint256(0x12345678)), bytes32(uint256(0x987654321)), owner); 29 | 30 | assertEq(keccak256(result), keccak256(tb)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /policies/gas/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /policies/gas/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /policies/gas/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /policies/gas/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /policies/gas/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | solady/=lib/kernel_v3/lib/solady/ 5 | -------------------------------------------------------------------------------- /policies/gas/src/GasPolicy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "kernel/sdk/moduleBase/PolicyBase.sol"; 4 | 5 | enum Status { 6 | NA, 7 | Live, 8 | Deprecated 9 | } 10 | 11 | struct GasPolicyConfig { 12 | uint128 allowed; 13 | bool enforcePaymaster; 14 | address allowedPaymaster; 15 | } 16 | 17 | contract GasPolicy is PolicyBase { 18 | mapping(address => uint256) public usedIds; 19 | mapping(bytes32 id => mapping(address => Status)) public status; 20 | mapping(bytes32 id => mapping(address => GasPolicyConfig)) public gasPolicyConfig; 21 | 22 | function isInitialized(address wallet) external view override returns (bool) { 23 | return usedIds[wallet] > 0; 24 | } 25 | 26 | function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp) 27 | external 28 | payable 29 | override 30 | returns (uint256) 31 | { 32 | require(status[id][msg.sender] == Status.Live); 33 | (uint256 verificationGasLimit, uint256 callGasLimit) = 34 | (uint128(bytes16(userOp.accountGasLimits)), uint128(uint256(userOp.accountGasLimits))); 35 | uint256 maxFeePerGas = uint128(uint256(userOp.gasFees)); 36 | uint128 maxAmount = uint128((userOp.preVerificationGas + verificationGasLimit + callGasLimit) * maxFeePerGas); 37 | if (gasPolicyConfig[id][msg.sender].enforcePaymaster) { 38 | if ( 39 | gasPolicyConfig[id][msg.sender].allowedPaymaster != address(0) 40 | && address(bytes20(userOp.paymasterAndData[0:20])) != gasPolicyConfig[id][msg.sender].allowedPaymaster 41 | ) { 42 | return 1; 43 | } 44 | } 45 | if (maxAmount > gasPolicyConfig[id][msg.sender].allowed) { 46 | return 1; 47 | } 48 | gasPolicyConfig[id][msg.sender].allowed -= maxAmount; 49 | return 0; 50 | } 51 | 52 | function checkSignaturePolicy(bytes32 id, address sender, bytes32 hash, bytes calldata sig) 53 | external 54 | view 55 | override 56 | returns (uint256) 57 | { 58 | require(status[id][msg.sender] == Status.Live); 59 | return 0; 60 | } 61 | 62 | function _policyOninstall(bytes32 id, bytes calldata _data) internal override { 63 | require(status[id][msg.sender] == Status.NA); 64 | (uint128 allowed, bool enforcePaymaster, address allowedPaymaster) = abi.decode(_data, (uint128, bool, address)); 65 | gasPolicyConfig[id][msg.sender] = GasPolicyConfig(allowed, enforcePaymaster, allowedPaymaster); 66 | status[id][msg.sender] = Status.Live; 67 | usedIds[msg.sender]++; 68 | } 69 | 70 | function _policyOnUninstall(bytes32 id, bytes calldata _data) internal override { 71 | require(status[id][msg.sender] == Status.Live); 72 | status[id][msg.sender] = Status.Deprecated; 73 | usedIds[msg.sender]--; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /policies/ratelimit/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /policies/ratelimit/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /policies/ratelimit/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /policies/ratelimit/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /policies/ratelimit/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | solady/=lib/kernel_v3/lib/solady/ 5 | -------------------------------------------------------------------------------- /policies/ratelimit/src/RateLimitPolicy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "kernel/sdk/moduleBase/PolicyBase.sol"; 4 | import {packValidationData, ValidAfter, ValidUntil} from "kernel/types/Types.sol"; 5 | 6 | enum Status { 7 | NA, 8 | Live, 9 | Deprecated 10 | } 11 | 12 | struct RateLimitConfig { 13 | uint48 interval; 14 | uint48 count; 15 | ValidAfter startAt; 16 | } 17 | 18 | contract RateLimitPolicy is PolicyBase { 19 | mapping(address => uint256) public usedIds; 20 | mapping(bytes32 id => mapping(address => Status)) public status; 21 | mapping(bytes32 id => mapping(address kernel => RateLimitConfig)) public rateLimitConfigs; 22 | 23 | function isInitialized(address wallet) external view override returns (bool) { 24 | return usedIds[wallet] > 0; 25 | } 26 | 27 | function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp) 28 | external 29 | payable 30 | override 31 | returns (uint256) 32 | { 33 | require(status[id][msg.sender] == Status.Live); 34 | RateLimitConfig memory config = rateLimitConfigs[id][msg.sender]; 35 | if (config.count == 0) { 36 | return 1; 37 | } 38 | rateLimitConfigs[id][msg.sender].count = config.count - 1; 39 | rateLimitConfigs[id][msg.sender].startAt = ValidAfter.wrap(ValidAfter.unwrap(config.startAt) + config.interval); 40 | return packValidationData(config.startAt, ValidUntil.wrap(0)); 41 | } 42 | 43 | function checkSignaturePolicy(bytes32 id, address sender, bytes32 hash, bytes calldata sig) 44 | external 45 | view 46 | override 47 | returns (uint256) 48 | { 49 | require(status[id][msg.sender] == Status.Live); 50 | return 0; 51 | } 52 | 53 | function _policyOninstall(bytes32 id, bytes calldata _data) internal override { 54 | require(status[id][msg.sender] == Status.NA); 55 | uint48 delay = uint48(bytes6(_data[0:6])); 56 | uint48 count = uint48(bytes6(_data[6:12])); 57 | uint48 startAt = uint48(bytes6(_data[12:18])); 58 | rateLimitConfigs[id][msg.sender] = RateLimitConfig(delay, count, ValidAfter.wrap(startAt)); 59 | status[id][msg.sender] = Status.Live; 60 | usedIds[msg.sender]++; 61 | } 62 | 63 | function _policyOnUninstall(bytes32 id, bytes calldata _data) internal override { 64 | require(status[id][msg.sender] == Status.Live); 65 | status[id][msg.sender] = Status.Deprecated; 66 | usedIds[msg.sender]--; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /policies/ratelimit/src/RateLimitPolicyReset.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "kernel/sdk/moduleBase/PolicyBase.sol"; 4 | import {packValidationData, ValidAfter, ValidUntil} from "kernel/types/Types.sol"; 5 | 6 | enum Status { 7 | NA, 8 | Live, 9 | Deprecated 10 | } 11 | 12 | /// @notice Configuration for the naive rate limiter. 13 | struct RateLimitConfig { 14 | uint48 interval; // Time window in seconds. 15 | uint48 initialCount; // Maximum number of operations allowed in each window. 16 | } 17 | 18 | /// @notice The current state for the naive rate limiter. 19 | struct NaiveRateLimitState { 20 | uint48 storedCount; // Remaining allowed operations in the current window. 21 | uint48 resetDate; // Timestamp at which the current window ends. 22 | } 23 | 24 | import "forge-std/console.sol"; 25 | 26 | contract RateLimitPolicyReset is PolicyBase { 27 | error RateLimited(); 28 | 29 | mapping(address => uint256) public usedIds; 30 | mapping(bytes32 id => mapping(address => Status)) public status; 31 | // Maps each policy id and wallet to its rate limiting configuration. 32 | mapping(bytes32 => mapping(address => RateLimitConfig)) public rateLimitConfigs; 33 | // Maps each policy id and wallet to its current rate limiting state. 34 | mapping(bytes32 => mapping(address => NaiveRateLimitState)) public rateLimitState; 35 | 36 | /// @notice Installs the policy with encoded configuration data. 37 | /// @dev Expects `_data` to be at least 12 bytes: 38 | /// - first 6 bytes: uint48 interval, 39 | /// - next 6 bytes: uint48 initialCount. 40 | function _policyOninstall(bytes32 id, bytes calldata _data) internal override { 41 | console.logBytes(_data); 42 | require(status[id][msg.sender] != Status.Live); 43 | require(_data.length >= 12, "Invalid data length"); 44 | uint48 interval = uint48(bytes6(_data[0:6])); 45 | uint48 initialCount = uint48(bytes6(_data[6:12])); 46 | rateLimitConfigs[id][msg.sender] = RateLimitConfig(interval, initialCount); 47 | 48 | // Initialize the state: set storedCount to initialCount and resetDate to now + interval. 49 | rateLimitState[id][msg.sender] = 50 | NaiveRateLimitState({storedCount: initialCount, resetDate: uint48(block.timestamp) + interval}); 51 | 52 | status[id][msg.sender] = Status.Live; 53 | usedIds[msg.sender]++; 54 | } 55 | 56 | function _policyOnUninstall(bytes32 id, bytes calldata _data) internal override { 57 | require(status[id][msg.sender] == Status.Live); 58 | status[id][msg.sender] = Status.Deprecated; 59 | usedIds[msg.sender]--; 60 | } 61 | 62 | /// @notice Checks the policy for a user operation. 63 | /// If the current time has passed the resetDate, it resets storedCount and resetDate. 64 | /// Then it decrements storedCount if there is quota remaining. 65 | function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata /*userOp*/ ) 66 | external 67 | payable 68 | override 69 | returns (uint256) 70 | { 71 | RateLimitConfig storage config = rateLimitConfigs[id][msg.sender]; 72 | NaiveRateLimitState storage state = rateLimitState[id][msg.sender]; 73 | uint48 currentTime = uint48(block.timestamp); 74 | 75 | // Reset the counter if the current time has passed the window's resetDate. 76 | if (currentTime >= state.resetDate) { 77 | state.storedCount = config.initialCount; 78 | state.resetDate = currentTime + config.interval; 79 | } 80 | 81 | require(state.storedCount > 0, RateLimited()); 82 | // Decrement the allowed count. 83 | state.storedCount--; 84 | 85 | // Return validation data: current time and next reset date. 86 | return packValidationData(ValidAfter.wrap(currentTime), ValidUntil.wrap(state.resetDate)); 87 | } 88 | 89 | /// @notice No signature validation is required for this policy. 90 | function checkSignaturePolicy(bytes32 id, address sender, bytes32 hash, bytes calldata sig) 91 | external 92 | view 93 | override 94 | returns (uint256) 95 | { 96 | return 0; 97 | } 98 | 99 | function isInitialized(address wallet) external view override returns (bool) { 100 | return rateLimitConfigs[keccak256(abi.encode(wallet))][wallet].initialCount > 0; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /policies/ratelimit/test/RateLimitReset.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "src/RateLimitPolicyReset.sol"; 4 | 5 | import "forge-std/Test.sol"; 6 | 7 | contract RateLimitPolicyResetTest is Test { 8 | RateLimitPolicyReset policy; 9 | 10 | address caller; 11 | 12 | function setUp() external { 13 | policy = new RateLimitPolicyReset(); 14 | caller = makeAddr("Caller"); 15 | } 16 | 17 | function testOnInstall(bytes32 id, uint48 interval, uint48 allowedCallCount) external { 18 | vm.assume(block.timestamp + uint256(interval) < type(uint48).max); 19 | vm.startPrank(caller); 20 | policy.onInstall(abi.encodePacked(id, interval, allowedCallCount)); 21 | vm.stopPrank(); 22 | 23 | Status s = policy.status(id, caller); 24 | assertEq(uint8(s), uint8(Status.Live)); 25 | assertEq(policy.usedIds(caller), 1); 26 | 27 | (uint48 cfgInterval, uint48 cfgCount) = policy.rateLimitConfigs(id, caller); 28 | assertEq(cfgInterval, interval); 29 | assertEq(cfgCount, allowedCallCount); 30 | 31 | (uint48 stateCount, uint48 stateResetTime) = policy.rateLimitState(id, caller); 32 | assertEq(stateCount, allowedCallCount); 33 | assertEq(stateResetTime, block.timestamp + interval); 34 | } 35 | 36 | function testCheckUserOp(bytes32 id) external { 37 | uint48 interval = 100; //100 seconds 38 | uint48 allowedCallCount = 10; // you are only allowed to call 10 times within 100 seconds 39 | vm.startPrank(caller); 40 | policy.onInstall(abi.encodePacked(id, interval, allowedCallCount)); 41 | vm.stopPrank(); 42 | 43 | // check initial state 44 | Status s = policy.status(id, caller); 45 | assertEq(uint8(s), uint8(Status.Live)); 46 | assertEq(policy.usedIds(caller), 1); 47 | 48 | (uint48 cfgInterval, uint48 cfgCount) = policy.rateLimitConfigs(id, caller); 49 | assertEq(cfgInterval, interval); 50 | assertEq(cfgCount, allowedCallCount); 51 | 52 | (uint48 stateCount, uint48 stateResetTime) = policy.rateLimitState(id, caller); 53 | assertEq(stateCount, allowedCallCount); 54 | assertEq(stateResetTime, block.timestamp + interval); 55 | 56 | // store next Reset; 57 | uint48 nextReset = stateResetTime; 58 | 59 | // check if call can happen up to allowedCallCount 60 | for (uint48 c; c < allowedCallCount; c++) { 61 | vm.startPrank(caller); 62 | PackedUserOperation memory empty; 63 | policy.checkUserOpPolicy(id, empty); 64 | vm.stopPrank(); 65 | 66 | (stateCount, stateResetTime) = policy.rateLimitState(id, caller); 67 | assertEq(stateCount, allowedCallCount - c - 1); 68 | assertEq(stateResetTime, stateResetTime); 69 | } 70 | 71 | // sanity check if rateLimited 72 | (stateCount, stateResetTime) = policy.rateLimitState(id, caller); 73 | assertEq(stateCount, 0); 74 | assertEq(stateResetTime, stateResetTime); 75 | // check call fails after alloweCallCount is used up 76 | vm.startPrank(caller); 77 | PackedUserOperation memory empty; 78 | vm.expectRevert(RateLimitPolicyReset.RateLimited.selector); 79 | policy.checkUserOpPolicy(id, empty); 80 | vm.stopPrank(); 81 | 82 | // sanity check 83 | (stateCount, stateResetTime) = policy.rateLimitState(id, caller); 84 | assertEq(stateCount, 0); 85 | assertEq(stateResetTime, stateResetTime); 86 | 87 | // warp 88 | vm.warp(stateResetTime); 89 | stateResetTime = stateResetTime + interval; 90 | // check if call can happen up to allowedCallCount, and check if stateResetTime remains same even if blockTimestamp increases 91 | for (uint48 c; c < allowedCallCount; c++) { 92 | vm.warp(block.timestamp + 1); // increase 1 sec 93 | vm.startPrank(caller); 94 | PackedUserOperation memory empty; 95 | policy.checkUserOpPolicy(id, empty); 96 | vm.stopPrank(); 97 | 98 | (stateCount, stateResetTime) = policy.rateLimitState(id, caller); 99 | assertEq(stateCount, allowedCallCount - c - 1); 100 | assertEq(stateResetTime, stateResetTime); 101 | } 102 | 103 | // sanity check 104 | (stateCount, stateResetTime) = policy.rateLimitState(id, caller); 105 | assertEq(stateCount, 0); 106 | assertEq(stateResetTime, stateResetTime); 107 | 108 | // warp 109 | vm.warp(stateResetTime); 110 | stateResetTime = stateResetTime + interval; 111 | // check if count is reset even if count is not used up 112 | for (uint48 c; c < allowedCallCount - 2; c++) { 113 | vm.warp(block.timestamp + 1); // increase timeframe by a bit 114 | vm.startPrank(caller); 115 | policy.checkUserOpPolicy(id, empty); 116 | vm.stopPrank(); 117 | 118 | (stateCount, stateResetTime) = policy.rateLimitState(id, caller); 119 | assertEq(stateCount, allowedCallCount - c - 1); 120 | assertEq(stateResetTime, stateResetTime); 121 | } 122 | 123 | assertEq(stateCount, 2); 124 | assertEq(stateResetTime, stateResetTime); 125 | 126 | vm.warp(stateResetTime); 127 | vm.startPrank(caller); 128 | policy.checkUserOpPolicy(id, empty); 129 | (stateCount, stateResetTime) = policy.rateLimitState(id, caller); 130 | assertEq(stateCount, allowedCallCount - 1); 131 | assertEq(stateResetTime, block.timestamp + interval); 132 | vm.stopPrank(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /policies/signature-caller/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /policies/signature-caller/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /policies/signature-caller/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /policies/signature-caller/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /policies/signature-caller/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | solady/=lib/kernel_v3/lib/solady/src/ 5 | -------------------------------------------------------------------------------- /policies/signature-caller/src/SignaturePolicy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "kernel/sdk/moduleBase/PolicyBase.sol"; 4 | 5 | enum Status { 6 | NA, 7 | Live, 8 | Deprecated 9 | } 10 | 11 | contract SignaturePolicy is PolicyBase { 12 | mapping(address => uint256) public usedIds; 13 | mapping(bytes32 id => mapping(address => Status)) public status; 14 | mapping(bytes32 id => mapping(address caller => mapping(address wallet => bool))) public allowedCaller; 15 | 16 | function isInitialized(address wallet) external view override returns (bool) { 17 | return usedIds[wallet] > 0; 18 | } 19 | 20 | function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp) 21 | external 22 | payable 23 | override 24 | returns (uint256) 25 | { 26 | require(status[id][msg.sender] == Status.Live); 27 | return 0; // always pass 28 | } 29 | 30 | function checkSignaturePolicy(bytes32 id, address sender, bytes32 hash, bytes calldata sig) 31 | external 32 | view 33 | override 34 | returns (uint256) 35 | { 36 | require(status[id][msg.sender] == Status.Live); 37 | if (allowedCaller[id][sender][msg.sender]) { 38 | return 0; 39 | } 40 | return 1; 41 | } 42 | 43 | function _policyOninstall(bytes32 id, bytes calldata _data) internal override { 44 | require(status[id][msg.sender] == Status.NA); 45 | address[] memory callers = abi.decode(_data, (address[])); 46 | for (uint256 i = 0; i < callers.length; i++) { 47 | allowedCaller[id][callers[i]][msg.sender] = true; 48 | } 49 | status[id][msg.sender] = Status.Live; 50 | usedIds[msg.sender]++; 51 | } 52 | 53 | function _policyOnUninstall(bytes32 id, bytes calldata _data) internal override { 54 | require(status[id][msg.sender] == Status.Live); 55 | status[id][msg.sender] = Status.Deprecated; 56 | usedIds[msg.sender]--; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /policies/sudo/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /policies/sudo/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /policies/sudo/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /policies/sudo/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /policies/sudo/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | -------------------------------------------------------------------------------- /policies/sudo/src/SudoPolicy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "kernel/sdk/moduleBase/PolicyBase.sol"; 5 | 6 | contract SudoPolicy is PolicyBase { 7 | mapping(address => uint256) public usedIds; 8 | 9 | function isInitialized(address wallet) external view override returns (bool) { 10 | return usedIds[wallet] > 0; 11 | } 12 | 13 | function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata userOp) 14 | external 15 | payable 16 | override 17 | returns (uint256) 18 | { 19 | return 0; 20 | } 21 | 22 | function checkSignaturePolicy(bytes32 id, address sender, bytes32 hash, bytes calldata sig) 23 | external 24 | view 25 | override 26 | returns (uint256) 27 | { 28 | return 0; 29 | } 30 | 31 | function _policyOninstall(bytes32 id, bytes calldata _data) internal override { 32 | usedIds[msg.sender]++; 33 | } 34 | 35 | function _policyOnUninstall(bytes32 id, bytes calldata _data) internal override { 36 | usedIds[msg.sender]--; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /policies/timestamp/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /policies/timestamp/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /policies/timestamp/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /policies/timestamp/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /policies/timestamp/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | -------------------------------------------------------------------------------- /policies/timestamp/src/TimestampPolicy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "kernel/sdk/moduleBase/PolicyBase.sol"; 4 | import {ValidAfter, ValidUntil, packValidationData} from "kernel/types/Types.sol"; 5 | 6 | enum Status { 7 | NA, 8 | Live, 9 | Deprecated 10 | } 11 | 12 | struct TimestampPolicyConfig { 13 | ValidAfter validAfter; 14 | ValidUntil validUntil; 15 | } 16 | 17 | contract TimestampPolicy is PolicyBase { 18 | mapping(address => uint256) public usedIds; 19 | mapping(bytes32 id => mapping(address => Status)) public status; 20 | mapping(bytes32 id => mapping(address => TimestampPolicyConfig)) public timestampPolicyConfig; 21 | 22 | function isInitialized(address wallet) external view override returns (bool) { 23 | return usedIds[wallet] > 0; 24 | } 25 | 26 | function checkUserOpPolicy(bytes32 id, PackedUserOperation calldata) 27 | external 28 | payable 29 | override 30 | returns (uint256) 31 | { 32 | require(status[id][msg.sender] == Status.Live); 33 | TimestampPolicyConfig memory config = timestampPolicyConfig[id][msg.sender]; 34 | return packValidationData(config.validAfter, config.validUntil); 35 | } 36 | 37 | function checkSignaturePolicy(bytes32 id, address, bytes32, bytes calldata) 38 | external 39 | view 40 | override 41 | returns (uint256) 42 | { 43 | require(status[id][msg.sender] == Status.Live); 44 | return 0; 45 | } 46 | 47 | function _policyOninstall(bytes32 id, bytes calldata _data) internal override { 48 | require(status[id][msg.sender] == Status.NA); 49 | (ValidAfter validAfter, ValidUntil validUntil) = abi.decode(_data, (ValidAfter, ValidUntil)); 50 | timestampPolicyConfig[id][msg.sender] = TimestampPolicyConfig(validAfter, validUntil); 51 | status[id][msg.sender] = Status.Live; 52 | usedIds[msg.sender]++; 53 | } 54 | 55 | function _policyOnUninstall(bytes32 id, bytes calldata) internal override { 56 | require(status[id][msg.sender] == Status.Live); 57 | status[id][msg.sender] = Status.Deprecated; 58 | usedIds[msg.sender]--; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /signers/anysigner/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /signers/anysigner/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /signers/anysigner/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /signers/anysigner/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /signers/anysigner/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | -------------------------------------------------------------------------------- /signers/anysigner/src/AnySigner.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "kernel/sdk/moduleBase/SignerBase.sol"; 4 | 5 | contract AnySigner is SignerBase { 6 | mapping(address => uint256) public usedIds; 7 | 8 | function isInitialized(address wallet) external view override returns (bool) { 9 | return usedIds[wallet] > 0; 10 | } 11 | 12 | function checkUserOpSignature(bytes32 id, PackedUserOperation calldata userOp, bytes32 userOpHash) 13 | external 14 | payable 15 | override 16 | returns (uint256) 17 | { 18 | return 0; 19 | } 20 | 21 | function checkSignature(bytes32 id, address sender, bytes32 hash, bytes calldata sig) 22 | external 23 | view 24 | override 25 | returns (bytes4) 26 | { 27 | return 0x1626ba7e; 28 | } 29 | 30 | function _signerOninstall(bytes32 id, bytes calldata _data) internal override { 31 | usedIds[msg.sender]++; 32 | } 33 | 34 | function _signerOnUninstall(bytes32 id, bytes calldata) internal override { 35 | require(usedIds[msg.sender] > 0); 36 | usedIds[msg.sender]--; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /signers/ecdsa/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /signers/ecdsa/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /signers/ecdsa/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /signers/ecdsa/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /signers/ecdsa/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/kernel_v3/lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | kernel/=lib/kernel_v3/src/ 4 | solady/=lib/kernel_v3/lib/solady/src/ 5 | -------------------------------------------------------------------------------- /signers/ecdsa/src/ECDSASigner.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "kernel/sdk/moduleBase/SignerBase.sol"; 4 | import {ECDSA} from "solady/utils/ECDSA.sol"; 5 | 6 | contract ECDSASigner is SignerBase { 7 | mapping(address => uint256) public usedIds; 8 | mapping(bytes32 id => mapping(address wallet => address)) public signer; 9 | 10 | function isInitialized(address wallet) external view override returns (bool) { 11 | return usedIds[wallet] > 0; 12 | } 13 | 14 | function checkUserOpSignature(bytes32 id, PackedUserOperation calldata userOp, bytes32 userOpHash) 15 | external 16 | payable 17 | override 18 | returns (uint256) 19 | { 20 | address owner = signer[id][msg.sender]; 21 | bytes calldata sig = userOp.signature; 22 | if (owner == ECDSA.recover(userOpHash, sig)) { 23 | return 0; 24 | } 25 | bytes32 ethHash = ECDSA.toEthSignedMessageHash(userOpHash); 26 | address recovered = ECDSA.recover(ethHash, sig); 27 | if (owner != recovered) { 28 | return 1; 29 | } 30 | return 0; 31 | } 32 | 33 | function checkSignature(bytes32 id, address sender, bytes32 hash, bytes calldata sig) 34 | external 35 | view 36 | override 37 | returns (bytes4) 38 | { 39 | address owner = signer[id][msg.sender]; 40 | if (owner == ECDSA.recover(hash, sig)) { 41 | return 0x1626ba7e; 42 | } 43 | bytes32 ethHash = ECDSA.toEthSignedMessageHash(hash); 44 | address recovered = ECDSA.recover(ethHash, sig); 45 | if (owner != recovered) { 46 | return 0xffffffff; 47 | } 48 | return 0x1626ba7e; 49 | } 50 | 51 | function _signerOninstall(bytes32 id, bytes calldata _data) internal override { 52 | require(signer[id][msg.sender] == address(0)); 53 | usedIds[msg.sender]++; 54 | signer[id][msg.sender] = address(bytes20(_data[0:20])); 55 | } 56 | 57 | function _signerOnUninstall(bytes32 id, bytes calldata) internal override { 58 | require(signer[id][msg.sender] != address(0)); 59 | delete signer[id][msg.sender]; 60 | usedIds[msg.sender]--; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /signers/webauthn/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /signers/webauthn/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /signers/webauthn/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /signers/webauthn/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /signers/webauthn/remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ 2 | ds-test/=lib/kernel/lib/forge-std/lib/ds-test/src/ 3 | erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ 4 | forge-std/=lib/forge-std/src/ 5 | kernel/=lib/kernel/src/ 6 | openzeppelin-contracts/=lib/openzeppelin-contracts/ 7 | solady/=lib/kernel/lib/solady/src/ 8 | -------------------------------------------------------------------------------- /signers/webauthn/src/Base64URL.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "openzeppelin-contracts/contracts/utils/Base64.sol"; 5 | 6 | library Base64URL { 7 | function encode(bytes memory data) internal pure returns (string memory) { 8 | string memory strb64 = Base64.encode(data); 9 | bytes memory b64 = bytes(strb64); 10 | 11 | // Base64 can end with "=" or "=="; Base64URL has no padding. 12 | uint256 equalsCount = 0; 13 | if (b64.length > 2 && b64[b64.length - 2] == "=") equalsCount = 2; 14 | else if (b64.length > 1 && b64[b64.length - 1] == "=") equalsCount = 1; 15 | 16 | uint256 len = b64.length - equalsCount; 17 | bytes memory result = new bytes(len); 18 | 19 | for (uint256 i = 0; i < len; i++) { 20 | if (b64[i] == "+") { 21 | result[i] = "-"; 22 | } else if (b64[i] == "/") { 23 | result[i] = "_"; 24 | } else { 25 | result[i] = b64[i]; 26 | } 27 | } 28 | 29 | return string(result); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /signers/webauthn/src/P256.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | * Helper library for external contracts to verify P256 signatures. 6 | * 7 | */ 8 | library P256 { 9 | address constant DAIMO_VERIFIER = 0xc2b78104907F722DABAc4C69f826a522B2754De4; 10 | address constant PRECOMPILED_VERIFIER = 0x0000000000000000000000000000000000000100; 11 | 12 | function verifySignatureAllowMalleability( 13 | bytes32 message_hash, 14 | uint256 r, 15 | uint256 s, 16 | uint256 x, 17 | uint256 y, 18 | bool usePrecompiled 19 | ) internal view returns (bool) { 20 | bytes memory args = abi.encode(message_hash, r, s, x, y); 21 | 22 | if (usePrecompiled) { 23 | (bool success, bytes memory ret) = PRECOMPILED_VERIFIER.staticcall(args); 24 | if (success == false || ret.length == 0) { 25 | return false; 26 | } 27 | return abi.decode(ret, (uint256)) == 1; 28 | } else { 29 | (, bytes memory ret) = DAIMO_VERIFIER.staticcall(args); 30 | return abi.decode(ret, (uint256)) == 1; 31 | } 32 | } 33 | 34 | /// P256 curve order n/2 for malleability check 35 | uint256 constant P256_N_DIV_2 = 57896044605178124381348723474703786764998477612067880171211129530534256022184; 36 | 37 | function verifySignature(bytes32 message_hash, uint256 r, uint256 s, uint256 x, uint256 y, bool usePrecompiled) 38 | internal 39 | view 40 | returns (bool) 41 | { 42 | // check for signature malleability 43 | if (s > P256_N_DIV_2) { 44 | return false; 45 | } 46 | 47 | return verifySignatureAllowMalleability(message_hash, r, s, x, y, usePrecompiled); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /signers/webauthn/src/WebAuthn.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Base64URL.sol"; 6 | import "./P256.sol"; 7 | 8 | /** 9 | * Helper library for external contracts to verify WebAuthn signatures. 10 | * 11 | */ 12 | library WebAuthn { 13 | /// Checks whether substr occurs in str starting at a given byte offset. 14 | function contains(string memory substr, string memory str, uint256 location) internal pure returns (bool) { 15 | bytes memory substrBytes = bytes(substr); 16 | bytes memory strBytes = bytes(str); 17 | 18 | uint256 substrLen = substrBytes.length; 19 | uint256 strLen = strBytes.length; 20 | 21 | for (uint256 i = 0; i < substrLen; i++) { 22 | if (location + i >= strLen) { 23 | return false; 24 | } 25 | 26 | if (substrBytes[i] != strBytes[location + i]) { 27 | return false; 28 | } 29 | } 30 | 31 | return true; 32 | } 33 | 34 | bytes1 constant AUTH_DATA_FLAGS_UP = 0x01; // Bit 0 35 | bytes1 constant AUTH_DATA_FLAGS_UV = 0x04; // Bit 2 36 | bytes1 constant AUTH_DATA_FLAGS_BE = 0x08; // Bit 3 37 | bytes1 constant AUTH_DATA_FLAGS_BS = 0x10; // Bit 4 38 | 39 | /// Verifies the authFlags in authenticatorData. Numbers in inline comment 40 | /// correspond to the same numbered bullets in 41 | /// https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. 42 | function checkAuthFlags(bytes1 flags, bool requireUserVerification) internal pure returns (bool) { 43 | // 17. Verify that the UP bit of the flags in authData is set. 44 | if (flags & AUTH_DATA_FLAGS_UP != AUTH_DATA_FLAGS_UP) { 45 | return false; 46 | } 47 | 48 | // 18. If user verification was determined to be required, verify that 49 | // the UV bit of the flags in authData is set. Otherwise, ignore the 50 | // value of the UV flag. 51 | if (requireUserVerification && (flags & AUTH_DATA_FLAGS_UV) != AUTH_DATA_FLAGS_UV) { 52 | return false; 53 | } 54 | 55 | // 19. If the BE bit of the flags in authData is not set, verify that 56 | // the BS bit is not set. 57 | if (flags & AUTH_DATA_FLAGS_BE != AUTH_DATA_FLAGS_BE) { 58 | if (flags & AUTH_DATA_FLAGS_BS == AUTH_DATA_FLAGS_BS) { 59 | return false; 60 | } 61 | } 62 | 63 | return true; 64 | } 65 | 66 | /** 67 | * Verifies a Webauthn P256 signature (Authentication Assertion) as described 68 | * in https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. We do not 69 | * verify all the steps as described in the specification, only ones relevant 70 | * to our context. Please carefully read through this list before usage. 71 | * Specifically, we do verify the following: 72 | * - Verify that authenticatorData (which comes from the authenticator, 73 | * such as iCloud Keychain) indicates a well-formed assertion. If 74 | * requireUserVerification is set, checks that the authenticator enforced 75 | * user verification. User verification should be required if, 76 | * and only if, options.userVerification is set to required in the request 77 | * - Verifies that the client JSON is of type "webauthn.get", i.e. the client 78 | * was responding to a request to assert authentication. 79 | * - Verifies that the client JSON contains the requested challenge. 80 | * - Finally, verifies that (r, s) constitute a valid signature over both 81 | * the authenicatorData and client JSON, for public key (x, y). 82 | * 83 | * We make some assumptions about the particular use case of this verifier, 84 | * so we do NOT verify the following: 85 | * - Does NOT verify that the origin in the clientDataJSON matches the 86 | * Relying Party's origin: It is considered the authenticator's 87 | * responsibility to ensure that the user is interacting with the correct 88 | * RP. This is enforced by most high quality authenticators properly, 89 | * particularly the iCloud Keychain and Google Password Manager were 90 | * tested. 91 | * - Does NOT verify That c.topOrigin is well-formed: We assume c.topOrigin 92 | * would never be present, i.e. the credentials are never used in a 93 | * cross-origin/iframe context. The website/app set up should disallow 94 | * cross-origin usage of the credentials. This is the default behaviour for 95 | * created credentials in common settings. 96 | * - Does NOT verify that the rpIdHash in authData is the SHA-256 hash of an 97 | * RP ID expected by the Relying Party: This means that we rely on the 98 | * authenticator to properly enforce credentials to be used only by the 99 | * correct RP. This is generally enforced with features like Apple App Site 100 | * Association and Google Asset Links. To protect from edge cases in which 101 | * a previously-linked RP ID is removed from the authorised RP IDs, 102 | * we recommend that messages signed by the authenticator include some 103 | * expiry mechanism. 104 | * - Does NOT verify the credential backup state: This assumes the credential 105 | * backup state is NOT used as part of Relying Party business logic or 106 | * policy. 107 | * - Does NOT verify the values of the client extension outputs: This assumes 108 | * that the Relying Party does not use client extension outputs. 109 | * - Does NOT verify the signature counter: Signature counters are intended 110 | * to enable risk scoring for the Relying Party. This assumes risk scoring 111 | * is not used as part of Relying Party business logic or policy. 112 | * - Does NOT verify the attestation object: This assumes that 113 | * response.attestationObject is NOT present in the response, i.e. the 114 | * RP does not intend to verify an attestation. 115 | */ 116 | function verifySignature( 117 | bytes memory challenge, 118 | bytes memory authenticatorData, 119 | bool requireUserVerification, 120 | string memory clientDataJSON, 121 | uint256 challengeLocation, 122 | uint256 responseTypeLocation, 123 | uint256 r, 124 | uint256 s, 125 | uint256 x, 126 | uint256 y, 127 | bool usePrecompiled 128 | ) internal view returns (bool) { 129 | /// @notice defer the result to the end so dummy signature can go through all verification process including p256.verifySignature 130 | bool deferredResult = true; 131 | 132 | // Check that authenticatorData has good flags 133 | if (authenticatorData.length < 37 || !checkAuthFlags(authenticatorData[32], requireUserVerification)) { 134 | deferredResult = false; 135 | } 136 | 137 | // Check that response is for an authentication assertion 138 | string memory responseType = '"type":"webauthn.get"'; 139 | if (!contains(responseType, clientDataJSON, responseTypeLocation)) { 140 | deferredResult = false; 141 | } 142 | 143 | // Check that challenge is in the clientDataJSON 144 | string memory challengeB64url = Base64URL.encode(challenge); 145 | string memory challengeProperty = string.concat('"challenge":"', challengeB64url, '"'); 146 | 147 | if (!contains(challengeProperty, clientDataJSON, challengeLocation)) { 148 | deferredResult = false; 149 | } 150 | 151 | // Check that the public key signed sha256(authenticatorData || sha256(clientDataJSON)) 152 | bytes32 clientDataJSONHash = sha256(bytes(clientDataJSON)); 153 | bytes32 messageHash = sha256(abi.encodePacked(authenticatorData, clientDataJSONHash)); 154 | 155 | // if responseTypeLocation is set to max, it means the signature is a dummy signature 156 | if (responseTypeLocation == type(uint256).max) { 157 | return P256.verifySignature(messageHash, r, s, x, y, false); 158 | } 159 | 160 | bool verified = P256.verifySignature(messageHash, r, s, x, y, usePrecompiled); 161 | 162 | if (verified && deferredResult) { 163 | return true; 164 | } 165 | return false; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /signers/webauthn/src/WebAuthnSigner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "kernel/sdk/moduleBase/SignerBase.sol"; 6 | import {SIG_VALIDATION_SUCCESS_UINT, SIG_VALIDATION_FAILED_UINT} from "kernel/types/Constants.sol"; 7 | import {PackedUserOperation} from "kernel/interfaces/PackedUserOperation.sol"; 8 | import {ERC1271_MAGICVALUE, ERC1271_INVALID} from "kernel/types/Constants.sol"; 9 | import {WebAuthn} from "./WebAuthn.sol"; 10 | 11 | struct WebAuthnSignerData { 12 | uint256 pubKeyX; 13 | uint256 pubKeyY; 14 | } 15 | 16 | /** 17 | * @title WebAuthnSigner 18 | * @notice This signer uses the P256 curve to validate signatures. 19 | */ 20 | contract WebAuthnSigner is SignerBase { 21 | // The location of the challenge in the clientDataJSON 22 | uint256 constant CHALLENGE_LOCATION = 23; 23 | 24 | // Emitted when a bad key is provided. 25 | error InvalidPublicKey(); 26 | 27 | // Emitted when the public key of a kernel is changed. 28 | event WebAuthnPublicKeyRegistered( 29 | address indexed kernel, bytes32 indexed authenticatorIdHash, uint256 pubKeyX, uint256 pubKeyY 30 | ); 31 | 32 | mapping(address => uint256) public usedIds; 33 | // The P256 public keys of a kernel. 34 | mapping(bytes32 id => mapping(address kernel => WebAuthnSignerData)) public webAuthnSignerStorage; 35 | 36 | function isInitialized(address kernel) external view override returns (bool) { 37 | return _isInitialized(kernel); 38 | } 39 | 40 | function _isInitialized(address kernel) internal view returns (bool) { 41 | return usedIds[kernel] > 0; 42 | } 43 | 44 | /** 45 | * @notice Validate a user operation. 46 | */ 47 | function checkUserOpSignature(bytes32 id, PackedUserOperation calldata userOp, bytes32 userOpHash) 48 | external 49 | payable 50 | override 51 | returns (uint256) 52 | { 53 | return _verifySignature(id, msg.sender, userOpHash, userOp.signature); 54 | } 55 | 56 | /** 57 | * @notice Verify a signature with sender for ERC-1271 validation. 58 | */ 59 | function checkSignature(bytes32 id, address, bytes32 hash, bytes calldata sig) 60 | external 61 | view 62 | override 63 | returns (bytes4) 64 | { 65 | return _verifySignature(id, msg.sender, hash, sig) == SIG_VALIDATION_SUCCESS_UINT 66 | ? ERC1271_MAGICVALUE 67 | : ERC1271_INVALID; 68 | } 69 | 70 | /** 71 | * @notice Verify a signature. 72 | */ 73 | function _verifySignature(bytes32 id, address account, bytes32 hash, bytes calldata signature) 74 | private 75 | view 76 | returns (uint256) 77 | { 78 | // decode the signature 79 | ( 80 | bytes memory authenticatorData, 81 | string memory clientDataJSON, 82 | uint256 responseTypeLocation, 83 | uint256 r, 84 | uint256 s, 85 | bool usePrecompiled 86 | ) = abi.decode(signature, (bytes, string, uint256, uint256, uint256, bool)); 87 | 88 | // get the public key from storage 89 | WebAuthnSignerData memory webAuthnData = webAuthnSignerStorage[id][account]; 90 | 91 | // verify the signature using the signature and the public key 92 | bool isValid = WebAuthn.verifySignature( 93 | abi.encodePacked(hash), 94 | authenticatorData, 95 | true, 96 | clientDataJSON, 97 | CHALLENGE_LOCATION, 98 | responseTypeLocation, 99 | r, 100 | s, 101 | webAuthnData.pubKeyX, 102 | webAuthnData.pubKeyY, 103 | usePrecompiled 104 | ); 105 | 106 | // return the validation data 107 | if (isValid) { 108 | return SIG_VALIDATION_SUCCESS_UINT; 109 | } 110 | 111 | return SIG_VALIDATION_FAILED_UINT; 112 | } 113 | /** 114 | * @notice Install WebAuthn signer for a kernel account. 115 | * @dev The kernel account need to be the `msg.sender`. 116 | * @dev The public key is encoded as `abi.encode(WebAuthnSignerData)` inside the data, so (uint256,uint256). 117 | * @dev The authenticatorIdHash is the hash of the authenticatorId. It enables to find public keys on-chain via event logs. 118 | */ 119 | 120 | function _signerOninstall(bytes32 id, bytes calldata _data) internal override { 121 | // check if the webauthn validator is already initialized 122 | if (_isInitialized(msg.sender)) revert AlreadyInitialized(msg.sender); 123 | usedIds[msg.sender]++; 124 | // check validity of the public key 125 | (WebAuthnSignerData memory webAuthnData, bytes32 authenticatorIdHash) = 126 | abi.decode(_data, (WebAuthnSignerData, bytes32)); 127 | if (webAuthnData.pubKeyX == 0 || webAuthnData.pubKeyY == 0) { 128 | revert InvalidPublicKey(); 129 | } 130 | // Update the key (so a sstore) 131 | webAuthnSignerStorage[id][msg.sender] = webAuthnData; 132 | // And emit the event 133 | emit WebAuthnPublicKeyRegistered(msg.sender, authenticatorIdHash, webAuthnData.pubKeyX, webAuthnData.pubKeyY); 134 | } 135 | 136 | /** 137 | * @notice Uninstall WebAuthn validator for a kernel account. 138 | * @dev The kernel account need to be the `msg.sender`. 139 | */ 140 | function _signerOnUninstall(bytes32 id, bytes calldata) internal override { 141 | if (!_isInitialized(msg.sender)) revert NotInitialized(msg.sender); 142 | delete webAuthnSignerStorage[id][msg.sender]; 143 | usedIds[msg.sender]--; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /validators/webauthn/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /validators/webauthn/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /validators/webauthn/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /validators/webauthn/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | bytecode_hash = "none" 6 | cbor_metadata = false 7 | optimize = true 8 | via-ir = true 9 | runs = 1000 10 | 11 | [profile.deploy] 12 | via-ir = true 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | [fuzz] 16 | runs = 256 17 | -------------------------------------------------------------------------------- /validators/webauthn/remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ 2 | ds-test/=lib/kernel/lib/forge-std/lib/ds-test/src/ 3 | erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ 4 | forge-std/=lib/forge-std/src/ 5 | kernel/=lib/kernel/src/ 6 | openzeppelin-contracts/=lib/openzeppelin-contracts/ 7 | solady/=lib/kernel/lib/solady/src/ 8 | -------------------------------------------------------------------------------- /validators/webauthn/src/Base64URL.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "openzeppelin-contracts/contracts/utils/Base64.sol"; 5 | 6 | library Base64URL { 7 | function encode(bytes memory data) internal pure returns (string memory) { 8 | string memory strb64 = Base64.encode(data); 9 | bytes memory b64 = bytes(strb64); 10 | 11 | // Base64 can end with "=" or "=="; Base64URL has no padding. 12 | uint256 equalsCount = 0; 13 | if (b64.length > 2 && b64[b64.length - 2] == "=") equalsCount = 2; 14 | else if (b64.length > 1 && b64[b64.length - 1] == "=") equalsCount = 1; 15 | 16 | uint256 len = b64.length - equalsCount; 17 | bytes memory result = new bytes(len); 18 | 19 | for (uint256 i = 0; i < len; i++) { 20 | if (b64[i] == "+") { 21 | result[i] = "-"; 22 | } else if (b64[i] == "/") { 23 | result[i] = "_"; 24 | } else { 25 | result[i] = b64[i]; 26 | } 27 | } 28 | 29 | return string(result); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /validators/webauthn/src/P256.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | * Helper library for external contracts to verify P256 signatures. 6 | * 7 | */ 8 | library P256 { 9 | address constant DAIMO_VERIFIER = 0xc2b78104907F722DABAc4C69f826a522B2754De4; 10 | address constant PRECOMPILED_VERIFIER = 0x0000000000000000000000000000000000000100; 11 | 12 | function verifySignatureAllowMalleability( 13 | bytes32 message_hash, 14 | uint256 r, 15 | uint256 s, 16 | uint256 x, 17 | uint256 y, 18 | bool usePrecompiled 19 | ) internal view returns (bool) { 20 | bytes memory args = abi.encode(message_hash, r, s, x, y); 21 | 22 | if (usePrecompiled) { 23 | (bool success, bytes memory ret) = PRECOMPILED_VERIFIER.staticcall(args); 24 | if (success == false || ret.length == 0) { 25 | return false; 26 | } 27 | return abi.decode(ret, (uint256)) == 1; 28 | } else { 29 | (, bytes memory ret) = DAIMO_VERIFIER.staticcall(args); 30 | return abi.decode(ret, (uint256)) == 1; 31 | } 32 | } 33 | 34 | /// P256 curve order n/2 for malleability check 35 | uint256 constant P256_N_DIV_2 = 57896044605178124381348723474703786764998477612067880171211129530534256022184; 36 | 37 | function verifySignature(bytes32 message_hash, uint256 r, uint256 s, uint256 x, uint256 y, bool usePrecompiled) 38 | internal 39 | view 40 | returns (bool) 41 | { 42 | // check for signature malleability 43 | if (s > P256_N_DIV_2) { 44 | return false; 45 | } 46 | 47 | return verifySignatureAllowMalleability(message_hash, r, s, x, y, usePrecompiled); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /validators/webauthn/src/P256Verifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Force a specific Solidity version for reproducibility. 3 | pragma solidity ^0.8.21; 4 | 5 | /** 6 | * This contract verifies P256 (secp256r1) signatures. It matches the exact 7 | * interface specified in the EIP-7212 precompile, allowing it to be used as a 8 | * fallback. It's based on Ledger's optimized implementation: 9 | * https://github.com/rdubois-crypto/FreshCryptoLib/tree/master/solidity 10 | * 11 | */ 12 | contract P256Verifier { 13 | /** 14 | * Precompiles don't use a function signature. The first byte of callldata 15 | * is the first byte of an input argument. In this case: 16 | * 17 | * input[ 0: 32] = signed data hash 18 | * input[ 32: 64] = signature r 19 | * input[ 64: 96] = signature s 20 | * input[ 96:128] = public key x 21 | * input[128:160] = public key y 22 | * 23 | * result[ 0: 32] = 0x00..00 (invalid) or 0x00..01 (valid) 24 | * 25 | * For details, see https://eips.ethereum.org/EIPS/eip-7212 26 | */ 27 | fallback(bytes calldata input) external returns (bytes memory) { 28 | if (input.length != 160) { 29 | return abi.encodePacked(uint256(0)); 30 | } 31 | 32 | bytes32 hash = bytes32(input[0:32]); 33 | uint256 r = uint256(bytes32(input[32:64])); 34 | uint256 s = uint256(bytes32(input[64:96])); 35 | uint256 x = uint256(bytes32(input[96:128])); 36 | uint256 y = uint256(bytes32(input[128:160])); 37 | 38 | uint256 ret = ecdsa_verify(hash, r, s, [x, y]) ? 1 : 0; 39 | 40 | return abi.encodePacked(ret); 41 | } 42 | 43 | // Parameters for the sec256r1 (P256) elliptic curve 44 | // Curve prime field modulus 45 | uint256 constant p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; 46 | // Short weierstrass first coefficient 47 | uint256 constant a = // The assumption a == -3 (mod p) is used throughout the codebase 48 | 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC; 49 | // Short weierstrass second coefficient 50 | uint256 constant b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B; 51 | // Generating point affine coordinates 52 | uint256 constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296; 53 | uint256 constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5; 54 | // Curve order (number of points) 55 | uint256 constant n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; 56 | // -2 mod p constant, used to speed up inversion and doubling (avoid negation) 57 | uint256 constant minus_2modp = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFD; 58 | // -2 mod n constant, used to speed up inversion 59 | uint256 constant minus_2modn = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC63254F; 60 | 61 | /** 62 | * @dev ECDSA verification given signature and public key. 63 | */ 64 | function ecdsa_verify(bytes32 message_hash, uint256 r, uint256 s, uint256[2] memory pubKey) 65 | private 66 | view 67 | returns (bool) 68 | { 69 | // Check r and s are in the scalar field 70 | if (r == 0 || r >= n || s == 0 || s >= n) { 71 | return false; 72 | } 73 | 74 | if (!ecAff_isValidPubkey(pubKey[0], pubKey[1])) { 75 | return false; 76 | } 77 | 78 | uint256 sInv = nModInv(s); 79 | 80 | uint256 scalar_u = mulmod(uint256(message_hash), sInv, n); // (h * s^-1) in scalar field 81 | uint256 scalar_v = mulmod(r, sInv, n); // (r * s^-1) in scalar field 82 | 83 | uint256 r_x = ecZZ_mulmuladd(pubKey[0], pubKey[1], scalar_u, scalar_v); 84 | return r_x % n == r; 85 | } 86 | 87 | /** 88 | * @dev Check if a point in affine coordinates is on the curve 89 | * Reject 0 point at infinity. 90 | */ 91 | function ecAff_isValidPubkey(uint256 x, uint256 y) internal pure returns (bool) { 92 | if (x >= p || y >= p || (x == 0 && y == 0)) { 93 | return false; 94 | } 95 | 96 | return ecAff_satisfiesCurveEqn(x, y); 97 | } 98 | 99 | function ecAff_satisfiesCurveEqn(uint256 x, uint256 y) internal pure returns (bool) { 100 | uint256 LHS = mulmod(y, y, p); // y^2 101 | uint256 RHS = addmod(mulmod(mulmod(x, x, p), x, p), mulmod(a, x, p), p); // x^3 + a x 102 | RHS = addmod(RHS, b, p); // x^3 + a*x + b 103 | 104 | return LHS == RHS; 105 | } 106 | 107 | /** 108 | * @dev Computation of uG + vQ using Strauss-Shamir's trick, G basepoint, Q public key 109 | * returns tuple of (x coordinate of uG + vQ, boolean that is false if internal precompile staticcall fail) 110 | * Strauss-Shamir is described well in https://stackoverflow.com/a/50994362 111 | */ 112 | function ecZZ_mulmuladd( 113 | uint256 QX, 114 | uint256 QY, // affine rep for input point Q 115 | uint256 scalar_u, 116 | uint256 scalar_v 117 | ) internal view returns (uint256 X) { 118 | uint256 zz = 1; 119 | uint256 zzz = 1; 120 | uint256 Y; 121 | uint256 HX; 122 | uint256 HY; 123 | 124 | if (scalar_u == 0 && scalar_v == 0) return 0; 125 | 126 | // H = g + Q 127 | (HX, HY) = ecAff_add(GX, GY, QX, QY); 128 | 129 | int256 index = 255; 130 | uint256 bitpair; 131 | 132 | // Find the first bit index that's active in either scalar_u or scalar_v. 133 | while (index >= 0) { 134 | bitpair = compute_bitpair(uint256(index), scalar_u, scalar_v); 135 | index--; 136 | if (bitpair != 0) break; 137 | } 138 | 139 | // initialise (X, Y) depending on the first active bitpair. 140 | // invariant(bitpair != 0); // bitpair == 0 is only possible if u and v are 0. 141 | 142 | if (bitpair == 1) { 143 | (X, Y) = (GX, GY); 144 | } else if (bitpair == 2) { 145 | (X, Y) = (QX, QY); 146 | } else if (bitpair == 3) { 147 | (X, Y) = (HX, HY); 148 | } 149 | 150 | uint256 TX; 151 | uint256 TY; 152 | while (index >= 0) { 153 | (X, Y, zz, zzz) = ecZZ_double_zz(X, Y, zz, zzz); 154 | 155 | bitpair = compute_bitpair(uint256(index), scalar_u, scalar_v); 156 | index--; 157 | 158 | if (bitpair == 0) { 159 | continue; 160 | } else if (bitpair == 1) { 161 | (TX, TY) = (GX, GY); 162 | } else if (bitpair == 2) { 163 | (TX, TY) = (QX, QY); 164 | } else { 165 | (TX, TY) = (HX, HY); 166 | } 167 | 168 | (X, Y, zz, zzz) = ecZZ_dadd_affine(X, Y, zz, zzz, TX, TY); 169 | } 170 | 171 | uint256 zzInv = pModInv(zz); // If zz = 0, zzInv = 0. 172 | X = mulmod(X, zzInv, p); // X/zz 173 | } 174 | 175 | /** 176 | * @dev Compute the bits at `index` of u and v and return 177 | * them as 2 bit concatenation. The bit at index 0 is on 178 | * if the `index`th bit of scalar_u is on and the bit at 179 | * index 1 is on if the `index`th bit of scalar_v is on. 180 | * Examples: 181 | * - compute_bitpair(0, 1, 1) == 3 182 | * - compute_bitpair(0, 1, 0) == 1 183 | * - compute_bitpair(0, 0, 1) == 2 184 | */ 185 | function compute_bitpair(uint256 index, uint256 scalar_u, uint256 scalar_v) internal pure returns (uint256 ret) { 186 | ret = (((scalar_v >> index) & 1) << 1) + ((scalar_u >> index) & 1); 187 | } 188 | 189 | /** 190 | * @dev Add two elliptic curve points in affine coordinates 191 | * Assumes points are on the EC 192 | */ 193 | function ecAff_add(uint256 x1, uint256 y1, uint256 x2, uint256 y2) internal view returns (uint256, uint256) { 194 | // invariant(ecAff_IsZero(x1, y1) || ecAff_isOnCurve(x1, y1)); 195 | // invariant(ecAff_IsZero(x2, y2) || ecAff_isOnCurve(x2, y2)); 196 | 197 | uint256 zz1; 198 | uint256 zzz1; 199 | 200 | if (ecAff_IsInf(x1, y1)) return (x2, y2); 201 | if (ecAff_IsInf(x2, y2)) return (x1, y1); 202 | 203 | (x1, y1, zz1, zzz1) = ecZZ_dadd_affine(x1, y1, 1, 1, x2, y2); 204 | 205 | return ecZZ_SetAff(x1, y1, zz1, zzz1); 206 | } 207 | 208 | /** 209 | * @dev Check if a point is the infinity point in affine rep. 210 | * Assumes point is on the EC or is the point at infinity. 211 | */ 212 | function ecAff_IsInf(uint256 x, uint256 y) internal pure returns (bool flag) { 213 | // invariant((x == 0 && y == 0) || ecAff_isOnCurve(x, y)); 214 | 215 | return (x == 0 && y == 0); 216 | } 217 | 218 | /** 219 | * @dev Check if a point is the infinity point in ZZ rep. 220 | * Assumes point is on the EC or is the point at infinity. 221 | */ 222 | function ecZZ_IsInf(uint256 zz, uint256 zzz) internal pure returns (bool flag) { 223 | // invariant((zz == 0 && zzz == 0) || ecAff_isOnCurve(x, y) for affine 224 | // form of the point) 225 | 226 | return (zz == 0 && zzz == 0); 227 | } 228 | 229 | /** 230 | * @dev Add a ZZ point to an affine point and return as ZZ rep 231 | * Uses madd-2008-s and mdbl-2008-s internally 232 | * https://hyperelliptic.org/EFD/g1p/auto-shortw-xyzz-3.html#addition-madd-2008-s 233 | * Matches https://github.com/supranational/blst/blob/9c87d4a09d6648e933c818118a4418349804ce7f/src/ec_ops.h#L705 closely 234 | * Handles points at infinity gracefully 235 | */ 236 | function ecZZ_dadd_affine(uint256 x1, uint256 y1, uint256 zz1, uint256 zzz1, uint256 x2, uint256 y2) 237 | internal 238 | pure 239 | returns (uint256 x3, uint256 y3, uint256 zz3, uint256 zzz3) 240 | { 241 | if (ecAff_IsInf(x2, y2)) { 242 | // (X2, Y2) is point at infinity 243 | if (ecZZ_IsInf(zz1, zzz1)) return ecZZ_PointAtInf(); 244 | return (x1, y1, zz1, zzz1); 245 | } else if (ecZZ_IsInf(zz1, zzz1)) { 246 | // (X1, Y1) is point at infinity 247 | return (x2, y2, 1, 1); 248 | } 249 | 250 | uint256 comp_R = addmod(mulmod(y2, zzz1, p), p - y1, p); // R = S2 - y1 = y2*zzz1 - y1 251 | uint256 comp_P = addmod(mulmod(x2, zz1, p), p - x1, p); // P = U2 - x1 = x2*zz1 - x1 252 | 253 | if (comp_P != 0) { 254 | // X1 != X2 255 | // invariant(x1 != x2); 256 | uint256 comp_PP = mulmod(comp_P, comp_P, p); // PP = P^2 257 | uint256 comp_PPP = mulmod(comp_PP, comp_P, p); // PPP = P*PP 258 | zz3 = mulmod(zz1, comp_PP, p); //// ZZ3 = ZZ1*PP 259 | zzz3 = mulmod(zzz1, comp_PPP, p); //// ZZZ3 = ZZZ1*PPP 260 | uint256 comp_Q = mulmod(x1, comp_PP, p); // Q = X1*PP 261 | x3 = addmod( 262 | addmod(mulmod(comp_R, comp_R, p), p - comp_PPP, p), // (R^2) + (-PPP) 263 | mulmod(minus_2modp, comp_Q, p), // (-2)*(Q) 264 | p 265 | ); // R^2 - PPP - 2*Q 266 | y3 = addmod( 267 | mulmod(addmod(comp_Q, p - x3, p), comp_R, p), //(Q+(-x3))*R 268 | mulmod(p - y1, comp_PPP, p), // (-y1)*PPP 269 | p 270 | ); // R*(Q-x3) - y1*PPP 271 | } else if (comp_R == 0) { 272 | // X1 == X2 and Y1 == Y2 273 | // invariant(x1 == x2 && y1 == y2); 274 | 275 | // Must be affine because (X2, Y2) is affine. 276 | (x3, y3, zz3, zzz3) = ecZZ_double_affine(x2, y2); 277 | } else { 278 | // X1 == X2 and Y1 == -Y2 279 | // invariant(x1 == x2 && y1 == p - y2); 280 | (x3, y3, zz3, zzz3) = ecZZ_PointAtInf(); 281 | } 282 | 283 | return (x3, y3, zz3, zzz3); 284 | } 285 | 286 | /** 287 | * @dev Double a ZZ point 288 | * Uses http://hyperelliptic.org/EFD/g1p/auto-shortw-xyzz.html#doubling-dbl-2008-s-1 289 | * Handles point at infinity gracefully 290 | */ 291 | function ecZZ_double_zz(uint256 x1, uint256 y1, uint256 zz1, uint256 zzz1) 292 | internal 293 | pure 294 | returns (uint256 x3, uint256 y3, uint256 zz3, uint256 zzz3) 295 | { 296 | if (ecZZ_IsInf(zz1, zzz1)) return ecZZ_PointAtInf(); 297 | 298 | uint256 comp_U = mulmod(2, y1, p); // U = 2*Y1 299 | uint256 comp_V = mulmod(comp_U, comp_U, p); // V = U^2 300 | uint256 comp_W = mulmod(comp_U, comp_V, p); // W = U*V 301 | uint256 comp_S = mulmod(x1, comp_V, p); // S = X1*V 302 | uint256 comp_M = addmod(mulmod(3, mulmod(x1, x1, p), p), mulmod(a, mulmod(zz1, zz1, p), p), p); //M = 3*(X1)^2 + a*(zz1)^2 303 | 304 | x3 = addmod(mulmod(comp_M, comp_M, p), mulmod(minus_2modp, comp_S, p), p); // M^2 + (-2)*S 305 | y3 = addmod(mulmod(comp_M, addmod(comp_S, p - x3, p), p), mulmod(p - comp_W, y1, p), p); // M*(S+(-X3)) + (-W)*Y1 306 | zz3 = mulmod(comp_V, zz1, p); // V*ZZ1 307 | zzz3 = mulmod(comp_W, zzz1, p); // W*ZZZ1 308 | } 309 | 310 | /** 311 | * @dev Double an affine point and return as a ZZ point 312 | * Uses http://hyperelliptic.org/EFD/g1p/auto-shortw-xyzz.html#doubling-mdbl-2008-s-1 313 | * Handles point at infinity gracefully 314 | */ 315 | function ecZZ_double_affine(uint256 x1, uint256 y1) 316 | internal 317 | pure 318 | returns (uint256 x3, uint256 y3, uint256 zz3, uint256 zzz3) 319 | { 320 | if (ecAff_IsInf(x1, y1)) return ecZZ_PointAtInf(); 321 | 322 | uint256 comp_U = mulmod(2, y1, p); // U = 2*Y1 323 | zz3 = mulmod(comp_U, comp_U, p); // V = U^2 = zz3 324 | zzz3 = mulmod(comp_U, zz3, p); // W = U*V = zzz3 325 | uint256 comp_S = mulmod(x1, zz3, p); // S = X1*V 326 | uint256 comp_M = addmod(mulmod(3, mulmod(x1, x1, p), p), a, p); // M = 3*(X1)^2 + a 327 | 328 | x3 = addmod(mulmod(comp_M, comp_M, p), mulmod(minus_2modp, comp_S, p), p); // M^2 + (-2)*S 329 | y3 = addmod(mulmod(comp_M, addmod(comp_S, p - x3, p), p), mulmod(p - zzz3, y1, p), p); // M*(S+(-X3)) + (-W)*Y1 330 | } 331 | 332 | /** 333 | * @dev Convert from ZZ rep to affine rep 334 | * Assumes (zz)^(3/2) == zzz (i.e. zz == z^2 and zzz == z^3) 335 | * See https://hyperelliptic.org/EFD/g1p/auto-shortw-xyzz-3.html 336 | */ 337 | function ecZZ_SetAff(uint256 x, uint256 y, uint256 zz, uint256 zzz) 338 | internal 339 | view 340 | returns (uint256 x1, uint256 y1) 341 | { 342 | if (ecZZ_IsInf(zz, zzz)) { 343 | (x1, y1) = ecAffine_PointAtInf(); 344 | return (x1, y1); 345 | } 346 | 347 | uint256 zzzInv = pModInv(zzz); // 1 / zzz 348 | uint256 zInv = mulmod(zz, zzzInv, p); // 1 / z 349 | uint256 zzInv = mulmod(zInv, zInv, p); // 1 / zz 350 | 351 | // invariant(mulmod(FCL_pModInv(zInv), FCL_pModInv(zInv), p) == zz) 352 | // invariant(mulmod(mulmod(FCL_pModInv(zInv), FCL_pModInv(zInv), p), FCL_pModInv(zInv), p) == zzz) 353 | 354 | x1 = mulmod(x, zzInv, p); // X / zz 355 | y1 = mulmod(y, zzzInv, p); // y = Y / zzz 356 | } 357 | 358 | /** 359 | * @dev Point at infinity in ZZ rep 360 | */ 361 | function ecZZ_PointAtInf() internal pure returns (uint256, uint256, uint256, uint256) { 362 | return (0, 0, 0, 0); 363 | } 364 | 365 | /** 366 | * @dev Point at infinity in affine rep 367 | */ 368 | function ecAffine_PointAtInf() internal pure returns (uint256, uint256) { 369 | return (0, 0); 370 | } 371 | 372 | /** 373 | * @dev u^-1 mod n 374 | */ 375 | function nModInv(uint256 u) internal view returns (uint256) { 376 | return modInv(u, n, minus_2modn); 377 | } 378 | 379 | /** 380 | * @dev u^-1 mod p 381 | */ 382 | function pModInv(uint256 u) internal view returns (uint256) { 383 | return modInv(u, p, minus_2modp); 384 | } 385 | 386 | /** 387 | * @dev u^-1 mod f = u^(phi(f) - 1) mod f = u^(f-2) mod f for prime f 388 | * by Fermat's little theorem, compute u^(f-2) mod f using modexp precompile 389 | * Assume f != 0. If u is 0, then u^-1 mod f is undefined mathematically, 390 | * but this function returns 0. 391 | */ 392 | function modInv(uint256 u, uint256 f, uint256 minus_2modf) internal view returns (uint256 result) { 393 | // invariant(f != 0); 394 | // invariant(f prime); 395 | 396 | // This seems like a relatively standard way to use this precompile: 397 | // https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298/files#diff-489d4519a087ca2c75be3315b673587abeca3b302f807643e97efa7de8cb35a5R427 398 | 399 | (bool success, bytes memory ret) = (address(0x05).staticcall(abi.encode(32, 32, 32, u, minus_2modf, f))); 400 | assert(success); // precompile should never fail on regular EVM environments 401 | result = abi.decode(ret, (uint256)); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /validators/webauthn/src/WebAuthn.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Base64URL.sol"; 6 | import "./P256.sol"; 7 | 8 | /** 9 | * Helper library for external contracts to verify WebAuthn signatures. 10 | * 11 | */ 12 | library WebAuthn { 13 | /// Checks whether substr occurs in str starting at a given byte offset. 14 | function contains(string memory substr, string memory str, uint256 location) internal pure returns (bool) { 15 | bytes memory substrBytes = bytes(substr); 16 | bytes memory strBytes = bytes(str); 17 | 18 | uint256 substrLen = substrBytes.length; 19 | uint256 strLen = strBytes.length; 20 | 21 | for (uint256 i = 0; i < substrLen; i++) { 22 | if (location + i >= strLen) { 23 | return false; 24 | } 25 | 26 | if (substrBytes[i] != strBytes[location + i]) { 27 | return false; 28 | } 29 | } 30 | 31 | return true; 32 | } 33 | 34 | bytes1 constant AUTH_DATA_FLAGS_UP = 0x01; // Bit 0 35 | bytes1 constant AUTH_DATA_FLAGS_UV = 0x04; // Bit 2 36 | bytes1 constant AUTH_DATA_FLAGS_BE = 0x08; // Bit 3 37 | bytes1 constant AUTH_DATA_FLAGS_BS = 0x10; // Bit 4 38 | 39 | /// Verifies the authFlags in authenticatorData. Numbers in inline comment 40 | /// correspond to the same numbered bullets in 41 | /// https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. 42 | function checkAuthFlags(bytes1 flags, bool requireUserVerification) internal pure returns (bool) { 43 | // 17. Verify that the UP bit of the flags in authData is set. 44 | if (flags & AUTH_DATA_FLAGS_UP != AUTH_DATA_FLAGS_UP) { 45 | return false; 46 | } 47 | 48 | // 18. If user verification was determined to be required, verify that 49 | // the UV bit of the flags in authData is set. Otherwise, ignore the 50 | // value of the UV flag. 51 | if (requireUserVerification && (flags & AUTH_DATA_FLAGS_UV) != AUTH_DATA_FLAGS_UV) { 52 | return false; 53 | } 54 | 55 | // 19. If the BE bit of the flags in authData is not set, verify that 56 | // the BS bit is not set. 57 | if (flags & AUTH_DATA_FLAGS_BE != AUTH_DATA_FLAGS_BE) { 58 | if (flags & AUTH_DATA_FLAGS_BS == AUTH_DATA_FLAGS_BS) { 59 | return false; 60 | } 61 | } 62 | 63 | return true; 64 | } 65 | 66 | /** 67 | * Verifies a Webauthn P256 signature (Authentication Assertion) as described 68 | * in https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. We do not 69 | * verify all the steps as described in the specification, only ones relevant 70 | * to our context. Please carefully read through this list before usage. 71 | * Specifically, we do verify the following: 72 | * - Verify that authenticatorData (which comes from the authenticator, 73 | * such as iCloud Keychain) indicates a well-formed assertion. If 74 | * requireUserVerification is set, checks that the authenticator enforced 75 | * user verification. User verification should be required if, 76 | * and only if, options.userVerification is set to required in the request 77 | * - Verifies that the client JSON is of type "webauthn.get", i.e. the client 78 | * was responding to a request to assert authentication. 79 | * - Verifies that the client JSON contains the requested challenge. 80 | * - Finally, verifies that (r, s) constitute a valid signature over both 81 | * the authenicatorData and client JSON, for public key (x, y). 82 | * 83 | * We make some assumptions about the particular use case of this verifier, 84 | * so we do NOT verify the following: 85 | * - Does NOT verify that the origin in the clientDataJSON matches the 86 | * Relying Party's origin: It is considered the authenticator's 87 | * responsibility to ensure that the user is interacting with the correct 88 | * RP. This is enforced by most high quality authenticators properly, 89 | * particularly the iCloud Keychain and Google Password Manager were 90 | * tested. 91 | * - Does NOT verify That c.topOrigin is well-formed: We assume c.topOrigin 92 | * would never be present, i.e. the credentials are never used in a 93 | * cross-origin/iframe context. The website/app set up should disallow 94 | * cross-origin usage of the credentials. This is the default behaviour for 95 | * created credentials in common settings. 96 | * - Does NOT verify that the rpIdHash in authData is the SHA-256 hash of an 97 | * RP ID expected by the Relying Party: This means that we rely on the 98 | * authenticator to properly enforce credentials to be used only by the 99 | * correct RP. This is generally enforced with features like Apple App Site 100 | * Association and Google Asset Links. To protect from edge cases in which 101 | * a previously-linked RP ID is removed from the authorised RP IDs, 102 | * we recommend that messages signed by the authenticator include some 103 | * expiry mechanism. 104 | * - Does NOT verify the credential backup state: This assumes the credential 105 | * backup state is NOT used as part of Relying Party business logic or 106 | * policy. 107 | * - Does NOT verify the values of the client extension outputs: This assumes 108 | * that the Relying Party does not use client extension outputs. 109 | * - Does NOT verify the signature counter: Signature counters are intended 110 | * to enable risk scoring for the Relying Party. This assumes risk scoring 111 | * is not used as part of Relying Party business logic or policy. 112 | * - Does NOT verify the attestation object: This assumes that 113 | * response.attestationObject is NOT present in the response, i.e. the 114 | * RP does not intend to verify an attestation. 115 | */ 116 | function verifySignature( 117 | bytes memory challenge, 118 | bytes memory authenticatorData, 119 | bool requireUserVerification, 120 | string memory clientDataJSON, 121 | uint256 challengeLocation, 122 | uint256 responseTypeLocation, 123 | uint256 r, 124 | uint256 s, 125 | uint256 x, 126 | uint256 y, 127 | bool usePrecompiled 128 | ) internal view returns (bool) { 129 | /// @notice defer the result to the end so dummy signature can go through all verification process including p256.verifySignature 130 | bool deferredResult = true; 131 | 132 | // Check that authenticatorData has good flags 133 | if (authenticatorData.length < 37 || !checkAuthFlags(authenticatorData[32], requireUserVerification)) { 134 | deferredResult = false; 135 | } 136 | 137 | // Check that response is for an authentication assertion 138 | string memory responseType = '"type":"webauthn.get"'; 139 | if (!contains(responseType, clientDataJSON, responseTypeLocation)) { 140 | deferredResult = false; 141 | } 142 | 143 | // Check that challenge is in the clientDataJSON 144 | string memory challengeB64url = Base64URL.encode(challenge); 145 | string memory challengeProperty = string.concat('"challenge":"', challengeB64url, '"'); 146 | 147 | if (!contains(challengeProperty, clientDataJSON, challengeLocation)) { 148 | deferredResult = false; 149 | } 150 | 151 | // Check that the public key signed sha256(authenticatorData || sha256(clientDataJSON)) 152 | bytes32 clientDataJSONHash = sha256(bytes(clientDataJSON)); 153 | bytes32 messageHash = sha256(abi.encodePacked(authenticatorData, clientDataJSONHash)); 154 | 155 | // if responseTypeLocation is set to max, it means the signature is a dummy signature 156 | if (responseTypeLocation == type(uint256).max) { 157 | return P256.verifySignature(messageHash, r, s, x, y, false); 158 | } 159 | 160 | bool verified = P256.verifySignature(messageHash, r, s, x, y, usePrecompiled); 161 | 162 | if (verified && deferredResult) { 163 | return true; 164 | } 165 | return false; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /validators/webauthn/src/WebAuthnValidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {IValidator, IHook} from "kernel/interfaces/IERC7579Modules.sol"; 6 | import {MODULE_TYPE_VALIDATOR, MODULE_TYPE_HOOK} from "kernel/types/Constants.sol"; 7 | import {PackedUserOperation} from "kernel/interfaces/PackedUserOperation.sol"; 8 | import { 9 | SIG_VALIDATION_FAILED_UINT, 10 | SIG_VALIDATION_SUCCESS_UINT, 11 | ERC1271_MAGICVALUE, 12 | ERC1271_INVALID 13 | } from "kernel/types/Constants.sol"; 14 | import {WebAuthn} from "./WebAuthn.sol"; 15 | 16 | struct WebAuthnValidatorData { 17 | uint256 pubKeyX; 18 | uint256 pubKeyY; 19 | } 20 | 21 | /** 22 | * @title WebAuthnValidator 23 | * @notice This validator uses the P256 curve to validate signatures. 24 | */ 25 | contract WebAuthnValidator is IValidator { 26 | // The location of the challenge in the clientDataJSON 27 | uint256 constant CHALLENGE_LOCATION = 23; 28 | 29 | // Emitted when a bad key is provided. 30 | error InvalidPublicKey(); 31 | 32 | // Emitted when the public key of a kernel is changed. 33 | event WebAuthnPublicKeyRegistered( 34 | address indexed kernel, bytes32 indexed authenticatorIdHash, uint256 pubKeyX, uint256 pubKeyY 35 | ); 36 | 37 | // The P256 public keys of a kernel. 38 | mapping(address kernel => WebAuthnValidatorData WebAuthnValidatorData) public webAuthnValidatorStorage; 39 | 40 | /** 41 | * @notice Install WebAuthn validator for a kernel account. 42 | * @dev The kernel account need to be the `msg.sender`. 43 | * @dev The public key is encoded as `abi.encode(WebAuthnValidatorData)` inside the data, so (uint256,uint256). 44 | * @dev The authenticatorIdHash is the hash of the authenticatorId. It enables to find public keys on-chain via event logs. 45 | */ 46 | function onInstall(bytes calldata _data) external payable override { 47 | // check if the webauthn validator is already initialized 48 | if (_isInitialized(msg.sender)) revert AlreadyInitialized(msg.sender); 49 | // check validity of the public key 50 | (WebAuthnValidatorData memory webAuthnData, bytes32 authenticatorIdHash) = 51 | abi.decode(_data, (WebAuthnValidatorData, bytes32)); 52 | if (webAuthnData.pubKeyX == 0 || webAuthnData.pubKeyY == 0) { 53 | revert InvalidPublicKey(); 54 | } 55 | // Update the key (so a sstore) 56 | webAuthnValidatorStorage[msg.sender] = webAuthnData; 57 | // And emit the event 58 | emit WebAuthnPublicKeyRegistered(msg.sender, authenticatorIdHash, webAuthnData.pubKeyX, webAuthnData.pubKeyY); 59 | } 60 | 61 | /** 62 | * @notice Uninstall WebAuthn validator for a kernel account. 63 | * @dev The kernel account need to be the `msg.sender`. 64 | */ 65 | function onUninstall(bytes calldata) external payable override { 66 | if (!_isInitialized(msg.sender)) revert NotInitialized(msg.sender); 67 | delete webAuthnValidatorStorage[msg.sender]; 68 | } 69 | 70 | function isModuleType(uint256 typeID) external view override returns (bool) { 71 | return typeID == MODULE_TYPE_VALIDATOR; 72 | } 73 | 74 | function isInitialized(address smartAccount) external view override returns (bool) { 75 | return _isInitialized(smartAccount); 76 | } 77 | 78 | function _isInitialized(address smartAccount) internal view returns (bool) { 79 | return webAuthnValidatorStorage[smartAccount].pubKeyX != 0; 80 | } 81 | 82 | /** 83 | * @notice Validate a user operation. 84 | */ 85 | function validateUserOp(PackedUserOperation calldata _userOp, bytes32 _userOpHash) 86 | external 87 | payable 88 | override 89 | returns (uint256) 90 | { 91 | return _verifySignature(msg.sender, _userOpHash, _userOp.signature); 92 | } 93 | 94 | /** 95 | * @notice Verify a signature with sender for ERC-1271 validation. 96 | */ 97 | function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata data) 98 | external 99 | view 100 | returns (bytes4) 101 | { 102 | return _verifySignature(msg.sender, hash, data) == SIG_VALIDATION_SUCCESS_UINT 103 | ? ERC1271_MAGICVALUE 104 | : ERC1271_INVALID; 105 | } 106 | 107 | /** 108 | * @notice Verify a signature. 109 | */ 110 | function _verifySignature(address account, bytes32 hash, bytes calldata signature) private view returns (uint256) { 111 | // decode the signature 112 | ( 113 | bytes memory authenticatorData, 114 | string memory clientDataJSON, 115 | uint256 responseTypeLocation, 116 | uint256 r, 117 | uint256 s, 118 | bool usePrecompiled 119 | ) = abi.decode(signature, (bytes, string, uint256, uint256, uint256, bool)); 120 | 121 | // get the public key from storage 122 | WebAuthnValidatorData memory webAuthnData = webAuthnValidatorStorage[account]; 123 | 124 | // verify the signature using the signature and the public key 125 | bool isValid = WebAuthn.verifySignature( 126 | abi.encodePacked(hash), 127 | authenticatorData, 128 | true, 129 | clientDataJSON, 130 | CHALLENGE_LOCATION, 131 | responseTypeLocation, 132 | r, 133 | s, 134 | webAuthnData.pubKeyX, 135 | webAuthnData.pubKeyY, 136 | usePrecompiled 137 | ); 138 | 139 | // return the validation data 140 | if (isValid) { 141 | return SIG_VALIDATION_SUCCESS_UINT; 142 | } 143 | 144 | return SIG_VALIDATION_FAILED_UINT; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /validators/webauthn/test/WebAuthnValidator.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "../src/WebAuthnValidator.sol"; 4 | import {MockCallee, KernelTestBase} from "kernel/sdk/KernelTestBase.sol"; 5 | import {PackedUserOperation} from "kernel/interfaces/PackedUserOperation.sol"; 6 | import {ValidatorLib} from "kernel/utils/ValidationTypeLib.sol"; 7 | import {ExecLib} from "kernel/utils/ExecLib.sol"; 8 | import {IHook} from "kernel/interfaces/IERC7579Modules.sol"; 9 | import {ValidatorLib, ValidationId, ValidationMode, ValidationType} from "kernel/utils/ValidationTypeLib.sol"; 10 | import {VALIDATION_MODE_ENABLE, VALIDATION_TYPE_VALIDATOR} from "kernel/types/Constants.sol"; 11 | 12 | import {P256Verifier} from "../src/P256Verifier.sol"; 13 | import {P256} from "../src/P256.sol"; 14 | import {FCL_ecdsa_utils} from "FreshCryptoLib/FCL_ecdsa_utils.sol"; 15 | import "forge-std/console.sol"; 16 | import {LibString} from "solady/utils/LibString.sol"; 17 | import {Base64} from "solady/utils/Base64.sol"; 18 | 19 | contract WebAuthnValidatorTest is KernelTestBase { 20 | P256Verifier p256Verifier; 21 | WebAuthnValidator webAuthnValidator; 22 | // Curve order (number of points) 23 | uint256 constant n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551; 24 | 25 | uint256 x; 26 | uint256 y; 27 | 28 | uint256 challengeLocation = 23; 29 | uint256 responseTypeLocation = 1; 30 | uint256 counter = 144444; 31 | 32 | address owner; 33 | uint256 ownerKey; 34 | 35 | function _setRootValidationConfig() internal override { 36 | vm.etch( 37 | 0xc2b78104907F722DABAc4C69f826a522B2754De4, 38 | hex"60e06040523461001a57610012366100c7565b602081519101f35b600080fd5b6040810190811067ffffffffffffffff82111761003b57604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60e0810190811067ffffffffffffffff82111761003b57604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761003b57604052565b60a08103610193578060201161001a57600060409180831161018f578060601161018f578060801161018f5760a01161018c57815182810181811067ffffffffffffffff82111761015f579061013291845260603581526080356020820152833560203584356101ab565b15610156575060ff6001915b5191166020820152602081526101538161001f565b90565b60ff909161013e565b6024837f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80fd5b5080fd5b5060405160006020820152602081526101538161001f565b909283158015610393575b801561038b575b8015610361575b6103585780519060206101dc818301938451906103bd565b1561034d57604051948186019082825282604088015282606088015260808701527fffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254f60a08701527fffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551958660c082015260c081526102588161006a565b600080928192519060055afa903d15610345573d9167ffffffffffffffff831161031857604051926102b1857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160185610086565b83523d828585013e5b156102eb57828280518101031261018c5750015190516102e693929185908181890994099151906104eb565b061490565b807f4e487b7100000000000000000000000000000000000000000000000000000000602492526001600452fd5b6024827f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b6060916102ba565b505050505050600090565b50505050600090565b507fffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325518310156101c4565b5082156101bd565b507fffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc6325518410156101b6565b7fffffffff00000001000000000000000000000000ffffffffffffffffffffffff90818110801590610466575b8015610455575b61044d577f5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b8282818080957fffffffff00000001000000000000000000000000fffffffffffffffffffffffc0991818180090908089180091490565b505050600090565b50801580156103f1575082156103f1565b50818310156103ea565b7f800000000000000000000000000000000000000000000000000000000000000081146104bc577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b909192608052600091600160a05260a05193600092811580610718575b61034d57610516838261073d565b95909460ff60c05260005b600060c05112156106ef575b60a05181036106a1575050507f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5957f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2969594939291965b600060c05112156105c7575050505050507fffffffff00000001000000000000000000000000ffffffffffffffffffffffff91506105c260a051610ca2565b900990565b956105d9929394959660a05191610a98565b9097929181928960a0528192819a6105f66080518960c051610722565b61060160c051610470565b60c0528061061b5750505050505b96959493929196610583565b969b5061067b96939550919350916001810361068857507f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5937f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29693610952565b979297919060a05261060f565b6002036106985786938a93610952565b88938893610952565b600281036106ba57505050829581959493929196610583565b9197917ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd0161060f575095508495849661060f565b506106ff6080518560c051610722565b8061070b60c051610470565b60c052156105215761052d565b5060805115610508565b91906002600192841c831b16921c1681018091116104bc5790565b8015806107ab575b6107635761075f91610756916107b3565b92919091610c42565b9091565b50507f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296907f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f590565b508115610745565b919082158061094a575b1561080f57507f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29691507f4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5906001908190565b7fb01cbd1c01e58065711814b583f061e9d431cca994cea1313449bf97c840ae0a917fffffffff00000001000000000000000000000000ffffffffffffffffffffffff808481600186090894817f94e82e0c1ed3bdb90743191a9c5bbf0d88fc827fd214cc5f0b5ec6ba27673d6981600184090893841561091b575050808084800993840994818460010994828088600109957f6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c29609918784038481116104bc5784908180867fffffffff00000001000000000000000000000000fffffffffffffffffffffffd0991818580090808978885038581116104bc578580949281930994080908935b93929190565b9350935050921560001461093b5761093291610b6d565b91939092610915565b50506000806000926000610915565b5080156107bd565b91949592939095811580610a90575b15610991575050831580610989575b61097a5793929190565b50600093508392508291508190565b508215610970565b85919294951580610a88575b610a78577fffffffff00000001000000000000000000000000ffffffffffffffffffffffff968703918783116104bc5787838189850908938689038981116104bc5789908184840908928315610a5d575050818880959493928180848196099b8c9485099b8c920999099609918784038481116104bc5784908180867fffffffff00000001000000000000000000000000fffffffffffffffffffffffd0991818580090808978885038581116104bc578580949281930994080908929190565b965096505050509093501560001461093b5761093291610b6d565b9550509150915091906001908190565b50851561099d565b508015610961565b939092821580610b65575b61097a577fffffffff00000001000000000000000000000000ffffffffffffffffffffffff908185600209948280878009809709948380888a0998818080808680097fffffffff00000001000000000000000000000000fffffffffffffffffffffffc099280096003090884808a7fffffffff00000001000000000000000000000000fffffffffffffffffffffffd09818380090898898603918683116104bc57888703908782116104bc578780969481809681950994089009089609930990565b508015610aa3565b919091801580610c3a575b610c2d577fffffffff00000001000000000000000000000000ffffffffffffffffffffffff90818460020991808084800980940991817fffffffff00000001000000000000000000000000fffffffffffffffffffffffc81808088860994800960030908958280837fffffffff00000001000000000000000000000000fffffffffffffffffffffffd09818980090896878403918483116104bc57858503928584116104bc5785809492819309940890090892565b5060009150819081908190565b508215610b78565b909392821580610c9a575b610c8d57610c5a90610ca2565b9182917fffffffff00000001000000000000000000000000ffffffffffffffffffffffff80809581940980099009930990565b5050509050600090600090565b508015610c4d565b604051906020918281019183835283604083015283606083015260808201527fffffffff00000001000000000000000000000000fffffffffffffffffffffffd60a08201527fffffffff00000001000000000000000000000000ffffffffffffffffffffffff60c082015260c08152610d1a8161006a565b600080928192519060055afa903d15610d93573d9167ffffffffffffffff83116103185760405192610d73857fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160185610086565b83523d828585013e5b156102eb57828280518101031261018c5750015190565b606091610d7c56fea2646970667358221220fa55558b04ced380e93d0a46be01bb895ff30f015c50c516e898c341cd0a230264736f6c63430008150033" 39 | ); 40 | (owner, ownerKey) = makeAddrAndKey("Owner"); 41 | webAuthnValidator = new WebAuthnValidator(); 42 | (x, y) = generatePublicKey(ownerKey); 43 | rootValidation = ValidatorLib.validatorToIdentifier(webAuthnValidator); 44 | bool usePrecompiled = generateUsePrecompiled(false); 45 | rootValidationConfig = 46 | RootValidationConfig({hook: IHook(address(0)), hookData: hex"", validatorData: abi.encode(x, y, "")}); 47 | } 48 | 49 | function _rootSignDigest(bytes32 digest, bool success) internal view override returns (bytes memory data) { 50 | bytes memory authenticatorData = createAuthenticatorData(true, true, counter); 51 | unchecked { 52 | if (!success) { 53 | digest = bytes32(uint256(digest) - 1); 54 | } 55 | } 56 | string memory clientDataJSON = createClientDataJSON(digest); 57 | bytes32 webAuthnHash = generateWebAuthnHash(authenticatorData, clientDataJSON); 58 | (uint256 r, uint256 s) = generateSignature(ownerKey, webAuthnHash); 59 | bytes memory sig = abi.encode(authenticatorData, clientDataJSON, responseTypeLocation, r, s, false); 60 | return sig; 61 | } 62 | 63 | function findChallengeLocation(string memory clientDataJSON) public pure returns (uint256) { 64 | bytes memory data = bytes(clientDataJSON); 65 | for (uint256 i = 0; i < data.length - 9; i++) { 66 | // "challenge" is 9 characters long 67 | if ( 68 | data[i] == '"' // Check for quote 69 | && data[i + 1] == "c" && data[i + 2] == "h" && data[i + 3] == "a" && data[i + 4] == "l" 70 | && data[i + 5] == "l" && data[i + 6] == "e" && data[i + 7] == "n" && data[i + 8] == "g" 71 | && data[i + 9] == "e" 72 | ) { 73 | return i; // Return the index of the quote 74 | } 75 | } 76 | revert("Challenge not found"); 77 | } 78 | 79 | function generatePublicKey(uint256 privateKey) internal view returns (uint256, uint256) { 80 | return FCL_ecdsa_utils.ecdsa_derivKpub(privateKey); 81 | } 82 | 83 | function generateSignature(uint256 privateKey, bytes32 hash) internal view returns (uint256 r, uint256 s) { 84 | // Securely generate a random k value for each signature 85 | uint256 k = uint256(keccak256(abi.encodePacked(hash, block.timestamp, block.prevrandao, privateKey))) % n; 86 | while (k == 0) { 87 | k = uint256(keccak256(abi.encodePacked(k))) % n; 88 | } 89 | 90 | // Generate the signature using the k value and the private key 91 | (r, s) = FCL_ecdsa_utils.ecdsa_sign(hash, k, privateKey); 92 | 93 | // Ensure that s is in the lower half of the range [1, n-1] 94 | if (r == 0 || s == 0 || s > P256.P256_N_DIV_2) { 95 | s = n - s; // If s is in the upper half, use n - s instead 96 | } 97 | 98 | return (r, s); 99 | } 100 | 101 | function generateWebAuthnHash(bytes memory authenticatorData, string memory clientDataJSON) 102 | internal 103 | pure 104 | returns (bytes32) 105 | { 106 | bytes32 clientDataJSONHash = sha256(bytes(clientDataJSON)); 107 | return sha256(abi.encodePacked(authenticatorData, clientDataJSONHash)); 108 | } 109 | 110 | function createClientDataJSON(bytes32 challenge) internal view returns (string memory) { 111 | // string memory challengeString = LibString.toHexString( 112 | // uint256(challenge), 113 | // 32 114 | // ); 115 | string memory encodedChallenge = Base64.encode(abi.encodePacked(challenge), true, true); 116 | return string( 117 | abi.encodePacked( 118 | '{"type":"webauthn.get","challenge":"', 119 | encodedChallenge, 120 | '","origin":"https://funny-froyo-3f9b75.netlify.app","crossOrigin":false}' 121 | ) 122 | ); 123 | } 124 | 125 | function createAuthenticatorData(bool userPresent, bool userVerified, uint256 counter) 126 | internal 127 | pure 128 | returns (bytes memory) 129 | { 130 | // Flags (bit 0 is the least significant bit): 131 | // - Bit 0: User Present (UP) result. 132 | // - Bit 2: User Verified (UV) result. 133 | // Other bits and flags can be set as needed per the WebAuthn specification. 134 | bytes1 flags = bytes1(uint8(userPresent ? 0x01 : 0x00) | uint8(userVerified ? 0x04 : 0x00)); 135 | 136 | // Counter is a 32-bit unsigned big-endian integer. 137 | bytes memory counterBytes = abi.encodePacked(uint32(counter)); 138 | 139 | // Combine the flags and counter into the authenticatorData. 140 | bytes32 rpIdHash = keccak256("example.com"); // Replace "example.com" with the actual RP ID. 141 | return abi.encodePacked(rpIdHash, flags, counterBytes); 142 | } 143 | 144 | function _rootSignUserOp(PackedUserOperation memory op, bool success) 145 | internal 146 | view 147 | override 148 | returns (bytes memory) 149 | { 150 | bytes32 hash = entrypoint.getUserOpHash(op); 151 | return _rootSignDigest(hash, success); 152 | } 153 | 154 | function generateUsePrecompiled(bool usePrecompiled) internal pure returns (bool) { 155 | return usePrecompiled; 156 | } 157 | } 158 | --------------------------------------------------------------------------------