Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
aa88f89
fix: audit batch — enable-mode install bypass (H), factory-nonce repl…
leekt Aug 5, 2026
ef2bb89
feat: remove Hook
leekt Aug 5, 2026
7dd9c00
feat: remove enable mode from ERC-1271
leekt Aug 5, 2026
274affb
feat: introduce ScopedExecutionHook
leekt Aug 5, 2026
3815fec
test: remove Hook and add ScopedExecutionHook
leekt Aug 5, 2026
8b08d55
test: remove enable mode from ERC-1271
leekt Aug 5, 2026
be3e010
fix: gate unhooked fallback selectors to entry point only
leekt Aug 8, 2026
62db998
refactor: centralize sentinel constants
leekt Aug 8, 2026
b8861c6
fix: restrict raw ERC-1271 mode to the fallback signer
leekt Aug 26, 2026
ded1250
fix: revoke module authority before the onUninstall callback
leekt Aug 26, 2026
498a61e
fix: protect current root permission policies from uninstall
leekt Sep 2, 2026
bac0d4b
fix: reject wrong-type and not-installed module uninstalls
leekt Sep 2, 2026
d92c9a2
fix: bind module install data decoding to declared initData bounds
leekt Sep 2, 2026
dc7a748
fix: revoke all root authorization state before teardown callbacks
leekt Sep 2, 2026
b42fa9a
fix: restrict executeUserOp to the entry point and forbid nesting it
leekt Sep 2, 2026
ba83ece
test: not-installed validator uninstall reverts per TOB-KERNEL-12
leekt Sep 2, 2026
104f1d6
docs: record Trail of Bits PR #60 response and TOB-11 rationale
leekt Sep 2, 2026
406dc17
docs: document in-bundle revocation finality limitation (TOB-KERNEL-3)
leekt Sep 2, 2026
fb17654
docs: simplify TOB-11 executor rationale to EOA executors
leekt Sep 2, 2026
bda40bb
docs: acknowledge TOB-KERNEL-9 instead of fixing
leekt Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG_AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Added support for ERC-4337 EntryPoint version 0.9.
- Gas snapshot updates reflecting v0.9 optimizations (reduced gas costs across all test scenarios)
- **Breaking Change:** UserOperation hash calculation has been changed in EntryPoint v0.9
- **Files:** `foundry.toml`, `remappings.txt`, `soldeer.lock`, `test/utils/EntryPointLib.sol`, `test/KernelUserOpTest.sol`, `test/KernelValidatorTest.sol`
- **EntryPoint Address:** `0x43370900c8de573dB349BEd8DD53b4Ebd3Cce709`
- **EntryPoint Address:** `0x433709009B8330FDa32311DF1C2AFA402eD8D009`
- **Commits:** 977ca07, aa91ef1, 110c7af, 3e72921
- **Note:** The module type ID was updated from 8 to 10 for `MODULE_TYPE_STATELESS_VALIDATOR_WITH_SENDER` as part of this upgrade
- you can find the release docs in [here](https://docs.google.com/document/d/1RKkKZsP1eYkOoBEkzJ1vWRK_bcWXaewoGPzawMjsleM/edit?usp=drivesdk), please do note that this document is not in public yet
Expand Down
121 changes: 49 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Kernel v4

ERC-4337 / ERC-7702 modular smart account with pluggable validation, execution, and hook modules. Implements [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) for standardized module interfaces.
ERC-4337 / ERC-7702 modular smart account with pluggable validation and execution modules. Implements [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) for standardized module interfaces.

## Key Features

### Modular Architecture (ERC-7579)

Six pluggable module types that can be installed and uninstalled at runtime:
Six supported module types can be installed and uninstalled at runtime:

| Type | Role |
|------|------|
| ------ | ------ |
| **Validator** | Validates UserOps and signatures — owns a nonce key namespace |
| **Executor** | Calls `executeFromExecutor` to perform actions on behalf of the account |
| **Fallback** | Extends the account with new function selectors (call or delegatecall) |
| **Hook** | Pre/post execution checks on validators, executors, and fallback selectors |
| **Policy** | Part of a permission — enforces rules (e.g. spending limits, target allowlists) |
| **Signer** | Part of a permission — provides the signature verification (e.g. passkey, multisig) |
| **Signer** | Part of a permission — provides signature verification (e.g. passkey, multisig) |
| **Scoped Execution Hook** | Optional pre/post checks scoped to one validation, executor, or selector (type 11) |

### Permission System

Expand All @@ -35,29 +35,28 @@ Install modules atomically with the first UserOp — no separate setup transacti

The 32-byte ERC-4337 nonce encodes which validator to use, giving each validator/permission its own nonce namespace. See [Data Encoding > UserOp Nonce](#userop-nonce) for the full layout.

### Hook System
### Scoped Execution Hooks

Hooks provide pre/post execution checks. They bind to validators, executors, and fallback selectors independently:
An optional type-11 scoped execution hook runs before and after execution in one of three scopes: validation, executor, or selector. Hooked non-root validations are routed through `executeUserOp`; root validations bypass hooks. Executor hooks wrap `executeFromExecutor`, and selector hooks wrap fallback selector dispatch. Selector hooks apply only to calls that reach Kernel's fallback; native Kernel function dispatch bypasses them, and built-in token-receiver selectors cannot be installed as fallback targets.

- **Validator hook** — Runs around `executeUserOp` when a non-root validator with a hook is used
- **Executor hook** — Runs around `executeFromExecutor` for any installed executor
- **Fallback hook** — Runs around fallback selector dispatch
**Fallback access control:** a fallback selector with no scoped execution hook installed is callable only by the EntryPoint. Installing a selector-scoped execution hook makes the selector publicly callable, with the hook gating direct access via `preCheck`/`postCheck`.

Two sentinel values: `address(0)` = not installed, `address(1)` = installed with no hook.
`preCheck` and `postCheck` receive the same Kernel-generated `bytes32 id`. The ID layout is `[bytes1 scope][target][zero padding]`, where the target is a 21-byte ValidationId, 20-byte executor address, or 4-byte selector. `Utils.sol` exposes generation and decoding helpers for every scope.

### Signature Verification (ERC-1271 / ERC-7739)

Three signature modes for `isValidSignature`:

1. **Raw** — Direct hash signing (only on `Kernel7702` where the EOA is the signer)
2. **Chain-specific nested EIP-712** — Wraps the hash in a `TypedDataSign` struct bound to chain ID
3. **Replayable nested EIP-712** — Same wrapping but without chain ID, valid across chains

All modes support both validator-based and permission-based signature verification, selected by the first 21 bytes of the signature.
Structured signatures select root, validator, or permission validation through the leading `vType` and optional ID. Raw Kernel7702 signatures have no validation header.

### Standards

| Standard | Support |
|----------|---------|
| ---------- | --------- |
| [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) | Account abstraction via EntryPoint v0.9 |
| [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) | Modular smart account interfaces |
| [ERC-7702](https://eips.ethereum.org/EIPS/eip-7702) | EOA code delegation (`Kernel7702`) |
Expand All @@ -79,7 +78,7 @@ The 32-byte ERC-4337 nonce encodes the validation mode, type, and identifier:
**vMode** (ValidationMode flags):

| Value | Meaning |
|-------|---------|
| ------- | --------- |
| `0x00` | Standard — chain-specific, no inline install |
| `0x08` | Enable — install modules inline, chain-specific enable signature |
| `0x0C` | Enable + replayable enable signature |
Expand All @@ -90,7 +89,7 @@ The 32-byte ERC-4337 nonce encodes the validation mode, type, and identifier:
**vType** (ValidationType):

| Value | Meaning | vId contains |
|-------|---------|-------------|
| ------- | --------- | ------------- |
| `0x00` | Root / Fallback | Ignored (uses stored root) |
| `0x01` | Validator | 20-byte validator address |
| `0x02` | Permission | 4-byte PermissionId (left-aligned, rest zero) |
Expand Down Expand Up @@ -143,44 +142,19 @@ If the enable-replayable flag (0x04) is set, the digest uses the chain-agnostic
After ERC-6492 unwrapping, the signature is parsed as:

```
| 1 byte | 1 byte | N bytes | remaining bytes |
| vMode | vType | vId | inner signature |
| 1 byte | N bytes | remaining bytes |
| vType | vId | inner signature |
```

Where N depends on vType:

| vType | N | vId content |
|-------|---|-------------|
| `0x00` (root) | 0 | Uses stored root, inner = `signature[2:]` |
| `0x01` (validator) | 20 | Validator address, inner = `signature[22:]` |
| `0x02` (permission) | 4 | PermissionId, inner = `signature[6:]` |

#### Standard Mode (no enable flag)

The inner signature is verified via `_verifySignature` against the installed validator or permission, same as UserOp standard mode.

#### Enable Mode for ERC-1271

Since `isValidSignature` is a `view` function, enable mode works differently than in UserOps — it **cannot** modify state (no module installation, no nonce increment). Instead it:

1. Verifies the install signature is valid (same digest as UserOp enable mode)
2. Checks the nonce is correct (view-only, no increment)
3. Uses **stateless** verification — finds the validator/permission modules inside the `packages` array and calls `IStatelessValidatorWithSender.validateSignatureWithDataWithSender` instead of the normal installed module

The inner signature format is the same `EnableModeSignature`:
| ------- | --- | ------------- |
| `0x00` (root) | 0 | Uses stored root, inner = `signature[1:]` |
| `0x01` (validator) | 20 | Validator address, inner = `signature[21:]` |
| `0x02` (permission) | 4 | PermissionId, inner = `signature[5:]` |

```
abi.encode(EnableModeSignature({
nonce: uint256,
packages: Install[],
enableSignature: bytes, // root validator's signature over the install digest
userOpSignature: bytes // verified statelessly against modules in packages
}))
```

For permission-based enable mode, `userOpSignature` is a `PermissionSignature` — one signature per policy/signer found in the packages with the matching PermissionId.

> **Note:** vType cannot be root (`0x00`) in enable mode — it must specify an explicit validator or permission.
The inner signature is verified via `_verifySignature` against the installed validator or permission. ERC-1271 has no validation-mode byte and does not support enable mode; validation modes remain part of ERC-4337 UserOperation nonces only.

#### Nested EIP-712 Wrapping

Expand Down Expand Up @@ -215,21 +189,15 @@ abi.encode(InstallModuleDataFormat({
#### internalData for Install

| Module Type | internalData format |
|-------------|---------------------|
| Validator (1) | `[bytes20 hookAddress][bytes4 selector₁][bytes4 selector₂]...` |
| Executor (2) | `[bytes20 hookAddress]` |
| Fallback (3) | `[bytes4 selector][bytes1 callType][bytes20 hookAddress]` |
| Hook (4) | Ignored (empty OK) |
| ------------- | --------------------- |
| Validator (1) | `[bytes4 selector₁][bytes4 selector₂]...` |
| Executor (2) | Empty (required) |
| Fallback (3) | Exactly `[bytes4 selector][bytes1 callType]` |
| Policy (5) | `[bytes4 permissionId]` |
| Signer (6) | `[bytes4 permissionId][bytes20 hookAddress][bytes4 selector₁]...` |

**hookAddress** sentinel values:
| Signer (6) | `[bytes4 permissionId][bytes4 selector₁]...` |
| Scoped Execution Hook (11) | `[bytes1 scope][target]` (validation: 21-byte ValidationId; executor: 20-byte address; selector: 4-byte selector) |

| Address | Meaning |
|---------|---------|
| `address(0)` | Not installed / entry-point-only (for fallback: only EntryPoint can call) |
| `address(1)` | Installed with no hook |
| Other | Hook contract address (must be installed as hook module first) |
Execution-hook scopes are `0x01` for validation, `0x02` for executor, and `0x03` for selector.

**callType** for fallback (type 3):

Expand All @@ -241,13 +209,13 @@ abi.encode(InstallModuleDataFormat({
#### internalData for Uninstall

| Module Type | internalData format |
|-------------|---------------------|
| ------------- | --------------------- |
| Validator (1) | Ignored |
| Executor (2) | Ignored |
| Fallback (3) | `[bytes4 selector]` (first 4 bytes used) |
| Hook (4) | Ignored |
| Executor (2) | Empty (required) |
| Fallback (3) | Exactly `[bytes4 selector]` |
| Policy (5) | `[bytes4 permissionId]` — must uninstall in LIFO order (last installed first) |
| Signer (6) | `[bytes4 permissionId]` — all policies must be uninstalled first |
| Signer (6) | `[bytes4 permissionId]` — all policies and its scoped execution hook must be removed first |
| Scoped Execution Hook (11) | `[bytes1 scope][target]` (same format as installation) |

### Batch Install via `Install[]`

Expand All @@ -268,23 +236,22 @@ Each `Install` struct:

```solidity
struct Install {
uint256 moduleType; // 1-6
uint256 moduleType; // 1, 2, 3, 5, 6, or 11
address module; // module contract address
bytes moduleData; // forwarded to onInstall
bytes internalData; // kernel config (same format as table above)
}
```

**Permission install order**: When installing a permission, all policies (type 5) for that PermissionId must come first, followed by exactly one signer (type 6) with the same PermissionId. The signer finalizes the permission. Multiple permissions can be installed in a single batch — just ensure each permission's policies+signer are grouped together.
**Permission install order**: policies (type 5) come first, followed by exactly one signer (type 6), then an optional scoped execution hook (type 11), all sharing the same PermissionId. The hook targets the permission's full 21-byte ValidationId and requires the signer-completed permission to exist. Validator, executor, and selector hooks are installed after their respective targets.

## Architecture

```
Kernel (abstract)
├── ModuleManager
│ ├── ValidationManager — Validator/permission lifecycle, enable-mode, nonce mgmt
│ ├── ExecutorManager — Executor install/uninstall with hook binding
│ ├── HookManager — Hook install/uninstall, pre/post check dispatch
│ ├── ExecutorManager — Executor install/uninstall state
│ └── SelectorManager — Fallback handler routing by function selector
├── ExecutionManager — ERC-7579 execution modes (single/batch/delegatecall)
└── ERC1271 — ERC-1271 / ERC-7739 signature verification
Expand All @@ -304,11 +271,10 @@ Supporting contracts:
All storage uses [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) namespaced slots to avoid collisions across modules and upgrades:

| Manager | Slot |
|---------|------|
| --------- | ------ |
| ValidationManager | `keccak256("kernel.v4.validation") - 1` |
| ModuleManager | `keccak256("kernel.v4.module") - 1` |
| ExecutorManager | `keccak256("kernel.v4.executor") - 1` |
| HookManager | `keccak256("kernel.v4.hook") - 1` |
| SelectorManager | `keccak256("kernel.v4.selector") - 1` |

## Project Structure
Expand Down Expand Up @@ -396,7 +362,7 @@ open coverage/index.html
## Dependencies

| Package | Version |
|---------|---------|
| --------- | --------- |
| [Solady](https://github.com/Vectorized/solady) | 0.1.26 |
| [account-abstraction](https://github.com/eth-infinitism/account-abstraction) | v0.9.0 |
| [OpenZeppelin Contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) | 5.4.0 |
Expand All @@ -415,6 +381,17 @@ Key settings in `foundry.toml`:

See [CHANGELOG_AUDIT.md](./CHANGELOG_AUDIT.md) for the full audit changelog covering all changes since the last audit.

### Known limitations

- **Revocation finality within a bundle** (TOB-KERNEL-3, acknowledged): Kernel checks that the
selected validator or root is installed during `validateUserOp` and does not re-verify it at
execution time. Because the EntryPoint validates every operation in a bundle before executing any
of them, a validator uninstall or root replacement that executes earlier in a `handleOps` bundle
does not invalidate a later operation in that same bundle, which was already validated against
the now-revoked authority. This grants no authority beyond what the revoked key already held and
is equivalent to the revocation being ordered after the attacker's transaction; treat revocations
of compromised keys as racing the key until the revoking transaction is mined.

## License

MIT
Loading
Loading