From ace0c423f1f930fead349790157021f3071fa989 Mon Sep 17 00:00:00 2001 From: roethke Date: Wed, 30 Sep 2026 15:44:59 -0700 Subject: [PATCH 1/4] docs(proofs): specify hinted TEE registration in the Registrar spec Bring over Leopold's changes from #1956 (merged into the #1953 branch, never landed on master): replace the ZK/Boundless registrar description with the hinted P-384 flow shipped in Cobalt, and add the Hinted TEE Registration page. Past tense where the migration has now shipped, and link the new page from the Cobalt overview. Co-authored-by: Leopold Joy Generated with Claude Code Co-Authored-By: Claude --- docs/AGENTS.md | 2 +- docs/docs.json | 1 + docs/llms-full.txt | 4 +- docs/llms.txt | 4 +- .../proofs/hinted-tee-registration.mdx | 77 +++ .../base-protocol/proofs/registrar.mdx | 574 +++++++----------- docs/upgrades/cobalt/overview.mdx | 2 +- 7 files changed, 320 insertions(+), 344 deletions(-) create mode 100644 docs/specifications/base-protocol/proofs/hinted-tee-registration.mdx diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 939172282..8bdd98ecb 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -57,7 +57,7 @@ npx skills add base/base-skills |Specifications/Specifications/Base Protocol/Consensus:specifications/base-protocol/consensus/specification,specifications/base-protocol/consensus/derivation,specifications/base-protocol/consensus/p2p,specifications/base-protocol/consensus/rpc |Specifications/Specifications/Base Protocol/Execution:specifications/base-protocol/execution/l2-execution-engine,specifications/base-protocol/execution/precompiles,specifications/base-protocol/execution/predeploys,specifications/base-protocol/execution/preinstalls |Specifications/Specifications/Base Protocol/Bridging:specifications/base-protocol/bridging/standard-bridges,specifications/base-protocol/bridging/deposits,specifications/base-protocol/bridging/withdrawals,specifications/base-protocol/bridging/cross-domain-messengers,specifications/base-protocol/bridging/base-solana-bridge -|Specifications/Specifications/Base Protocol/Proofs:specifications/base-protocol/proofs/overview,specifications/base-protocol/proofs/challenger,specifications/base-protocol/proofs/proposer,specifications/base-protocol/proofs/registrar,specifications/base-protocol/proofs/tee-prover,specifications/base-protocol/proofs/zk-prover,specifications/base-protocol/proofs/proof-contracts +|Specifications/Specifications/Base Protocol/Proofs:specifications/base-protocol/proofs/overview,specifications/base-protocol/proofs/challenger,specifications/base-protocol/proofs/proposer,specifications/base-protocol/proofs/hinted-tee-registration,specifications/base-protocol/proofs/registrar,specifications/base-protocol/proofs/tee-prover,specifications/base-protocol/proofs/zk-prover,specifications/base-protocol/proofs/proof-contracts |Specifications/Specifications/B20:specifications/b20/index,specifications/b20/introduction,specifications/b20/changelog |Specifications/Specifications/B20/Concepts:specifications/b20/concepts/token-types,specifications/b20/concepts/policies,specifications/b20/concepts/roles-and-pause,specifications/b20/concepts/multipliers |Specifications/Specifications/B20/Reference:specifications/b20/reference/interfaces,specifications/b20/reference/constants,specifications/b20/reference/errors,specifications/b20/reference/events diff --git a/docs/docs.json b/docs/docs.json index afaf71715..8dcc89934 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -233,6 +233,7 @@ "specifications/base-protocol/proofs/overview", "specifications/base-protocol/proofs/challenger", "specifications/base-protocol/proofs/proposer", + "specifications/base-protocol/proofs/hinted-tee-registration", "specifications/base-protocol/proofs/registrar", "specifications/base-protocol/proofs/tee-prover", "specifications/base-protocol/proofs/zk-prover", diff --git a/docs/llms-full.txt b/docs/llms-full.txt index e65d4a976..365909e0c 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -282,7 +282,9 @@ const client = createPublicClient({ chain: base, transport: http() }) - [Proposer](https://docs.base.org/specifications/base-protocol/proofs/proposer): Specification of the proposer, an offchain service that turns canonical L2 checkpoint ranges into AggregateVerifier games on L1. -- [Registrar](https://docs.base.org/specifications/base-protocol/proofs/registrar): Specification of the registrar, an offchain service that maintains the onchain registry of accepted TEE signer identities via attestation ZK proofs. +- [Hinted TEE Registration](https://docs.base.org/specifications/base-protocol/proofs/hinted-tee-registration): Migration from external ZK proving to checked P-384 hints for registering new AWS Nitro Enclave signers. + +- [Registrar](https://docs.base.org/specifications/base-protocol/proofs/registrar): Specification of the registrar and the hinted P-384 flow used to register AWS Nitro Enclave signer identities on L1. - [TEE Prover](https://docs.base.org/specifications/base-protocol/proofs/tee-prover): Specification of the TEE prover, an offchain service that re-executes L2 block ranges inside AWS Nitro Enclaves to produce signed proof material for AggregateVerifier games. diff --git a/docs/llms.txt b/docs/llms.txt index 2d8a8504d..fd9e7aabb 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -216,7 +216,9 @@ - [Proposer](https://docs.base.org/specifications/base-protocol/proofs/proposer): Specification of the proposer, an offchain service that turns canonical L2 checkpoint ranges into AggregateVerifier games on L1. -- [Registrar](https://docs.base.org/specifications/base-protocol/proofs/registrar): Specification of the registrar, an offchain service that maintains the onchain registry of accepted TEE signer identities via attestation ZK proofs. +- [Hinted TEE Registration](https://docs.base.org/specifications/base-protocol/proofs/hinted-tee-registration): Migration from external ZK proving to checked P-384 hints for registering new AWS Nitro Enclave signers. + +- [Registrar](https://docs.base.org/specifications/base-protocol/proofs/registrar): Specification of the registrar and the hinted P-384 flow used to register AWS Nitro Enclave signer identities on L1. - [TEE Prover](https://docs.base.org/specifications/base-protocol/proofs/tee-prover): Specification of the TEE prover, an offchain service that re-executes L2 block ranges inside AWS Nitro Enclaves to produce signed proof material for AggregateVerifier games. diff --git a/docs/specifications/base-protocol/proofs/hinted-tee-registration.mdx b/docs/specifications/base-protocol/proofs/hinted-tee-registration.mdx new file mode 100644 index 000000000..27553b7bc --- /dev/null +++ b/docs/specifications/base-protocol/proofs/hinted-tee-registration.mdx @@ -0,0 +1,77 @@ +--- +title: "Hinted TEE Registration" +description: "Migration from external ZK proving to checked P-384 hints for registering new AWS Nitro Enclave signers." +--- + +The [Cobalt](/upgrades/cobalt/overview) upgrade replaced the RISC Zero and Boundless flow used to register new TEE signers with direct +AWS Nitro attestation verification using checked P-384 hints. + +The change makes registration faster and removes the dependency on an external ZK proving service. +It does not change how enclaves produce TEE state proofs or how those proofs are verified. + +## Previous Flow + +The registrar previously sent each Nitro attestation to an external proving service. That service +produced a RISC Zero proof showing that the attestation and its AWS certificate chain were valid. +The registrar then submitted the proof to `TEEProverRegistry` on L1. + +This added an external availability dependency and could delay new signer registration while the ZK +proof was generated. + +## New Flow + +The registrar now: + +1. Fetches a fresh AWS Nitro attestation for the new enclave signer. +2. Generates P-384 modular inverse hints locally. +3. Verifies and caches any missing AWS certificates through `CertManager`. +4. Submits the attestation, its signature, and the final hint stream to `TEEProverRegistry`. +5. Records the signer and its attested PCR0 after all onchain checks pass. + +`TEEProverRegistry` permits only its owner or manager to perform the final registration. Certificate +cache writes are permissionless because `CertManager` verifies the certificate, its parent chain, +and every supplied hint before storing it. + +## Checked Hints + +P-384 verification requires many modular inversions. Computing those inversions in the EVM is +expensive, but checking a proposed inverse is comparatively cheap. The registrar computes the +values offchain and supplies them as hints. + +Every hint is checked onchain before use. Incorrect, missing, or surplus hints revert, so the hint +generator is required for liveness but is not trusted for correctness. A faulty or malicious hint +cannot make an invalid certificate or attestation pass. + +## Certificate Caching + +The AWS Nitro root is pinned in `CertManager`. Non-root CA certificates and the leaf certificate are +verified and cached in parent-first order. Later registrations can reuse cached certificates while +they remain valid and unrevoked. + +For a typical Nitro certificate chain: + +| Cache state | Registration transactions | +| --- | ---: | +| Empty | 5 | +| CA chain cached, new leaf | 2 | +| CA chain and leaf cached | 1 | + +The registrar reads onchain cache state before each write, allowing it to resume after a partial +registration or process restart without repeating completed certificate transactions. + +## Unchanged Behavior + +This migration does not change: + +- signer key generation inside the Nitro Enclave; +- the attestation fields produced by AWS Nitro; +- PCR0-based enclave image selection; +- TEE proposal or dispute proof formats; +- `TEEVerifier` or `AggregateVerifier` behavior; or +- SP1 state-proof verification. + +The existing `TEEProverRegistry` proxy was upgraded without changing its storage layout. Existing +owners, managers, proposers, registered signers, and signer image hashes are preserved. + +For the full registration lifecycle, validation rules, revocation handling, and operator +requirements, see the [Registrar specification](/specifications/base-protocol/proofs/registrar). diff --git a/docs/specifications/base-protocol/proofs/registrar.mdx b/docs/specifications/base-protocol/proofs/registrar.mdx index 48668a06a..9cd7d01d7 100644 --- a/docs/specifications/base-protocol/proofs/registrar.mdx +++ b/docs/specifications/base-protocol/proofs/registrar.mdx @@ -1,412 +1,306 @@ --- title: "Registrar" -description: "Specification of the registrar, an offchain service that maintains the onchain registry of accepted TEE signer identities via attestation ZK proofs." +description: "Specification of the registrar and the hinted P-384 flow used to register AWS Nitro Enclave signer identities on L1." --- -The registrar is an offchain service that maintains the onchain registry of accepted TEE signer -identities. It discovers running TEE prover instances, fetches AWS Nitro Enclave attestation -documents from each enclave, generates a ZK proof that the attestation is well-formed, and submits -the resulting signer registration to [`TEEProverRegistry`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEProverRegistry.sol) -on L1. It also deregisters signers whose backing instances are no longer reachable, and revokes -intermediate certificates that AWS has withdrawn. +The registrar is an offchain service that maintains the signer set in +[`TEEProverRegistry`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEProverRegistry.sol). +It discovers Base TEE prover instances, obtains an AWS Nitro attestation for each enclave signer, +generates P-384 verification hints locally, caches the attestation certificate chain on L1, and +submits the final signer registration. -A registrar is operated by Base. The proof system trusts only signers that this registrar has -registered, so registrar correctness is a prerequisite for accepting TEE proofs onchain. Its output -is still self-validating: the attestation ZK proof, the enclave PCR0 measurement, and the signer -public key are all checked by `TEEProverRegistry` and [`NitroEnclaveVerifier`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/NitroEnclaveVerifier.sol) -before the signer becomes valid. +This flow replaces the previous RISC Zero attestation proof and external Boundless proving path. +There is no runtime backend selector or fallback to ZK verification. The TEE proof format, +`TEEVerifier`, `AggregateVerifier`, `TEE_IMAGE_HASH`, and SP1 state-proof behavior are unchanged. +Registration requires the hinted-compatible registrar and prover-host signer API. ## Responsibilities A conforming registrar performs the following work: -1. Discover the current set of TEE prover instances behind the production load balancer. -2. Fetch the per-enclave signer public keys and Nitro attestation documents from each instance. -3. Optionally check the attestation certificate chain against AWS-published CRLs and against the - onchain durable revocation set. -4. Generate a ZK proof of attestation correctness for every enclave that is not yet registered. -5. Submit `TEEProverRegistry.registerSigner()` for newly attested signers. -6. Submit `TEEProverRegistry.deregisterSigner()` for onchain signers whose instances are gone. -7. Submit `NitroEnclaveVerifier.revokeCert()` for intermediate certificates discovered to be - revoked. -8. Recover in-flight proof requests across process restarts without re-spending proving work. - -The registrar does not gate which PCR0 measurements are accepted. Registration is PCR0-agnostic so -that the next image's signers can be pre-registered ahead of a hardfork. Acceptance of proofs -produced by a given signer is enforced onchain by [`TEEVerifier`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEVerifier.sol) -against the current `TEE_IMAGE_HASH` of the active game implementation. - -The registrar also does not create proposals, generate proof material for proposals or disputes, -or dispute invalid state transitions. Those responsibilities belong to the proposer, the TEE -provers, and the challenger. - -## Startup Configuration - -At startup, the registrar connects to: - -- an L1 execution RPC for contract reads and transaction submission -- AWS APIs for ELBv2 target health and EC2 instance metadata -- a JSON-RPC endpoint on each discovered TEE prover instance -- a proving backend (Boundless marketplace or a self-hosted RISC Zero prover) -- `TEEProverRegistry` -- an optional `NitroEnclaveVerifier`, required only when CRL checking is enabled - -The registrar reads no contract configuration at startup beyond the registry and verifier -addresses provided by the operator. It treats every onchain signer it has not seen in its own -instance set as an orphan candidate, so a single registrar must be the sole writer for a given -registry. +1. Discover TEE prover instances behind the configured AWS load balancer. +2. Fetch each enclave's secp256k1 public key and a fresh Nitro attestation bound to that signer. +3. Parse the COSE and X.509 data without changing the signed encodings. +4. Validate the expected signer, nonce, PCR0, trust anchor, certificate order, and freshness locally. +5. Generate the checked P-384 inverse hints required by the onchain verifier. +6. Reuse valid cached certificates and cache missing certificates in parent-first order. +7. Submit the final attestation to `TEEProverRegistry.registerSigner()`. +8. Optionally monitor AWS certificate revocation lists and persist confirmed revocations on L1. +9. Deregister signers whose backing prover instances are no longer active. + +The registrar does not select the accepted enclave image. Registration records the attested PCR0, +but [`TEEVerifier`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEVerifier.sol) +accepts a signer only when its recorded image hash matches the active `TEE_IMAGE_HASH`. This allows +the next image's signers to be registered before an image rotation without allowing them to produce +accepted proofs early. + +## Architecture + +```mermaid TEE Signer Registration lines wrap expandable +flowchart LR + registrar[Registrar] + prover[TEE prover instance] + registry[TEEProverRegistry] + validator[NitroValidator] + certs[CertManager] + p384[P384Verifier] + + prover -- "signer key + attestation" --> registrar + registrar -- "cache certificate + hints" --> certs + registrar -- "attestation TBS + signature + hints" --> registry + registry --> validator + validator --> certs + certs --> p384 + validator --> p384 +``` -## Driver Loop +The onchain validation stack contains three contracts: -The registrar runs a single driver loop: - -1. Discover the current instance set. -2. Process every instance concurrently, bounded by `max_concurrency`. -3. Read the onchain signer set. -4. Deregister orphan signers. -5. Sleep `poll_interval` seconds, or exit on cancellation. - -The loop runs `step()` once on startup before sleeping. Cancellation is observed promptly between -ticks and inside long-running tx retries so the service can shut down without leaving partial -state. - -## Instance Discovery - -The registrar uses AWS ALB target group polling. DNS, SRV, and Kubernetes discovery are not -supported. - -Each discovery cycle: - -1. Calls `elasticloadbalancingv2.DescribeTargetHealth(target_group_arn)`. -2. Filters out non-instance targets (target IDs that do not start with `i-`). -3. Deduplicates instance IDs that appear on more than one port. -4. Calls `ec2.DescribeInstances(instance_ids)` to read each instance's private IP and launch time. -5. Builds JSON-RPC endpoint URLs of the form `http://{private_ip}:{prover_port}` and pairs each - with its ALB-reported health state. - -Health states map as follows: - -| AWS state | Internal state | `should_register()` | -| ------------ | -------------- | ------------------- | -| `initial` | `Initial` | true | -| `healthy` | `Healthy` | true | -| `draining` | `Draining` | false | -| anything else| `Unhealthy` | false | - -`Unhealthy` instances within `unhealthy_registration_window` seconds of `launch_time` are still -allowed to register. This is a warm-up grace period: it lets a new instance whose JSON-RPC -endpoint is briefly slow finish enclave attestation and registration before the next ALB health -check would deregister it. The window must be smaller than the Boundless proving timeout so that -a started proof can complete before the instance becomes ineligible. - -Discovery failures abort that tick and skip orphan cleanup. They do not deregister live signers. - -## Per-Instance Processing - -For each discovered instance, the registrar: - -1. Calls `enclave_signerPublicKey` to fetch the per-enclave SEC1 public keys. Each instance can - host multiple enclaves and each enclave has its own signer key. -2. Derives the Ethereum signer address from each public key as the last 20 bytes of - `keccak256(uncompressed_pubkey_xy)`. -3. Returns immediately if no signers were reported. The address set still contributes nothing for - this instance and the call is a no-op. -4. Decides whether the instance is currently registerable: - - `Initial` and `Healthy` instances proceed. - - `Unhealthy` instances within the warm-up window proceed. - - All other instances contribute their addresses to the active set but do not generate new - proofs or transactions. -5. Generates a single 32-byte random nonce and calls `enclave_signerAttestation` once with that - nonce. The nonce binds every per-enclave attestation in the returned batch to the same - freshness commitment. -6. Performs CRL checks once per batch when CRL checking is enabled. Each enclave has its own - signing key, but AWS Nitro attestations are signed by the parent EC2 instance's Nitro - Hypervisor, whose signing key is endorsed by a per-instance AWS-issued certificate chain. - Every enclave on the same instance therefore produces an attestation under the same parent - chain, so a single CRL check per instance is sufficient. -7. For each signer address, runs the registration pipeline. - -All reachable instances contribute to the active signer set, including `Draining` and `Unhealthy` -ones. This prevents an instance that is rotating in or out from being deregistered prematurely. - -## Attestation Proof Generation - -The registrar produces proof material for every signer not yet onchain by calling an -`AttestationProofProvider`. The provider returns: - -```text Attestation Proof Provider Output -output // ABI-encoded VerifierJournal (PCRs, public key, timestamp, cert hashes) -proofBytes // Groth16 seal -``` +| Contract | Role | +| --- | --- | +| `P384Verifier` | Verifies P-384 signatures while checking caller-supplied modular inverse hints. | +| `CertManager` | Pins the AWS Nitro root, verifies and caches non-root certificates, and enforces certificate expiry and revocation. | +| `NitroValidator` | Parses the signed Nitro attestation, re-walks the cached certificate chain, and verifies the final COSE signature. | + +`TEEProverRegistry` holds an immutable `NitroValidator` reference. The registrar discovers +`CertManager` through `TEEProverRegistry.NITRO_VALIDATOR()` and `NitroValidator.certManager()`; +operators do not configure those addresses separately. -`output` is the `VerifierJournal` consumed by `NitroEnclaveVerifier.verify()` during -`registerSigner()`. `proofBytes` is the Groth16 SNARK that proves the journal corresponds to a -valid Nitro attestation document. +## Driver Loop -The registrar supports two backends: +The registrar runs one polling loop: -| Backend | Description | -| ----------- | ------------------------------------------------------------------------------------------------------------ | -| `boundless` | Submits the proving job to the Boundless marketplace using a dedicated wallet. | -| `direct` | Loads the guest ELF locally and proves via `risc0_zkvm::default_prover()`, routing to Bonsai or a local prover according to RISC Zero environment variables. | +1. Query AWS ALB and EC2 for the current prover instances. +2. Probe `/readyz` on every non-draining target. The load balancer's `/healthz` is registration-gated, + so it cannot be used to bootstrap registration. +3. Resolve signer public keys and attestations concurrently, bounded by `max_concurrency`. +4. Start one registration task per eligible signer and cancel tasks whose signer is no longer eligible. +5. Read the onchain signer set and deregister unprotected signers when discovery was conclusive. +6. Sleep for `poll_interval`, or stop on cancellation. -Both backends are valid production paths. `boundless` is the primary production backend. -`direct` is also used for local development and tests, but it is suitable for production fallback -when an operator needs to bypass the marketplace, for example during a Boundless incident or for -private-deployment scenarios. +Only instances that pass `/readyz` are eligible for new registrations. Draining instances still +contribute their known signer addresses to the active set so a rotation does not immediately +deregister them. -For Boundless, the registrar submits a `RequestParams` containing the program URL, the attestation -input, the expected `image_id`, and a `prefix_match(image_id)` requirement so a fulfilled request -cannot be replayed against a different program. Onchain Boundless submissions are serialized -behind a mutex to avoid wallet nonce races. +When an instance disappears or becomes unhealthy, the registrar preserves its last-known signers +for `instance_cache_ttl_cycles`. It skips the entire orphan pass while any instance remains +unresolved. Pending registration tasks are also protected from orphan cleanup. -### Restart Recovery +Discovery supports AWS ELBv2 target groups whose targets are EC2 instances. Targets whose IDs do +not start with `i-` are ignored. AWS API errors and missing EC2 data abort the tick and skip orphan +cleanup, but an empty target group is a conclusive result. The last-known-signer cache is in memory +and starts empty after a process restart. Operators must therefore configure and monitor the target +group carefully: a cold registrar pointed at an empty target group has no cached signers to protect. -The registrar process is itself ephemeral. Across restarts, it must not re-spend proving work and -must not submit stale proofs. Boundless `RequestId` slots are derived deterministically: +## Attestation Challenge -```text Request Index Derivation -request_index(signer, attempt) = u32::from_be_bytes(keccak256(signer || attempt)[..4]) -``` +Each signer receives a deterministic 32-byte nonce: -For each signer, the registrar probes `max_recovery_attempts` consecutive deterministic slots -before submitting a fresh request. The action depends on the slot status: +```text Attestation Nonce +keccak256( + "base-proof-tee-registrar:attestation-nonce:v1" || + teeProverRegistryAddress || + signerAddress +) +``` -| Slot status | Registrar action | -| ------------- | --------------------------------------------------------------------------------- | -| `Unknown` | Record the first such slot as the candidate fresh-submission slot; keep scanning. | -| `Locked` | Resume `wait_for_request_fulfillment` and use the resulting receipt. | -| `Fulfilled` | Fetch the receipt and check journal freshness before accepting it. | -| `Expired` | Skip the slot permanently; continue scanning. | +The registrar requests one attestation per signer with that nonce. It rejects the response unless: -A `RequestIsNotLocked` revert encountered mid-scan is treated as in-flight and short-circuits to -waiting on that slot. +- the attested `public_key` derives to the expected signer address; +- the attested nonce exactly matches the deterministic challenge; +- PCR0 is present, is 48 bytes, and is not the all-zero debug-mode measurement; +- the certificate chain starts at the pinned AWS Nitro root; +- the certificate chain is ordered parent-first and ends in one leaf certificate; and +- the attestation remains within the configured local freshness window. -If a recovered receipt's attestation timestamp is older than `max_attestation_age`, the registrar -discards it and submits a fresh request in the candidate slot. The default freshness window is -3300 seconds, kept strictly under the onchain `MAX_AGE` of 3600 seconds so a recovered proof can -still be submitted before it ages out onchain. +The deterministic challenge binds the attestation to one registry and signer without requiring +registrar state across restarts. `TEEProverRegistry` independently enforces timestamp freshness, +but nonce matching is a registrar policy check because `NitroValidator` only exposes the signed +nonce to its caller. -After an `ExecutionReverted` from `registerSigner()`, the signer is added to a per-process -`recovery_blocked` set. The next cycle skips the recovery scan for that signer and submits a fresh -request, so a known-bad recovered proof is never tried twice. The set is cleared on restart, which -gives one fresh attempt per process even for previously blocked signers. +## Registration Plan -## Registration Transactions +The registrar preserves the exact protected-header and payload encodings from the input +`COSE_Sign1` document when constructing the signed `Sig_structure`. Re-encoding signed CBOR would +change the message and invalidate the AWS signature. -For each unregistered signer, the registrar: +For parity with the pinned `NitroValidator`, the registrar accepts an optional compact `0xD2` tag +followed by the compact `0x84` `COSE_Sign1` array, the ES384 protected header `0x44a1013822`, a +96-byte P-384 signature, and no trailing COSE or TBS data. The payload, `pcrs`, and `cabundle` +containers may use definite or indefinite lengths. Unknown payload keys are skipped, but recognized +keys must be unique. -1. Calls `TEEProverRegistry.isRegisteredSigner(signer)`. If true, the signer is skipped. -2. Generates or recovers proof material as described above. -3. ABI-encodes `registerSigner(output, proofBytes)`. -4. Submits the transaction through the L1 transaction manager. -5. Retries failed submissions according to the rules below. -6. On a successful receipt, increments the registration counter. +The parsed plan contains: -The transaction retry rules are: +- the signer derived from the 65-byte `0x04 || x || y` secp256k1 public key; +- PCR0, timestamp, and nonce; +- the pinned root certificate; +- non-root CA certificates followed by the leaf certificate; +- the exact attestation to-be-signed bytes and 96-byte P-384 signature; and +- cache and revocation identifiers matching `CertManager`. -| Failure | Required behavior | -| ----------------------------- | ------------------------------------------------------------------------------------------------------- | -| Retryable error | Sleep `tx_retry_delay`, then retry, up to `max_tx_retries` total attempts. | -| `ExecutionReverted` revert | Block recovery for this signer so the next cycle generates a fresh proof, then return the error. | -| Insufficient funds, fee cap | Treat as non-retryable. Surface the error and stop attempting this signer for the current cycle. | -| Reverted receipt | Treat as a transaction failure even when submission succeeded. | -| Reported error after mining | Re-read `isRegisteredSigner(signer)`. If true, treat the attempt as success. | +The pinned root cache key is `keccak256(root DER)`. Every non-root cache key is +`keccak256(TBSCertificate DER)`, excluding the malleable outer ECDSA signature. Non-root revocation +uses `keccak256(issuerHash || serialHash)`. `issuerHash` hashes the issuer Name content octets, +excluding its DER tag and length. `serialHash` hashes the serial INTEGER content octets, including a +leading `0x00` used for DER sign extension. These identities remain stable across equivalent outer +certificate encodings. -The post-error reconciliation is required because fee-bumping and nonce races can return errors -even when the underlying transaction has already been mined. Without the recheck, the registrar -would burn proving work generating a fresh proof for an already-registered signer. +## P-384 Hints -Transaction submission is cancellation-aware: both the active send and the inter-attempt sleep -abort cleanly on shutdown, so the next process starts from a clean nonce state without committing -a partial transaction. +AWS signs Nitro certificates and attestations with ECDSA over P-384. P-384 verification requires +many modular inversions, which are expensive to compute in the EVM. The registrar computes each +inverse offchain and supplies it as a hint. -## Orphan Deregistration +For every division by `b` modulo a prime `m`, the contract checks: -After processing every instance, the registrar reconciles the onchain signer set against the -active set: +```text Inverse Check +b * hint == 1 (mod m) +``` -1. If discovery failed for this tick, skip cleanup. -2. If cancellation was requested, skip cleanup. -3. Compare the number of reachable instances against the total discovered instances. If - `reachable_instances * 2 <= total_instances`, skip cleanup. -4. Read the onchain set with `TEEProverRegistry.getRegisteredSigners()`. -5. Compute `orphans = onchain_signers \ active_signers`. -6. For each orphan, in order: - 1. Recheck `isRegisteredSigner(signer)`. Skip if it returns false. - 2. ABI-encode `deregisterSigner(signer)` and submit it through the transaction manager. +The hint is used only after this equality holds. Since an invertible value has one inverse modulo a +prime, a passing hint is the same value the contract would have computed. Incorrect, truncated, or +surplus hints revert. Hints affect liveness, not correctness: a faulty generator can prevent a +registration but cannot make an invalid signature pass. -The majority-reachable guard prevents a transient AWS or VPC outage from deregistering most of -the prover fleet at once. The per-orphan `isRegisteredSigner` recheck is a race guard: the set -returned by `getRegisteredSigners()` is read once per cycle, and another writer could have -deregistered a signer between that read and this transaction. Skipping already-deregistered -addresses avoids wasted gas on a no-op transaction. +Each hint stream is a concatenation of 48-byte big-endian inverses in the verifier's deterministic +consumption order. The registrar generates one stream for every non-root certificate signature and +one for the final attestation signature. Production hint generation is native Rust and does not +invoke Node, Go, or an external proving service. -This procedure assumes a single registrar per `TEEProverRegistry`. Two registrars sharing a -registry would each treat the other's signers as orphans. +## Certificate Cache -## Certificate Revocation +`CertManager` stores the pinned AWS Nitro root at deployment. The registrar processes every +remaining certificate in parent-first order: -When the operator enables CRL checking, the registrar enforces revocation using two layers in -order. Both are required to make CRL handling safe. +1. Read the candidate's cached metadata and revocation state. +2. If cached, require the expected CA or leaf role, an unexpired validity period, the original + parent binding, and a complete unrevoked path to the pinned root. +3. If not cached, call `verifyCACertWithHints()` or `verifyClientCertWithHints()` with the DER + certificate, parent cache key, and signature hints. +4. Re-read state after the transaction. A transaction error is treated as success if the expected + usable cache entry is now present. -### Layer 1: Onchain Durable Revocation Pre-Check +Cache writes are permissionless because all certificate data and hints are verified onchain. +Per-certificate locks prevent concurrent signer tasks from submitting duplicate cache transactions +for a shared chain. On restart or after a partial failure, the registrar reads the cache again and +continues from the first missing certificate. -For each intermediate certificate in the attestation chain, the registrar reads -`NitroEnclaveVerifier.revokedCerts(certPathDigest)`. Any hit blocks registration for that batch -and skips Layer 2 entirely. +For a typical Nitro chain with three non-root CAs, transaction counts are: -This layer protects against a known attack against the cached-cert path: an intermediate that was -once revoked onchain could be reintroduced through a later `_cacheNewCert` write if its CRL entry -is later pruned by AWS. Reading the durable mapping first ensures a revoked cert cannot be -silently rehabilitated. +| Cache state | Transactions | +| --- | ---: | +| Empty | 5: three CA cache writes, one leaf cache write, one registration | +| CA chain cached, new leaf | 2: one leaf cache write, one registration | +| CA chain and leaf cached | 1: registration only | -RPC errors against `revokedCerts` fail open and fall through to Layer 2, but are counted as -revocation check errors. `RegistrationDriver::new` requires a `NitroEnclaveVerifier` client when -CRL checking is enabled and rejects misconfiguration at startup. +Each signature verification is split into its own transaction so it remains below the EIP-7825 +per-transaction gas limit. Final registration intentionally supplies no certificate hints; +`NitroValidator` succeeds only if the complete chain is already cached and usable. -### Layer 2: AWS CRL Distribution Points +## Final Registration -For intermediates that pass Layer 1, the registrar: +After the cache is ready, the registrar calls: -1. Parses each CRL distribution point from the chain. -2. Validates the URL host against an allowlist requiring the `.amazonaws.com` suffix and the - `nitro-enclave` keyword. HTTP redirects are disabled and responses are bounded to 10 MiB. -3. Fetches the CRL with a configurable timeout. -4. Searches for the certificate's serial number. -5. For each revoked intermediate, submits `NitroEnclaveVerifier.revokeCert(certPathDigest)`. -6. Returns true if any intermediate is revoked, blocking registration for the batch. +```solidity Registration Call +TEEProverRegistry.registerSigner(attestationTbs, signature, hints) +``` -`revokeCert` failures are counted but do not abort registration of other instances on the same -tick. The submitted revocations transition Layer 1 to a hit on the next cycle so subsequent -registrations can short-circuit without re-fetching the CRL. +The registry permits only its owner or manager to call this method. It delegates cryptographic and +certificate validation to `NitroValidator`, then applies Base-specific policy: -## Pending Registration Lifecycle +1. Reject attestations at least 60 minutes old. +2. Reject attestations whose second-level timestamp is greater than or equal to `block.timestamp`. +3. Require PCR0 at index zero, exactly 48 bytes, and not the debug-mode measurement. +4. Require a 65-byte uncompressed secp256k1 public key. +5. Derive the signer as the last 20 bytes of `keccak256(x || y)`. +6. Store the signer as registered and record `keccak256(PCR0)` as its image hash. -Each per-signer pipeline is keyed by Ethereum signer address. The Boundless proof slot for a -signer transitions through: +The registrar uses a 3,300-second default local freshness limit, leaving submission headroom under +the registry's 3,600-second limit. It checks freshness before every certificate, revocation, and +registration transaction so a multi-transaction cold flow stops before submitting stale material. -```mermaid Pending Registration Lifecycle lines wrap expandable -flowchart TB - Start([process_instance]) --> Recover[Recovery scan] - Recover -->|Locked slot| Wait[wait_for_request_fulfillment] - Recover -->|Fulfilled slot| Fresh{Journal fresh?} - Recover -->|All slots Unknown/Expired| Submit[Submit fresh request] - Recover -->|Blocked recovery| Submit +Before each registration attempt, and after ambiguous transaction errors, the registrar reads +`isRegisteredSigner(signer)`. An observed registration is treated as success. Retryable transaction +errors use bounded exponential backoff; reverted receipts and non-retryable errors fail the current +task. - Fresh -->|yes| Receipt[Use recovered receipt] - Fresh -->|no| Submit - Wait --> Receipt - Submit --> Wait +## Certificate Revocation - Receipt --> Send[tx_manager.send registerSigner] - Send -->|Ok| Done([Registered]) - Send -->|Retryable| Send - Send -->|ExecutionReverted| Block[Block recovery for signer] - Block --> Done -``` +`CertManager` maintains a durable revocation set and an immutable pinned AWS Nitro root. The owner +can revoke or unrevoke the root as an emergency halt, set the non-root revoker, and unrevoke +certificate identities. The revoker role can revoke non-root issuer/serial identities. Revocation +is checked during cold verification, cache reuse, and the final cached-chain walk. -A pending recovery state, a fulfilled-but-stale receipt, and an `ExecutionReverted` revert all -funnel back to a fresh submission on the next tick rather than wedging the signer. +For every registration attempt, the registrar checks the pinned root and every planned certificate +against `CertManager.isRevoked()`. A confirmed onchain revocation rejects the registration. -## Onchain Interactions +When CRL fetching is additionally enabled, the registrar: -The registrar uses the following contract calls. `TEEProverRegistry.isValidSigner()` is -intentionally not called by the registrar; that predicate is enforced by `TEEVerifier` at proof -submission time and includes an image-hash match that the registrar cannot satisfy by itself. +1. Fetches CRLs only from allowlisted AWS Nitro hosts, without redirects and with a 10 MiB response + limit. +2. Matches intermediate certificate serial numbers against the CRLs. +3. Calls `CertManager.revokeCert()` for confirmed revocations before rejecting the registration. -| Contract | Method | Caller path | -| -------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------- | -| [`TEEProverRegistry`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEProverRegistry.sol) | `registerSigner(output, proof)` | Per-signer registration transaction. | -| [`TEEProverRegistry`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEProverRegistry.sol) | `deregisterSigner(signer)` | Per-orphan deregistration transaction. | -| [`TEEProverRegistry`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEProverRegistry.sol) | `isRegisteredSigner(signer)` | Pre-check, post-error reconciliation, orphan race guard. | -| [`TEEProverRegistry`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEProverRegistry.sol) | `getRegisteredSigners()` | Once per cycle for orphan computation. | -| [`NitroEnclaveVerifier`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/NitroEnclaveVerifier.sol) | `revokeCert(certHash)` | When AWS CRL revokes an intermediate. | -| [`NitroEnclaveVerifier`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/NitroEnclaveVerifier.sol) | `revokedCerts(certHash)` | Layer-1 onchain durable revocation pre-check. | +CRL fetch and parse failures are fail-open and retried on later cycles. Confirmed onchain +revocations always fail closed. -PCR0 enforcement happens onchain at proof submission, not at registration. The registrar registers -any enclave whose Nitro attestation verifies, regardless of its PCR0. This allows the next image's -fleet to be brought up and pre-registered in advance of a hardfork; those signers cannot produce -accepted proposals until the active game implementation's `TEE_IMAGE_HASH` matches their -registered image hash. +## Orphan Deregistration -## Service Lifecycle +After registration task reconciliation, the registrar computes: -At startup, the registrar: +```text Orphan Formula +orphans = registered signers - active signers - pending signers +``` -1. Parses CLI configuration and validates it. -2. Initializes tracing and installs the `rustls` ring crypto provider. -3. Installs a signal handler that triggers a cancellation token. -4. Initializes Prometheus metrics, including L1 wallet and Boundless wallet balance monitoring. -5. Builds the L1 provider, transaction manager, AWS SDK clients, and discovery client. -6. Builds the registry client and the optional Nitro verifier client. -7. Builds the proof provider for the configured backend. -8. Starts the health server and marks readiness. -9. Starts the driver loop. +It calls `deregisterSigner()` for each orphan only when discovery completed without unresolved +instances and the configured last-known-signer grace period has expired. Deregistration removes the +registered flag and stored image hash. This flow assumes one registrar controls a given registry; +independent registrars would otherwise classify each other's signers as orphans. -The health endpoint reports ready as soon as wiring completes. Connectivity gating is intentionally -omitted because the registrar is outbound-only. +## Upgrade Compatibility -Each driver tick: +The hinted migration, shipped with [Cobalt](/upgrades/cobalt/overview), upgraded the existing `TEEProverRegistry` proxy implementation. It preserves +the proxy storage layout, owner, manager, game type, proposers, registered signers, and stored signer +image hashes. The active registrar and registry support only the new three-argument hinted API. -1. Discovers instances. -2. Processes instances concurrently. -3. Computes orphans subject to the majority-reachable guard. -4. Submits deregistration transactions for confirmed orphans. +The migration does not change: -Shutdown is driven by a cancellation token. The driver loop exits, in-flight per-instance futures -are dropped, the readiness flag clears, the `up` metric is set to zero, and the health server is -joined. +- Nitro enclave signer generation or attestation production; +- PCR0-based image selection in `TEEVerifier`; +- `AggregateVerifier` behavior; +- TEE proposal and dispute proof formats; or +- SP1 state-proof behavior. ## Operator Inputs -A registrar needs: - -- L1 RPC endpoint and chain ID. -- `TEEProverRegistry` address. -- AWS region and ALB target group ARN. -- Prover JSON-RPC port shared by the fleet. -- L1 transaction signer (local key, or remote signing endpoint plus expected address). -- Proving backend selection: `boundless` or `direct`. -- For `boundless`: marketplace RPC URL, dedicated wallet key, guest program URL, polling interval, - prove timeout, recovery attempt limit, and attestation freshness window. -- For `direct`: path to the guest ELF. -- Poll interval, prover JSON-RPC timeout, max concurrency, max transaction retries, transaction - retry delay, and the unhealthy registration warm-up window. +A registrar requires: -Optional inputs: +- an L1 RPC endpoint and `TEEProverRegistry` address; +- AWS region and ALB target group ARN; +- the prover JSON-RPC port; +- an L1 transaction signer and transaction-manager limits; +- attestation freshness, polling, timeout, concurrency, cache-grace, and retry settings; and +- health, logging, and Prometheus metrics settings. -- CRL checking enable flag. -- `NitroEnclaveVerifier` address, required when CRL checking is enabled. -- CRL fetch timeout. -- Health server bind address and port. -- Logging filter and Prometheus metrics settings. +No Boundless wallet, marketplace endpoint, RISC Zero program identifier, guest ELF, or proof-backend +selection is used. CRL monitoring is optional and requires the registrar transaction signer to hold +the configured `CertManager` revoker role. In the current CLI, +`--crl-nitro-verifier-address` or `BASE_REGISTRAR_CRL_NITRO_VERIFIER_ADDRESS` enables CRL fetching. +This legacy-named value is only an enable flag; contract discovery still follows the registry to +`NitroValidator` and then `CertManager`. ## Safety Requirements -A registrar implementation must preserve these safety properties: - -- Do not deregister live signers because of a transient AWS or VPC outage. Apply a - majority-reachable guard before any deregistration. -- Treat `Draining` and `Unhealthy` instances as part of the active set as long as their JSON-RPC - endpoint responds, so rotations do not race deregistration. -- Use a fresh random nonce per instance batch and pass it to the enclave attestation request so - the verifier journal carries an unguessable freshness commitment. -- Derive Boundless request slots deterministically from the signer address so a restarted process - can recover in-flight proving work without spending fresh proof costs. -- Reject recovered proofs whose attestation timestamp is older than `max_attestation_age` to keep - recovered proofs strictly inside the onchain `MAX_AGE` window. -- Block recovery for a signer after an `ExecutionReverted` so the next cycle proves freshly - rather than re-submitting the same bad proof. -- Recheck `isRegisteredSigner` after a transaction error to absorb fee-bump and nonce-race false - negatives. -- Recheck `isRegisteredSigner` for every orphan candidate immediately before submitting a - deregistration, so a concurrent writer or earlier in-flight tx cannot cause a redundant - deregistration transaction. -- When CRL checking is enabled, run the onchain durable revocation pre-check before fetching - network CRLs so a previously revoked intermediate cannot be silently rehabilitated. -- Restrict CRL fetches to allowlisted hosts and bound the response size to defeat SSRF and - resource-exhaustion attacks. -- Treat unavailable AWS APIs, unreachable prover endpoints, transient RPC errors, and Boundless - polling failures as retryable conditions for the next tick rather than as deregistration or - failure signals. +A conforming implementation must preserve these properties: + +- Preserve the exact signed COSE encodings when constructing the attestation TBS. +- Match the attested signer and deterministic nonce before any transaction is submitted. +- Pin the AWS Nitro root and reject malformed, expired, revoked, or parent-mismatched chains. +- Generate hints in the exact verifier order and rely on onchain checks for every supplied inverse. +- Cache certificates parent-first and recover by reading onchain state after every ambiguous result. +- Recheck freshness before each costly transaction in a cold registration. +- Recheck registration state before submission and after ambiguous transaction errors. +- Do not deregister signers while discovery is unresolved or during the configured grace period. +- Keep PCR0 acceptance at proof-submission time so image rotations can be staged safely. +- Do not add an unhinted, ZK, or externally proved runtime fallback to the hinted registration path. diff --git a/docs/upgrades/cobalt/overview.mdx b/docs/upgrades/cobalt/overview.mdx index 6d941bcda..9ae40aaba 100644 --- a/docs/upgrades/cobalt/overview.mdx +++ b/docs/upgrades/cobalt/overview.mdx @@ -39,7 +39,7 @@ Dynamic Upgrades introduces an Ethereum smart contract that stores upgrade times **Base** · Infrastructure -Cobalt changes how new Trusted Execution Environment (TEE) signers are registered: AWS Nitro attestations are trustlessly verified fully onchain using checked P-384 hints, replacing the RISC Zero and Boundless proving flow. Registration is faster and no longer depends on an external offchain proving service. Enclave key generation, PCR0 image selection, and proof verification are unchanged. See [Registrar](/specifications/base-protocol/proofs/registrar) for the full specification. +Cobalt changes how new Trusted Execution Environment (TEE) signers are registered: AWS Nitro attestations are trustlessly verified fully onchain using checked P-384 hints, replacing the RISC Zero and Boundless proving flow. Registration is faster and no longer depends on an external offchain proving service. Enclave key generation, PCR0 image selection, and proof verification are unchanged. See [Hinted TEE Registration](/specifications/base-protocol/proofs/hinted-tee-registration) for an overview and [Registrar](/specifications/base-protocol/proofs/registrar) for the full specification. From 93b55fd93cbdd2d313cce9a8e2aeb70425b5bafe Mon Sep 17 00:00:00 2001 From: roethke Date: Wed, 30 Sep 2026 16:01:37 -0700 Subject: [PATCH 2/4] docs(proofs): fold Hinted TEE Registration into the Registrar page Per Leopold: the hinted flow is the Registrar's architecture going forward, and only the migration itself is upgrade-specific. Drop the standalone page, replace Upgrade Compatibility with a Hinted Registration Migration section (previous flow, why, what was preserved), and link that anchor from the Cobalt overview. Generated with Claude Code Co-Authored-By: Claude --- docs/AGENTS.md | 2 +- docs/docs.json | 1 - docs/llms-full.txt | 2 - docs/llms.txt | 2 - .../proofs/hinted-tee-registration.mdx | 77 ------------------- .../base-protocol/proofs/registrar.mdx | 28 ++++--- docs/upgrades/cobalt/overview.mdx | 2 +- 7 files changed, 21 insertions(+), 93 deletions(-) delete mode 100644 docs/specifications/base-protocol/proofs/hinted-tee-registration.mdx diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 8bdd98ecb..939172282 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -57,7 +57,7 @@ npx skills add base/base-skills |Specifications/Specifications/Base Protocol/Consensus:specifications/base-protocol/consensus/specification,specifications/base-protocol/consensus/derivation,specifications/base-protocol/consensus/p2p,specifications/base-protocol/consensus/rpc |Specifications/Specifications/Base Protocol/Execution:specifications/base-protocol/execution/l2-execution-engine,specifications/base-protocol/execution/precompiles,specifications/base-protocol/execution/predeploys,specifications/base-protocol/execution/preinstalls |Specifications/Specifications/Base Protocol/Bridging:specifications/base-protocol/bridging/standard-bridges,specifications/base-protocol/bridging/deposits,specifications/base-protocol/bridging/withdrawals,specifications/base-protocol/bridging/cross-domain-messengers,specifications/base-protocol/bridging/base-solana-bridge -|Specifications/Specifications/Base Protocol/Proofs:specifications/base-protocol/proofs/overview,specifications/base-protocol/proofs/challenger,specifications/base-protocol/proofs/proposer,specifications/base-protocol/proofs/hinted-tee-registration,specifications/base-protocol/proofs/registrar,specifications/base-protocol/proofs/tee-prover,specifications/base-protocol/proofs/zk-prover,specifications/base-protocol/proofs/proof-contracts +|Specifications/Specifications/Base Protocol/Proofs:specifications/base-protocol/proofs/overview,specifications/base-protocol/proofs/challenger,specifications/base-protocol/proofs/proposer,specifications/base-protocol/proofs/registrar,specifications/base-protocol/proofs/tee-prover,specifications/base-protocol/proofs/zk-prover,specifications/base-protocol/proofs/proof-contracts |Specifications/Specifications/B20:specifications/b20/index,specifications/b20/introduction,specifications/b20/changelog |Specifications/Specifications/B20/Concepts:specifications/b20/concepts/token-types,specifications/b20/concepts/policies,specifications/b20/concepts/roles-and-pause,specifications/b20/concepts/multipliers |Specifications/Specifications/B20/Reference:specifications/b20/reference/interfaces,specifications/b20/reference/constants,specifications/b20/reference/errors,specifications/b20/reference/events diff --git a/docs/docs.json b/docs/docs.json index 8dcc89934..afaf71715 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -233,7 +233,6 @@ "specifications/base-protocol/proofs/overview", "specifications/base-protocol/proofs/challenger", "specifications/base-protocol/proofs/proposer", - "specifications/base-protocol/proofs/hinted-tee-registration", "specifications/base-protocol/proofs/registrar", "specifications/base-protocol/proofs/tee-prover", "specifications/base-protocol/proofs/zk-prover", diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 365909e0c..6f4d267a7 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -282,8 +282,6 @@ const client = createPublicClient({ chain: base, transport: http() }) - [Proposer](https://docs.base.org/specifications/base-protocol/proofs/proposer): Specification of the proposer, an offchain service that turns canonical L2 checkpoint ranges into AggregateVerifier games on L1. -- [Hinted TEE Registration](https://docs.base.org/specifications/base-protocol/proofs/hinted-tee-registration): Migration from external ZK proving to checked P-384 hints for registering new AWS Nitro Enclave signers. - - [Registrar](https://docs.base.org/specifications/base-protocol/proofs/registrar): Specification of the registrar and the hinted P-384 flow used to register AWS Nitro Enclave signer identities on L1. - [TEE Prover](https://docs.base.org/specifications/base-protocol/proofs/tee-prover): Specification of the TEE prover, an offchain service that re-executes L2 block ranges inside AWS Nitro Enclaves to produce signed proof material for AggregateVerifier games. diff --git a/docs/llms.txt b/docs/llms.txt index fd9e7aabb..55e1b5c26 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -216,8 +216,6 @@ - [Proposer](https://docs.base.org/specifications/base-protocol/proofs/proposer): Specification of the proposer, an offchain service that turns canonical L2 checkpoint ranges into AggregateVerifier games on L1. -- [Hinted TEE Registration](https://docs.base.org/specifications/base-protocol/proofs/hinted-tee-registration): Migration from external ZK proving to checked P-384 hints for registering new AWS Nitro Enclave signers. - - [Registrar](https://docs.base.org/specifications/base-protocol/proofs/registrar): Specification of the registrar and the hinted P-384 flow used to register AWS Nitro Enclave signer identities on L1. - [TEE Prover](https://docs.base.org/specifications/base-protocol/proofs/tee-prover): Specification of the TEE prover, an offchain service that re-executes L2 block ranges inside AWS Nitro Enclaves to produce signed proof material for AggregateVerifier games. diff --git a/docs/specifications/base-protocol/proofs/hinted-tee-registration.mdx b/docs/specifications/base-protocol/proofs/hinted-tee-registration.mdx deleted file mode 100644 index 27553b7bc..000000000 --- a/docs/specifications/base-protocol/proofs/hinted-tee-registration.mdx +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: "Hinted TEE Registration" -description: "Migration from external ZK proving to checked P-384 hints for registering new AWS Nitro Enclave signers." ---- - -The [Cobalt](/upgrades/cobalt/overview) upgrade replaced the RISC Zero and Boundless flow used to register new TEE signers with direct -AWS Nitro attestation verification using checked P-384 hints. - -The change makes registration faster and removes the dependency on an external ZK proving service. -It does not change how enclaves produce TEE state proofs or how those proofs are verified. - -## Previous Flow - -The registrar previously sent each Nitro attestation to an external proving service. That service -produced a RISC Zero proof showing that the attestation and its AWS certificate chain were valid. -The registrar then submitted the proof to `TEEProverRegistry` on L1. - -This added an external availability dependency and could delay new signer registration while the ZK -proof was generated. - -## New Flow - -The registrar now: - -1. Fetches a fresh AWS Nitro attestation for the new enclave signer. -2. Generates P-384 modular inverse hints locally. -3. Verifies and caches any missing AWS certificates through `CertManager`. -4. Submits the attestation, its signature, and the final hint stream to `TEEProverRegistry`. -5. Records the signer and its attested PCR0 after all onchain checks pass. - -`TEEProverRegistry` permits only its owner or manager to perform the final registration. Certificate -cache writes are permissionless because `CertManager` verifies the certificate, its parent chain, -and every supplied hint before storing it. - -## Checked Hints - -P-384 verification requires many modular inversions. Computing those inversions in the EVM is -expensive, but checking a proposed inverse is comparatively cheap. The registrar computes the -values offchain and supplies them as hints. - -Every hint is checked onchain before use. Incorrect, missing, or surplus hints revert, so the hint -generator is required for liveness but is not trusted for correctness. A faulty or malicious hint -cannot make an invalid certificate or attestation pass. - -## Certificate Caching - -The AWS Nitro root is pinned in `CertManager`. Non-root CA certificates and the leaf certificate are -verified and cached in parent-first order. Later registrations can reuse cached certificates while -they remain valid and unrevoked. - -For a typical Nitro certificate chain: - -| Cache state | Registration transactions | -| --- | ---: | -| Empty | 5 | -| CA chain cached, new leaf | 2 | -| CA chain and leaf cached | 1 | - -The registrar reads onchain cache state before each write, allowing it to resume after a partial -registration or process restart without repeating completed certificate transactions. - -## Unchanged Behavior - -This migration does not change: - -- signer key generation inside the Nitro Enclave; -- the attestation fields produced by AWS Nitro; -- PCR0-based enclave image selection; -- TEE proposal or dispute proof formats; -- `TEEVerifier` or `AggregateVerifier` behavior; or -- SP1 state-proof verification. - -The existing `TEEProverRegistry` proxy was upgraded without changing its storage layout. Existing -owners, managers, proposers, registered signers, and signer image hashes are preserved. - -For the full registration lifecycle, validation rules, revocation handling, and operator -requirements, see the [Registrar specification](/specifications/base-protocol/proofs/registrar). diff --git a/docs/specifications/base-protocol/proofs/registrar.mdx b/docs/specifications/base-protocol/proofs/registrar.mdx index 9cd7d01d7..3b4d704c5 100644 --- a/docs/specifications/base-protocol/proofs/registrar.mdx +++ b/docs/specifications/base-protocol/proofs/registrar.mdx @@ -9,10 +9,10 @@ It discovers Base TEE prover instances, obtains an AWS Nitro attestation for eac generates P-384 verification hints locally, caches the attestation certificate chain on L1, and submits the final signer registration. -This flow replaces the previous RISC Zero attestation proof and external Boundless proving path. -There is no runtime backend selector or fallback to ZK verification. The TEE proof format, -`TEEVerifier`, `AggregateVerifier`, `TEE_IMAGE_HASH`, and SP1 state-proof behavior are unchanged. -Registration requires the hinted-compatible registrar and prover-host signer API. +This flow replaced the previous RISC Zero attestation proof and external Boundless proving path in +[Cobalt](/upgrades/cobalt/overview); see [Hinted Registration Migration](#hinted-registration-migration). +There is no runtime backend selector or fallback to ZK verification. Registration requires the +hinted-compatible registrar and prover-host signer API. ## Responsibilities @@ -258,16 +258,26 @@ instances and the configured last-known-signer grace period has expired. Deregis registered flag and stored image hash. This flow assumes one registrar controls a given registry; independent registrars would otherwise classify each other's signers as orphans. -## Upgrade Compatibility +## Hinted Registration Migration -The hinted migration, shipped with [Cobalt](/upgrades/cobalt/overview), upgraded the existing `TEEProverRegistry` proxy implementation. It preserves -the proxy storage layout, owner, manager, game type, proposers, registered signers, and stored signer +[Cobalt](/upgrades/cobalt/overview) replaced ZK-proved signer registration with the hinted flow +described on this page. + +Before Cobalt, the registrar sent each Nitro attestation to an external proving service (Boundless +or a self-hosted RISC Zero prover). That service produced a RISC Zero proof that the attestation +and its AWS certificate chain were valid, and the registrar submitted the proof to +`TEEProverRegistry`. This added an external availability dependency and could delay new signer +registration while the proof was generated. The hinted flow verifies the attestation directly on +L1, so registration is faster and no longer depends on an offchain proving service. + +The migration upgraded the existing `TEEProverRegistry` proxy implementation. It preserved the +proxy storage layout, owner, manager, game type, proposers, registered signers, and stored signer image hashes. The active registrar and registry support only the new three-argument hinted API. -The migration does not change: +The migration did not change: - Nitro enclave signer generation or attestation production; -- PCR0-based image selection in `TEEVerifier`; +- PCR0-based image selection in `TEEVerifier` or the `TEE_IMAGE_HASH` it checks; - `AggregateVerifier` behavior; - TEE proposal and dispute proof formats; or - SP1 state-proof behavior. diff --git a/docs/upgrades/cobalt/overview.mdx b/docs/upgrades/cobalt/overview.mdx index 9ae40aaba..d4ea1df7d 100644 --- a/docs/upgrades/cobalt/overview.mdx +++ b/docs/upgrades/cobalt/overview.mdx @@ -39,7 +39,7 @@ Dynamic Upgrades introduces an Ethereum smart contract that stores upgrade times **Base** · Infrastructure -Cobalt changes how new Trusted Execution Environment (TEE) signers are registered: AWS Nitro attestations are trustlessly verified fully onchain using checked P-384 hints, replacing the RISC Zero and Boundless proving flow. Registration is faster and no longer depends on an external offchain proving service. Enclave key generation, PCR0 image selection, and proof verification are unchanged. See [Hinted TEE Registration](/specifications/base-protocol/proofs/hinted-tee-registration) for an overview and [Registrar](/specifications/base-protocol/proofs/registrar) for the full specification. +Cobalt changes how new Trusted Execution Environment (TEE) signers are registered: AWS Nitro attestations are trustlessly verified fully onchain using checked P-384 hints, replacing the RISC Zero and Boundless proving flow. Registration is faster and no longer depends on an external offchain proving service. Enclave key generation, PCR0 image selection, and proof verification are unchanged. See [Hinted Registration Migration](/specifications/base-protocol/proofs/registrar#hinted-registration-migration) for what changed and [Registrar](/specifications/base-protocol/proofs/registrar) for the full specification. From 372ab042008cc89dbe4edf7fd759d73b82eede9f Mon Sep 17 00:00:00 2001 From: roethke Date: Thu, 1 Oct 2026 08:57:56 -0700 Subject: [PATCH 3/4] docs(proofs): apply Leopold's Registrar review suggestions - Condense Responsibilities into a high-level summary - State that revocation/expiry does not invalidate registered signers - Drop the repeated no-fallback safety bullet Co-authored-by: Leopold Joy Generated with Claude Code Co-Authored-By: Claude --- .../base-protocol/proofs/registrar.mdx | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/docs/specifications/base-protocol/proofs/registrar.mdx b/docs/specifications/base-protocol/proofs/registrar.mdx index 3b4d704c5..3e0fffc98 100644 --- a/docs/specifications/base-protocol/proofs/registrar.mdx +++ b/docs/specifications/base-protocol/proofs/registrar.mdx @@ -16,17 +16,9 @@ hinted-compatible registrar and prover-host signer API. ## Responsibilities -A conforming registrar performs the following work: - -1. Discover TEE prover instances behind the configured AWS load balancer. -2. Fetch each enclave's secp256k1 public key and a fresh Nitro attestation bound to that signer. -3. Parse the COSE and X.509 data without changing the signed encodings. -4. Validate the expected signer, nonce, PCR0, trust anchor, certificate order, and freshness locally. -5. Generate the checked P-384 inverse hints required by the onchain verifier. -6. Reuse valid cached certificates and cache missing certificates in parent-first order. -7. Submit the final attestation to `TEEProverRegistry.registerSigner()`. -8. Optionally monitor AWS certificate revocation lists and persist confirmed revocations on L1. -9. Deregister signers whose backing prover instances are no longer active. +The registrar discovers TEE prover instances, obtains and validates signer attestations, generates +P-384 hints, caches certificates, and registers signers. It also deregisters orphaned signers and can +monitor AWS certificate revocation lists. The registrar does not select the accepted enclave image. Registration records the attested PCR0, but [`TEEVerifier`](https://github.com/base/contracts/blob/main/src/L1/proofs/tee/TEEVerifier.sol) @@ -228,9 +220,12 @@ task. ## Certificate Revocation `CertManager` maintains a durable revocation set and an immutable pinned AWS Nitro root. The owner -can revoke or unrevoke the root as an emergency halt, set the non-root revoker, and unrevoke -certificate identities. The revoker role can revoke non-root issuer/serial identities. Revocation -is checked during cold verification, cache reuse, and the final cached-chain walk. +can revoke the root to halt new registrations, unrevoke the root, set the non-root revoker, and +unrevoke certificate identities. The revoker role can revoke non-root issuer/serial identities. +Revocation is checked during cold verification, cache reuse, and the final cached-chain walk. + +Certificate revocation and expiration do not invalidate previously registered signers. Deregister +affected signers separately. For every registration attempt, the registrar checks the pinned root and every planned certificate against `CertManager.isRevoked()`. A confirmed onchain revocation rejects the registration. @@ -313,4 +308,3 @@ A conforming implementation must preserve these properties: - Recheck registration state before submission and after ambiguous transaction errors. - Do not deregister signers while discovery is unresolved or during the configured grace period. - Keep PCR0 acceptance at proof-submission time so image rotations can be staged safely. -- Do not add an unhinted, ZK, or externally proved runtime fallback to the hinted registration path. From b8223749624b2d2191dbcb9f9837e6237b46cd94 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Thu, 1 Oct 2026 18:08:29 +0200 Subject: [PATCH 4/4] docs(proofs): align hinted registration references (#2045) * docs(proofs): align hinted registration references * docs(proofs): keep hinted API references readable --- docs/llms-full.txt | 2 +- docs/llms.txt | 2 +- .../base-protocol/proofs/proof-contracts.mdx | 219 +++++++++--------- .../base-protocol/proofs/tee-prover.mdx | 23 +- 4 files changed, 131 insertions(+), 115 deletions(-) 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