diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 6f4d267a7..6625f06c8 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -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. diff --git a/docs/llms.txt b/docs/llms.txt index 55e1b5c26..344bc9b2d 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -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. diff --git a/docs/specifications/base-protocol/proofs/proof-contracts.mdx b/docs/specifications/base-protocol/proofs/proof-contracts.mdx index f83b8f2ce..4402a4d40 100644 --- a/docs/specifications/base-protocol/proofs/proof-contracts.mdx +++ b/docs/specifications/base-protocol/proofs/proof-contracts.mdx @@ -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 @@ -17,7 +17,9 @@ This page specifies the contract behavior used by the proof system: - `ZKVerifier` - `TEEVerifier` - `TEEProverRegistry` -- `NitroEnclaveVerifier` +- `NitroValidator` +- `CertManager` +- `P384Verifier` ## Contract Graph @@ -29,16 +31,19 @@ flowchart TB 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 @@ -78,8 +83,8 @@ The final intermediate root must equal the game's `rootClaim`. 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 @@ -493,7 +498,7 @@ The registry has: - an owner - a manager -- a `NitroEnclaveVerifier` +- an immutable `NitroValidator` reference - a `DisputeGameFactory` - a configurable `gameType` - registered signer state @@ -514,21 +519,30 @@ DisputeGameFactory.gameImpls(gameType).TEE_IMAGE_HASH() 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 @@ -540,13 +554,14 @@ The registry derives the signer address as: 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 @@ -555,109 +570,97 @@ from the enumerable set, and emits `SignerDeregistered`. `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 @@ -676,8 +679,8 @@ The proof contracts rely on the following cross-contract properties: 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 @@ -688,8 +691,8 @@ The proof contracts rely on the following cross-contract properties: | `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 diff --git a/docs/specifications/base-protocol/proofs/tee-prover.mdx b/docs/specifications/base-protocol/proofs/tee-prover.mdx index 794829474..7956c53ea 100644 --- a/docs/specifications/base-protocol/proofs/tee-prover.mdx +++ b/docs/specifications/base-protocol/proofs/tee-prover.mdx @@ -131,11 +131,24 @@ range. ### 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>` | +| `nonces` | `Option>>` | + +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