diff --git a/docs/llms-full.txt b/docs/llms-full.txt index e65d4a976..6625f06c8 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -282,13 +282,13 @@ 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. +- [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. - [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 2d8a8504d..344bc9b2d 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -216,13 +216,13 @@ - [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. +- [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. - [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/registrar.mdx b/docs/specifications/base-protocol/proofs/registrar.mdx index 48668a06a..3e0fffc98 100644 --- a/docs/specifications/base-protocol/proofs/registrar.mdx +++ b/docs/specifications/base-protocol/proofs/registrar.mdx @@ -1,412 +1,310 @@ --- 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 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 -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 +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) +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 +``` -At startup, the registrar connects to: +The onchain validation stack contains three contracts: -- 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 +| 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. | -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. +`TEEProverRegistry` holds an immutable `NitroValidator` reference. The registrar discovers +`CertManager` through `TEEProverRegistry.NITRO_VALIDATOR()` and `NitroValidator.certManager()`; +operators do not configure those addresses separately. ## Driver Loop -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 -``` - -`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. +The registrar runs one polling loop: -The registrar supports two backends: +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. -| 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. | +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. -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. +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. -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. +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. -### Restart Recovery +## Attestation Challenge -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: +Each signer receives a deterministic 32-byte nonce: -```text Request Index Derivation -request_index(signer, attempt) = u32::from_be_bytes(keccak256(signer || attempt)[..4]) +```text Attestation Nonce +keccak256( + "base-proof-tee-registrar:attestation-nonce:v1" || + teeProverRegistryAddress || + signerAddress +) ``` -For each signer, the registrar probes `max_recovery_attempts` consecutive deterministic slots -before submitting a fresh request. The action depends on the slot status: +The registrar requests one attestation per signer with that nonce. It rejects the response unless: -| 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 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. -A `RequestIsNotLocked` revert encountered mid-scan is treated as in-flight and short-circuits to -waiting on that slot. +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. -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. +## Registration Plan -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. +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. -## Registration Transactions +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. -For each unregistered signer, the registrar: +The parsed plan contains: -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 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`. -The transaction retry rules are: +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. -| 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. | +## P-384 Hints -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. +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. -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. +For every division by `b` modulo a prime `m`, the contract checks: -## Orphan Deregistration +```text Inverse Check +b * hint == 1 (mod m) +``` -After processing every instance, the registrar reconciles the onchain signer set against the -active set: +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. -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. +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. -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. +## Certificate Cache -This procedure assumes a single registrar per `TEEProverRegistry`. Two registrars sharing a -registry would each treat the other's signers as orphans. +`CertManager` stores the pinned AWS Nitro root at deployment. The registrar processes every +remaining certificate in parent-first order: -## Certificate Revocation +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. -When the operator enables CRL checking, the registrar enforces revocation using two layers in -order. Both are required to make CRL handling safe. +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. -### Layer 1: Onchain Durable Revocation Pre-Check +For a typical Nitro chain with three non-root CAs, transaction counts are: -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. +| 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 | -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. +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. -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. +## Final Registration -### Layer 2: AWS CRL Distribution Points +After the cache is ready, the registrar calls: -For intermediates that pass Layer 1, the registrar: +```solidity Registration Call +TEEProverRegistry.registerSigner(attestationTbs, signature, hints) +``` -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. +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: -`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. +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. -## Pending Registration Lifecycle +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. -Each per-signer pipeline is keyed by Ethereum signer address. The Boundless proof slot for a -signer transitions through: +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. -```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 +## Certificate Revocation - Fresh -->|yes| Receipt[Use recovered receipt] - Fresh -->|no| Submit - Wait --> Receipt - Submit --> Wait +`CertManager` maintains a durable revocation set and an immutable pinned AWS Nitro root. The owner +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. - Receipt --> Send[tx_manager.send registerSigner] - Send -->|Ok| Done([Registered]) - Send -->|Retryable| Send - Send -->|ExecutionReverted| Block[Block recovery for signer] - Block --> Done -``` +Certificate revocation and expiration do not invalidate previously registered signers. Deregister +affected signers separately. -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. +## Hinted Registration Migration -Each driver tick: +[Cobalt](/upgrades/cobalt/overview) replaced ZK-proved signer registration with the hinted flow +described on this page. -1. Discovers instances. -2. Processes instances concurrently. -3. Computes orphans subject to the majority-reachable guard. -4. Submits deregistration transactions for confirmed orphans. +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. -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. +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. -## Operator Inputs +The migration did not change: -A registrar needs: +- Nitro enclave signer generation or attestation production; +- 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. + +## Operator Inputs -- 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. 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 diff --git a/docs/upgrades/cobalt/overview.mdx b/docs/upgrades/cobalt/overview.mdx index 89b9cba54..d096000c6 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 Registration Migration](/specifications/base-protocol/proofs/registrar#hinted-registration-migration) for what changed and [Registrar](/specifications/base-protocol/proofs/registrar) for the full specification.