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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ const client = createPublicClient({ chain: base, transport: http() })

- [ZK Prover](https://docs.base.org/specifications/base-protocol/proofs/zk-prover): Specification of the ZK prover, an offchain service that uses SP1 programs to produce permissionless proofs for checkpoint proposals and disputes.

- [Proof Contracts](https://docs.base.org/specifications/base-protocol/proofs/proof-contracts): Specification of the onchain contracts that verify proof material, track game state, and release withdrawals for the Azul proof system.
- [Proof Contracts](https://docs.base.org/specifications/base-protocol/proofs/proof-contracts): Specification of the onchain contracts that verify proof material, track game state, and release withdrawals for the Base proof system.

- [Design Goals](https://docs.base.org/specifications/base-protocol/design-goals): Design philosophy and lineage of the Base Chain protocol specification.

Expand Down
2 changes: 1 addition & 1 deletion docs/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@

- [ZK Prover](https://docs.base.org/specifications/base-protocol/proofs/zk-prover): Specification of the ZK prover, an offchain service that uses SP1 programs to produce permissionless proofs for checkpoint proposals and disputes.

- [Proof Contracts](https://docs.base.org/specifications/base-protocol/proofs/proof-contracts): Specification of the onchain contracts that verify proof material, track game state, and release withdrawals for the Azul proof system.
- [Proof Contracts](https://docs.base.org/specifications/base-protocol/proofs/proof-contracts): Specification of the onchain contracts that verify proof material, track game state, and release withdrawals for the Base proof system.

- [Design Goals](https://docs.base.org/specifications/base-protocol/design-goals): Design philosophy and lineage of the Base Chain protocol specification.

Expand Down
219 changes: 111 additions & 108 deletions docs/specifications/base-protocol/proofs/proof-contracts.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Proof Contracts"
description: "Specification of the onchain contracts that verify proof material, track game state, and release withdrawals for the Azul proof system."
description: "Specification of the onchain contracts that verify proof material, track game state, and release withdrawals for the Base proof system."
---

The proof contracts turn offchain proof material into onchain checkpoint games. A game claims an
Expand All @@ -17,11 +17,13 @@
- `ZKVerifier`
- `TEEVerifier`
- `TEEProverRegistry`
- `NitroEnclaveVerifier`
- `NitroValidator`
- `CertManager`
- `P384Verifier`

## Contract Graph

```mermaid Contract Graph lines wrap expandable

Check warning on line 26 in docs/specifications/base-protocol/proofs/proof-contracts.mdx

View workflow job for this annotation

GitHub Actions / Docs Style / Conformance

Docs style

[codeblock/highlight] Consider `highlight={}` to draw attention to key lines

Check warning on line 26 in docs/specifications/base-protocol/proofs/proof-contracts.mdx

View workflow job for this annotation

GitHub Actions / Docs Style / Conformance

Docs style

[codeblock/highlight] Consider `highlight={}` to draw attention to key lines
flowchart TB
Factory[DisputeGameFactory] -->|clones| Game[AggregateVerifier game]
Game -->|validates parent and finality| ASR[AnchorStateRegistry]
Expand All @@ -29,16 +31,19 @@
Game -->|TEE proofs| TEEVerifier[TEEVerifier]
Game -->|ZK proofs| ZKVerifier[ZKVerifier]
TEEVerifier -->|signer and proposer checks| Registry[TEEProverRegistry]
Registry -->|attestation proof| Nitro[NitroEnclaveVerifier]
Registry -->|signed attestation and hints| Nitro[NitroValidator]
Registry -->|current TEE_IMAGE_HASH| Factory
ZKVerifier -->|SP1 proof| SP1[SP1 verifier gateway]
Nitro -->|RISC Zero or SP1 proof| Coprocessor[ZK verifier contract]
Nitro -->|cached certificate chain| Certs[CertManager]
Nitro -->|attestation signature| P384[P384Verifier]
Certs -->|certificate signatures| P384
```

`DisputeGameFactory`, `AnchorStateRegistry`, and `DelayedWETH` are proxied system contracts.
`AggregateVerifier` is deployed as an implementation and cloned by the factory with immutable
arguments. `TEEVerifier`, `ZKVerifier`, `TEEProverRegistry`, and `NitroEnclaveVerifier` are
standalone verifier and registry contracts referenced by the game implementation.
`DisputeGameFactory`, `AnchorStateRegistry`, `DelayedWETH`, and `TEEProverRegistry` are proxied
system contracts. `AggregateVerifier` is deployed as an implementation and cloned by the factory
with immutable arguments. `TEEVerifier` and `ZKVerifier` are standalone proof verifiers referenced
by the game implementation. The registry references a standalone `NitroValidator`, which uses
`CertManager` and `P384Verifier` to validate signer attestations.

## Data Model

Expand Down Expand Up @@ -78,8 +83,8 @@

1. The factory owner configures a game type with an `AggregateVerifier` implementation and an
initialization bond.
2. TEE operators register enclave signer addresses in `TEEProverRegistry` using ZK-verified Nitro
attestation.
2. The registrar caches Nitro certificates in `CertManager`, then registers enclave signers through
`TEEProverRegistry` using the attestation and P-384 verification hints.
3. A proposer creates a game through `DisputeGameFactory.createWithInitData()`, paying the exact
initialization bond and providing an initial TEE or ZK proof.
4. The game validates its parent, L2 block number, intermediate roots, L1 origin, and proof
Expand Down Expand Up @@ -329,7 +334,7 @@

TEE and ZK proofs commit to the same transition shape:

```text Proof Journal Fields lines wrap expandable

Check warning on line 337 in docs/specifications/base-protocol/proofs/proof-contracts.mdx

View workflow job for this annotation

GitHub Actions / Docs Style / Conformance

Docs style

[codeblock/highlight] Consider `highlight={}` to draw attention to key lines

Check warning on line 337 in docs/specifications/base-protocol/proofs/proof-contracts.mdx

View workflow job for this annotation

GitHub Actions / Docs Style / Conformance

Docs style

[codeblock/highlight] Consider `highlight={}` to draw attention to key lines
proposer
l1OriginHash
startingRoot
Expand Down Expand Up @@ -493,7 +498,7 @@

- an owner
- a manager
- a `NitroEnclaveVerifier`
- an immutable `NitroValidator` reference
- a `DisputeGameFactory`
- a configurable `gameType`
- registered signer state
Expand All @@ -514,21 +519,30 @@
returns true only when the signer is registered and its stored image hash matches the current
expected hash.

Signer registration itself is PCR0-agnostic. This lets operators pre-register signers for a future
image before a game-type migration. Those signers do not become valid for proof submission until
the game implementation's `TEE_IMAGE_HASH` matches their registered image hash.
Registration does not compare PCR0 with the current `TEE_IMAGE_HASH`. This lets operators
pre-register signers for a future image before a game-type migration. Those signers do not become
valid for proof submission until the game's expected image hash matches their registered image hash.

### Signer Registration

`registerSigner(output, proofBytes)` calls:
`registerSigner(attestationTbs, signature, hints)` calls:

```text Attestation Verification Call
NITRO_VERIFIER.verify(output, ZkCoProcessorType.RiscZero, proofBytes)
```text Attestation Verification Call wrap
NITRO_VALIDATOR.validateAttestationWithHints(
attestationTbs,
signature,
hints
)
```

The returned journal must have `VerificationResult.Success`. The attestation timestamp must not be
older than `MAX_AGE`, which is fixed at 60 minutes. The public key must be exactly 65 bytes in
uncompressed ANSI X9.62 form:
The attestation's certificate chain must already be verified and cached in `CertManager`. The
validator returns field pointers into the signed attestation, and the registry applies Base-specific
checks.

The attestation timestamp, converted from milliseconds to seconds, must be strictly earlier than
`block.timestamp` and less than `MAX_AGE` (3,600 seconds) old. PCR0 must be present at index zero,
exactly 48 bytes, and not the all-zero debug-mode measurement. The public key must be exactly
65 bytes in uncompressed ANSI X9.62 form:

```text Uncompressed Public Key Layout
0x04 || x || y
Expand All @@ -540,13 +554,14 @@
address(uint160(uint256(keccak256(x || y))))
```

The registry extracts PCR0 from the journal and stores:
The registry extracts the 48-byte PCR0 measurement from the attestation and stores:

```text Signer Image Hash
signerImageHash[signer] = keccak256(pcr0.first || pcr0.second)
signerImageHash[signer] = keccak256(PCR0)
```

It then marks the signer as registered and adds it to an enumerable signer set.
It then marks the signer as registered, adds it to an enumerable signer set, and emits
`SignerRegistered`.

### Deregistration

Expand All @@ -555,109 +570,97 @@

`getRegisteredSigners()` returns the current enumerable set. Ordering is not guaranteed.

## NitroEnclaveVerifier

`NitroEnclaveVerifier` verifies ZK proofs of AWS Nitro Enclave attestation documents. It is the
attestation verifier used by `TEEProverRegistry`.

The contract supports:

- single-attestation verification
- batch attestation verification
- RISC Zero and Succinct SP1 proof systems
- root certificate configuration
- trusted intermediate certificate caching
- certificate revocation
- route-specific verifier selection
- permanently frozen proof routes

### Roles and Configuration

The owner controls:
## NitroValidator

- `rootCert`
- `maxTimeDiff`
- `proofSubmitter`
- `revoker`
- ZK verifier configuration
- verifier program IDs
- aggregator program IDs
- route-specific verifier overrides
- route freezing
`NitroValidator` validates AWS Nitro attestations using immutable `CertManager` and `P384Verifier`
references.

The `revoker` can also revoke trusted intermediate certificates. `proofSubmitter` is the only
address allowed to call `verify()` or `batchVerify()`.
```text Hinted Attestation Validation wrap
validateAttestationWithHints(
attestationTbs,
signature,
attestationSigHints
)
```

`zkConfig[zkCoProcessor]` stores:
The call:

| Field | Purpose |
| -------------- | ----------------------------------------------- |
| `verifierId` | Program ID for single-attestation verification. |
| `aggregatorId` | Program ID for batch verification. |
| `zkVerifier` | Default verifier contract address. |
1. Parses the signed COSE `Sig_structure` and validates the Nitro payload structure.
2. Re-walks the certificate chain through `CertManager`, requiring a complete, unexpired, unrevoked
path to the pinned AWS Nitro root.
3. Verifies the 96-byte P-384 attestation signature over `SHA384(attestationTbs)` with the leaf
certificate's public key and the supplied inverse hints.
4. Returns `Ptrs`, containing the timestamp and CBOR field pointers into `attestationTbs`.

Route-specific verifier overrides are keyed by `(zkCoProcessor, selector)`, where `selector` is
the first four bytes of `proofBytes`. If a route is frozen, verification through that route
permanently reverts.
The certificate-chain walk supplies no certificate hints, so every certificate must already be
cached. `decodeAttestationTbs()` separates a raw `COSE_Sign1` document into its signed TBS bytes and
signature; decoding alone does not validate the attestation.

### Single Verification
`NitroValidator` authenticates the signed fields but does not enforce Base's freshness window,
signer-key format, or accepted enclave image. The [registry](#signer-registration) applies the
timestamp, public-key, and PCR0 checks. The [registrar's challenge policy](/specifications/base-protocol/proofs/registrar#attestation-challenge)
checks the nonce offchain, and `TEEVerifier` enforces the game's expected image at proof submission.

`verify(output, zkCoprocessor, proofBytes)`:
The deprecated unhinted `validateAttestation()` entry point always reverts.

1. Requires `msg.sender == proofSubmitter`.
2. Resolves the verifier route from the proof selector.
3. Verifies the ZK proof against `zkConfig[zkCoprocessor].verifierId`.
4. Decodes `output` as a `VerifierJournal`.
5. Validates the journal.
6. Emits `AttestationSubmitted`.
7. Returns the journal with its final verification result.
## CertManager

For RISC Zero, proof verification uses:
`CertManager` pins the AWS Nitro root at deployment and caches verified CA and leaf certificates.
Its active verification methods are:

```text RISC Zero Verification Call
IRiscZeroVerifier.verify(proofBytes, programId, sha256(output))
```text Certificate Cache Calls wrap
verifyCACertWithHints(
cert, parentCertHash, signatureHints
)
verifyClientCertWithHints(
cert, parentCertHash, signatureHints
)
```

For Succinct, proof verification uses:
Both methods are permissionless. For non-root certificates, the parent must already be cached.
Cold verification checks the certificate signature through `P384Verifier`. Cached reuse checks the
certificate's CA or leaf role, expiry, original parent binding, and unrevoked path to the root.
The CA method returns the certificate's cache key; the leaf method returns `VerifiedCert` metadata.

```text Succinct Verification Call
ISP1Verifier.verifyProof(programId, output, proofBytes)
```
Non-root cache keys are `keccak256(TBSCertificate DER)`, excluding the outer signature. The root
uses its pinned `keccak256(root DER)` key. `loadVerified()` is a raw cache read: its result can be
expired or revoked and is not itself evidence that a certificate remains usable.

### Batch Verification
Revocation uses a separate identity: `computeCertId()` returns the non-root issuer/serial identity,
and `isRevoked()` reads its revocation status. The root uses the pinned root hash instead. Revocation
is checked during cold verification, cached reuse, and final attestation validation. See the
[registration plan](/specifications/base-protocol/proofs/registrar#registration-plan) for the exact
cache-key and revocation-identity encodings.

`batchVerify(output, zkCoprocessor, proofBytes)`:
Revoking the root blocks new signer registrations. Certificate revocation and expiration do not
invalidate previously registered signers; affected signers must be deregistered separately through
`TEEProverRegistry.deregisterSigner()`.

1. Requires `msg.sender == proofSubmitter`.
2. Verifies the ZK proof against `zkConfig[zkCoprocessor].aggregatorId`.
3. Decodes `output` as a `BatchVerifierJournal`.
4. Requires `batchJournal.verifierVk == getVerifierProofId(zkCoprocessor)`.
5. Validates every embedded `VerifierJournal`.
6. Emits `BatchAttestationSubmitted`.
7. Returns the validated journals.
The deprecated unhinted `verifyCACert()` and `verifyClientCert()` entry points always revert.

### Journal Validation
## P384Verifier

A successful journal remains successful only when:
```text Hinted P-384 Signature Verification wrap
verifyP384SignatureWithHints(
hash, signature, pubKey, inverseHints
)
```

- the trusted certificate prefix length is non-zero
- the first certificate equals `rootCert`
- every trusted intermediate certificate is still trusted and unexpired
- every newly supplied certificate is unexpired
- the attestation timestamp is not too old
- the attestation timestamp is not in the future
`P384Verifier` verifies P-384 ECDSA signatures for both certificates and attestations. It consumes
48-byte big-endian inverse hints and checks `b * hint == 1 (mod m)` before using each inverse.
Incorrect, truncated, or surplus hints revert; hints cannot make an invalid signature valid.
See [P-384 Hints](/specifications/base-protocol/proofs/registrar#p-384-hints) for the generation and
encoding requirements.

Attestation timestamps are provided in milliseconds and converted to seconds. The timestamp is
valid only when:
Low-S is not enforced, so equivalent signatures can have different bytes. Signature bytes must not
be used as unique certificate or attestation identifiers.

```text Timestamp Validity Conditions
timestamp + maxTimeDiff > block.timestamp
timestamp < block.timestamp
```
## NitroEnclaveVerifier

New certificates beyond the trusted prefix are cached with their expiry timestamps after successful
validation. A revoked certificate can become trusted again only if it appears in a later successful
attestation proof and is cached again.
`NitroEnclaveVerifier` is the legacy, pre-Cobalt ZK attestation verifier. The current registry uses
`NitroValidator`, `CertManager`, and `P384Verifier` instead. See
[Hinted Registration Migration](/specifications/base-protocol/proofs/registrar#hinted-registration-migration)
for the upgrade and preserved registry state.

## Cross-Contract Safety Properties

Expand All @@ -676,8 +679,8 @@
while a game with one proof waits five days since [Beryl](/upgrades/beryl/reducing-canonical-withdrawal-delay).
- Registry finality is separate from game resolution: a game can resolve before the
`AnchorStateRegistry` accepts it as a valid claim.
- Safety controls fail closed: pause, blacklist, retirement, verifier nullification, route
freezing, and certificate revocation all prevent acceptance rather than expanding trust.
- Safety controls fail closed: pause, blacklist, retirement, and verifier nullification prevent
acceptance rather than expanding trust.

## Administrative Surfaces

Expand All @@ -688,8 +691,8 @@
| `DelayedWETH` | Proxy admin owner | Recover ETH and hold WETH from accounts. |
| `TEEProverRegistry` | Owner | Set proposers, update game type, transfer ownership or management. |
| `TEEProverRegistry` | Owner or manager | Register and deregister TEE signers. |
| `NitroEnclaveVerifier` | Owner | Configure root certificate, time tolerance, proof submitter, revoker, ZK routes, and program IDs. |
| `NitroEnclaveVerifier` | Owner or revoker | Revoke trusted intermediate certificates. |
| `CertManager` | Owner | Transfer ownership, set the revoker, revoke the pinned root, and unrevoke certificate identities or the root. |
| `CertManager` | Revoker | Revoke non-root issuer/serial identities with `revokeCert()` or `revokeCerts()`. |

These surfaces are intentionally narrow but high impact. Operational changes to them can affect
which games are respected, which proofs verify, and which attestations can register new TEE
Expand Down
23 changes: 18 additions & 5 deletions docs/specifications/base-protocol/proofs/tee-prover.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
The two processes communicate only over vsock. The enclave has no network interface; all external
RPC connectivity is on the host side.

```mermaid TEE Prover Architecture lines wrap expandable

Check warning on line 51 in docs/specifications/base-protocol/proofs/tee-prover.mdx

View workflow job for this annotation

GitHub Actions / Docs Style / Conformance

Docs style

[codeblock/highlight] Consider `highlight={}` to draw attention to key lines

Check warning on line 51 in docs/specifications/base-protocol/proofs/tee-prover.mdx

View workflow job for this annotation

GitHub Actions / Docs Style / Conformance

Docs style

[codeblock/highlight] Consider `highlight={}` to draw attention to key lines
flowchart LR
caller[Proposer / Challenger]
host[NitroProverServer\nbase-prover-nitro-host]
Expand Down Expand Up @@ -131,11 +131,24 @@

### enclave_signerAttestation

Takes optional `user_data` and `nonce` byte arguments. Both are capped at 512 bytes by the NSM
hardware and rejected at the host RPC layer before the vsock call. The host returns one raw
`COSE_Sign1` document per configured enclave, in the same order as `enclave_signerPublicKey`. The
registrar uses this endpoint to bind each enclave's signer to a fresh attestation before
submitting it onchain.
`enclave_signerAttestation(user_data, nonces)` takes two optional arguments:

| Argument | Type |
| --- | --- |
| `user_data` | `Option<Vec<u8>>` |
| `nonces` | `Option<Vec<Vec<u8>>>` |

The host includes the same `user_data` in every attestation and assigns `nonces` in
`enclave_signerPublicKey` order.

If supplied, `nonces` must contain exactly as many entries as there are configured enclaves. If
omitted, no nonce is sent to any enclave. The NSM limit is 512 bytes for `user_data` and 512 bytes
for each nonce. The host rejects a wrong nonce count or oversized input with JSON-RPC error
`-32602` before any vsock call.

The host returns one raw `COSE_Sign1` document per configured enclave in that same order. The
registrar supplies a [deterministic challenge per signer](/specifications/base-protocol/proofs/registrar#attestation-challenge)
to bind each attestation to the expected registry and signer before submitting it onchain.

## Proof Pipeline

Expand Down
Loading