Skip to content
Closed
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
183 changes: 183 additions & 0 deletions eip-0045.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
Native STARK Proof Verification
===============================

* Author: a-shannon
* Status: Draft
* Created: 30-Apr-2026
* License: CC0
* Forking: soft-fork needed
* Reference Implementation: [sigmastate-interpreter PR #1116](https://github.com/ergoplatform/sigmastate-interpreter/pull/1116)

Description
-----------

This EIP proposes adding a native `VerifyStark` opcode to ErgoScript that enables on-chain verification of STARK (Scalable Transparent Arguments of Knowledge) proofs. This allows zero-knowledge virtual machine execution proofs — from systems like RISC Zero, SP1, and Valida — to be verified as a first-class L1 consensus operation.


Background And Motivation
-------------------------

STARK proof verification involves finite field arithmetic over extension fields (BabyBear to Ext16), Poseidon hash evaluations, FRI (Fast Reed-Solomon IOP) folding with iFFT, Merkle tree path verification, and DEEP-ALI quotient polynomial checks.

These operations **cannot** be expressed in ErgoScript within a single block's JIT budget using existing opcodes. Even with the `UnsignedBigInt` operations from EIP-0050, the overhead of interpreting field arithmetic through generic opcodes would exceed the block cost limit by orders of magnitude.

A native opcode encapsulates the entire STARK verification pipeline in optimized Scala, provides preemptive AOT cost estimation from proof metadata to prevent DoS attacks, returns a simple Boolean, and is extensible via a `vmType` registry to support multiple zkVM architectures.

### Use Cases

* **Privacy pools**: Verify that a withdrawal proof was computed correctly without revealing the depositor's identity
* **Rollups**: Verify L2 state transitions on Ergo L1
* **Verifiable computation**: Prove arbitrary program execution (RISC-V programs) on-chain
* **Cross-chain bridges**: Verify proofs of state from other chains


Native VerifyStark Opcode
-------------------------

### Signature

```
VerifyStark(
proofChunks: Coll[Coll[Byte]], // Segmented proof data
publicInputs: Coll[Byte], // Public inputs to the computation
imageId: Coll[Byte], // Hash of the zkVM program image
vmType: Int, // AIR registry identifier
costParams: Coll[Int] // [Q, D, ...] cost derivation parameters
) => Boolean
```

### Parameters

* `proofChunks` (`Coll[Coll[Byte]]`) — STARK proof data, split into chunks to avoid SigmaSerializer OOM on large proofs. Chunks are concatenated by the verifier.
* `publicInputs` (`Coll[Byte]`) — Public inputs to the verified computation (e.g., transaction hash, nullifier).
* `imageId` (`Coll[Byte]`) — 32-byte Blake2b hash identifying the zkVM program whose execution is being verified.
* `vmType` (`Int`) — Selects the AIR (Algebraic Intermediate Representation) constraint system. `0` = Fibonacci test AIR; future values map to RISC-V, Cairo, etc.
* `costParams` (`Coll[Int]`) — Parameters for AOT cost derivation: `[Q, D]` where Q = number of FRI queries and D = Merkle tree depth.

### Return Value

* `true` — the STARK proof is valid for the given public inputs and program image
* `false` — the proof is invalid, malformed, or the vmType is unrecognized

The opcode never throws exceptions to user-level ErgoScript. Invalid proofs return `false`. Only `CostLimitException` (budget overflow) propagates upward to the block evaluator.


OpCode Assignment
-----------------

```
VerifyStarkCode = newOpCode(73) // byte value: 112 + 73 = 185 (0xB9)
```

This claims one slot from the reserved `73-80` gap in the opcode space.

**Important**: The opcode space is fully saturated. `XorOfCode = newOpCode(143)` maps to byte `0xFF`. Any index >= 144 would overflow the signed-byte range and collide with existing opcodes. New opcodes MUST use reserved slots.


AOT Cost Model
--------------

The evaluation follows a three-phase strategy designed to prevent DoS attacks:

### Phase 1: Preemptive Cost (O(1) scalars only)

```
aotCost = BASE_COST + (Q * PER_QUERY_COST) + (Q * D * PER_MERKLE_LAYER_COST)
```

This cost is charged before any byte arrays are evaluated. If it exceeds the block budget, `CostLimitException` is thrown immediately.

### Phase 2: Data Loading (after cost secured)

Only after Phase 1 succeeds, the heavy `proofChunks`, `publicInputs`, and `imageId` byte arrays are evaluated.

### Phase 3: Byte Ingestion + Verification

```
ingestionCost = PerItemCost(base=10, perChunk=1, chunkSize=1024) * totalBytes
```

### Preliminary Cost Constants

| Constant | Value (JIT units) | Rationale |
|---|---|---|
| BASE_COST | 5,000 | Transcript setup, OOD derivation, DEEP-ALI |
| PER_QUERY_COST | 50 | iFFT-8 folding + Horner evaluation per query |
| PER_MERKLE_LAYER_COST | 10 | Hash verification per query per Merkle layer |

These constants are preliminary and require JMH benchmark calibration on the Foundation reference machine.

### Example: Standard Proof (Q=35, D=16)

```
AOT cost = 5000 + 35*50 + 35*16*10 = 12,350 JIT units = 1,235 block cost
Block budget = 1,000,000 block cost
Utilization = 0.12%
```

### Example: DoS Attack (Q=1000, D=1000)

```
AOT cost = 5000 + 1000*50 + 1000*1000*10 = 10,055,000 JIT units
Block budget = 1,000,000 block cost (= 10,000,000 JIT)
Result: CostLimitException thrown BEFORE byte arrays loaded
```


STARK Verifier Architecture
---------------------------

The verifier is implemented in pure Scala with zero external dependencies, located in `org.ergo.stark`:

* `BabyBearField` — Prime field p = 2^31 - 2^27 + 1
* `QuadraticTower` — Ext4 to Ext16 tower via x^2 - 11 (quadratic non-residue), Karatsuba multiplication
* `Poseidon1BabyBear` — Sponge hash with BabyBear-optimized S-box (x^7), width=16, rate=8, zero-allocation
* `MerkleVerifier` — Batch Merkle path verification with zero-sibling deduplication
* `FiatShamirTranscript` — QROM-safe Fiat-Shamir with rejection sampling and anti-grinding
* `FriVerifier` — FRI with folding factor 8 (Radix-8), unrolled iFFT butterflies, zero Ext16 inversions
* `DeepAliVerifier` — DEEP-ALI quotient verification with Montgomery batch inverse
* `StarkVerifier` — Top-level orchestrator
* `AirRegistry` — Pluggable AIR constraint registry for vmType dispatch

### Security Level

With Q=35 FRI queries and D=16 Merkle depth: 132-bit FRI soundness + 155-bit DEEP-ALI soundness, exceeding the 128-bit minimum requirement for institutional custody applications.


Serialization Format
--------------------

`VerifyStarkSerializer` serializes the 5 children in strict consensus order:

```
[proofChunks][publicInputs][imageId][vmType][costParams]
```

Each child is serialized using the standard `ValueSerializer.serialize/deserialize` protocol.


Security Considerations
-----------------------

### DoS Resistance
The AOT preemptive costing model (Phase 1) ensures that an attacker cannot force expensive verification without paying the corresponding JIT cost upfront. The cost is computed from O(1) integer parameters, not from proof data.

### Proof Inflation
Chunked `proofChunks` prevents memory exhaustion. The byte ingestion cost (Phase 3) ensures that padding and spam attacks are economically infeasible.

### CostLimitException Propagation
The catch block in `eval()` explicitly re-throws `CostLimitException`. Without this, a generic catch-all would silently defeat the budget enforcement.

### Soft-Fork Compatibility
Invalid proofs, unrecognized `vmType` values, and malformed data all return `false` without throwing user-visible exceptions. This ensures forward compatibility: older nodes treat unknown STARK proofs as invalid (conservative rejection), while upgraded nodes verify them.


Backward Compatibility
----------------------

This EIP requires a soft fork (new opcode activation via protocol version increment):

* **Pre-activation nodes**: Will not recognize `VerifyStarkCode (0xB9)` and will reject transactions containing it (standard unknown-opcode behavior).
* **Post-activation nodes**: Will evaluate `VerifyStark` with the full STARK verifier engine.

The opcode uses reserved slot 73 from the `73-80` gap, which was explicitly set aside for future consensus extensions.