Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions EIPS/eip-9999.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ This EIP solves this problem by adding **STARK-based aggregation**. Instead of a

### New Frame Types

This EIP introduces two new frame modes for [EIP-8141](./eip-8141.md) frame transactions:
This EIP introduces one new frame mode for [EIP-8141](./eip-8141.md) frame transactions:

#### Dependency Verification Frame (`DEP_VERIFY_FRAME_MODE`)

We modify EIP-8141 to also allow frames with mode `DEP_VERIFY_FRAME_MODE`, in addition to all previously allowed modes. A frame with mode `DEP_VERIFY_FRAME_MODE` is a **dependency verification frame**. It declares a set of **dependencies** as `(scheme, data_hash, verification_key)` triples. These triples are not executed as EVM code; instead, they are recorded as dependencies that must be proven valid by the recursive STARK in the block.
We modify EIP-8141 to also allow frames with mode `DEP_VERIFY_FRAME_MODE`, in addition to all previously allowed modes. Concretely, EIP-8141's per-frame transaction-validity rule `assert frame.mode < 3` is amended to `assert frame.mode < 4` (equivalently `frame.mode <= DEP_VERIFY_FRAME_MODE`), so a dependency verification frame is not rejected as an unknown mode. A frame with mode `DEP_VERIFY_FRAME_MODE` is a **dependency verification frame**. It declares a set of **dependencies** as `(scheme, data_hash, verification_key)` triples. These triples are not executed as EVM code; instead, they are recorded as dependencies that must be proven valid by the recursive STARK in the block.

The **payload encoding** is a simple `96*k` byte concatenation of `k` triples, each consisting of:

Expand Down Expand Up @@ -99,9 +99,9 @@ dependencies(block) = [

### Transaction Execution Visibility

During EVM execution, the transaction's dependencies are made available through the existing [EIP-8141](./eip-8141.md) `FRAMEPARAM`, `FRAMEDATASIZE`, and `FRAMEDATACOPY` instructions.
During EVM execution, the transaction's dependencies are made available through the existing [EIP-8141](./eip-8141.md) `FRAMEPARAM` and `FRAMEDATACOPY` instructions.

Contracts can iterate over all frames in the transaction using `FRAMEPARAM` to identify `DEP_VERIFY_FRAME_MODE` frames, then use `FRAMEDATASIZE` and `FRAMEDATACOPY` to read the dependency data from each frame's `data` field.
Contracts can iterate over all frames in the transaction using `FRAMEPARAM` to identify `DEP_VERIFY_FRAME_MODE` frames, then use `FRAMEPARAM(0x04, i)` (length) and `FRAMEDATACOPY` to read the dependency data from each frame's `data` field.

Specifically:

Expand Down Expand Up @@ -133,7 +133,7 @@ recursive_stark = [stark_proof, block_deps_hash]
```
block_deps_hash = hash(concat([
int_to_bytes32_big_endian(t.scheme) + t.data_hash + t.verification_key
for t in get_dependencies(block)
for t in dependencies(block)
]))
```

Expand Down Expand Up @@ -174,10 +174,10 @@ for i in range(len(recursive_proofs)):
all_deps.extend(inner_deps)

# Remove discards and duplicates
discard_hashes = set(get_deps_hash([d]) for d in discards)
discard_set = set(discards)
filtered_deps = []
for dep in all_deps:
if dep not in filtered_deps and dep not in discard_hashes:
if dep not in filtered_deps and dep not in discard_set:
filtered_deps.append(dep)

# Verify final deps hash matches public input
Expand All @@ -200,10 +200,12 @@ To enable unlimited-depth recursion, we need to pass the `AGGREGATED_VK` in as a

A block is valid if and only if:

1. The `block_deps_hash` in the recursive STARK header entry matches the result of hashing `get_dependencies(block)`
1. The `block_deps_hash` in the recursive STARK header entry matches the result of hashing `dependencies(block)`
2. The recursive STARK proof verifies successfully against `block_deps_hash` and the fixed protocol-level `AGGREGATED_VK`.
3. All transactions satisfy the normal (EIP-8141 or other) validity rules.

`AGGREGATED_VK` is a fixed protocol constant and is never chosen by users. It is passed as a public input to enable unlimited-depth recursion, but block validity rule 2 pins the outer proof to the protocol value, and the identical `AGGREGATED_VK` must be used as the verification key and public input for every nested recursive proof at every depth (this is why a recursive proof's public inputs are `[deps_hash, AGGREGATED_VK]`, whereas a leaf verification takes only `data_hash`). The per-dependency leanSTARK verification keys inside the `(scheme, data_hash, verification_key)` triples, referenced in Security Considerations, are distinct from `AGGREGATED_VK`.

### Mempool Wrapper Object

At the mempool level, transactions with dependency verification frames are wrapped in **wrapper objects** that are broadcast once per `AGGREGATION_INTERVAL`. Each wrapper contains a **list of transactions** and either their direct dependencies or a single recursive STARK proving all dependencies.
Expand All @@ -220,7 +222,7 @@ content = if mode == 0: [deps, proofs]

Where:

- `transactions` = list of transactions in the wrapper. For transactions that were already broadcast in a previous wrapper, their hash is used instead of the full transaction to save space.
- `transactions` = list of transactions in the wrapper. For transactions that were already broadcast in a previous wrapper, their hash is used instead of the full transaction to save space. A node can validate a wrapper only once it holds the full transaction bytes for every referenced transaction (required to recompute each transaction's dependencies); a wrapper with an unresolved transaction hash is not yet validatable or forwardable.
- `deps` = concatenation of all `(scheme, data_hash, verification_key)` triples from all transactions in the wrapper
- `proofs` = list of individual proofs corresponding to each dependency in `deps`
- `recursive_stark` = a single recursive STARK proving validity of all deps, with public input `hash(deps)`
Expand All @@ -234,10 +236,11 @@ Where:

**Mode 1 (Recursive STARK)**: When a mempool node receives a wrapper with `mode == 1`, they must verify that:

1. The recursive STARK's public input `deps_hash` equals `hash(deps)`
2. The recursive STARK is valid (proves all deps are valid)
3. The total number of leanSPHINCS deps is `<= MAX_LEANSIG_DEPS_PER_WRAPPER`
4. The total number of leanSTARK deps is `<= MAX_LEANSTARK_DEPS_PER_WRAPPER`
1. The `deps` list is the concatenation of all dependencies from each transaction in `transactions` (identical to the mode 0 check)
2. The recursive STARK's public input `deps_hash` equals `hash(deps)`
3. The recursive STARK is valid (proves all deps are valid)
4. The total number of leanSPHINCS deps is `<= MAX_LEANSIG_DEPS_PER_WRAPPER`
5. The total number of leanSTARK deps is `<= MAX_LEANSTARK_DEPS_PER_WRAPPER`

Each wrapper contains exactly one STARK in mode 1, and one STARK per leanSTARK dependency in mode 0. Per `AGGREGATION_INTERVAL`, a mempool node broadcasts exactly one wrapper containing all currently active transactions, with exactly one STARK covering all their dependencies.

Expand All @@ -264,6 +267,8 @@ If a node anticipates that it will not have enough proving time to aggregate in

Nodes may also alternate between adding one object at a time from (3) and (4).

If a node cannot produce a new recursive wrapper before the next aggregation interval, it SHOULD forward the valid wrappers it received unchanged, rather than blocking propagation. Recursive folding is a bandwidth optimization, not a precondition for forwarding: a node always verifies a wrapper before forwarding it (per the mode 0 / mode 1 rules above), but a node that cannot fold in time still propagates what it has already verified.

#### Mempool-Level Limits

To prevent DoS attacks, the following mempool-level limits apply to recursive STARK aggregation:
Expand Down