> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cowboy.inc/llms.txt
> Use this file to discover all available pages before exploring further.

# CIP-23: TEE Execution and Composite Attestation (v3)

> Hardware-backed, end-to-end verifiable TEE execution for off-chain AI and privacy workloads. v3 is a single consolidated specification — it splits the verification philosophy (TeeAttested vs Deterministic), verifies on-chain DCAP via a SNARK-compressed proof with governance-anchored collateral and a TCB-level policy, adopts multi-root trust (chip ∧ operator / Proof-of-Cloud) post-TEE.Fail, and folds in the prior v2 alignment (three-layer chain, CIP-13 delegation, canonical TEE types).

<Note>
  **Status:** Draft<br />
  **Type:** Standards Track<br />
  **Category:** Core<br />
  **Version:** v3 (consolidated) — supersedes v1 (2026-04-20) and the v2 alignment revision (2026-05-26); **v3.1 amendment (2026-07-20, COW-2703)**<br />
  **Updated:** 2026-07-20<br />
  **Requires:** CIP-2 (off-chain compute), CIP-3 (fee model), CIP-10 (containers)<br />
  **Related:** CIP-1 (deferred callbacks), CIP-9 (off-chain storage / CBFS), CIP-13 (delegation), CIP-24 (CBSS)
</Note>

> **v3.1 amendment (2026-07-20, COW-2703).** Pins the Job-path `result_payload` canonical encoding (§3.5.1a) and the `Deterministic` cross-runner byte-match (§3.5.2; primary home CIP-2 §9): `result.data` was hashed/compared via non-canonical `serde_json` (unpinned object-key order + `f64` formatting), so honest producers/verifiers could hash the same logical result to different bytes → wrongful reject or **wrongful slashing**. The amendment adopts the CIP-11 §12.6 **CBOR-float64 profile** (sorted keys, integral-normalizing, negative-int), `VERSION`-prefixed and single-sourced in `cowboy-protocol-codec` with a frozen cross-stack fixture, gated by a dormant `CANONICAL_RESULT_PREIMAGE_ACTIVATION_HEIGHT` (no-op until governance activates; fork-free). **Scope: the CAE preimage (§3.5.1a, all `tee_required` results — `binary64` admitted) and the `Deterministic` full-result byte-match (§3.5.2, no-floats), with grouping keyed at a single verification-block height.** An INVALID result (NaN/±Inf/non-integral `binary64`/over-cap) has no canonical key → cannot join the equality group and is dispositioned by the **existing threshold-gated byte-mismatch slash** — no new `result_schema` predicate (gating a slash on `data_format`, free-form per CIP-2 §1, would itself split consensus): slashed as a §6.1 invalid reveal only when the valid cohort **reaches `threshold`** (gated per-result on inclusion ≥ activation); a **sub-`threshold`** valid cohort (`ThresholdNotMet`) or an all-INVALID round both fail with **no** slash. The threshold gate is symmetric — a lone below-quorum valid result neither slashes an honest fractional majority (F1) nor lets a defector escape an above-quorum one. A single-runner `tee_required` INVALID result is the all-INVALID case at N=1 → rejected, no slash. The `binary64`-in-canonical-preimage vs whitepaper-no-floats (WP §207) tension is an **open governance item that blocks activation, not merge**. Caps are a `CANONICAL_RESULT_VERSION`-bumped codec constant (not a free parameter). `MajorityVote`/`StructuredMatch` are field-scoped/tolerant (CIP-2 §9/§9.1) and out of scope — a tracked exposure, not yet resolved.
>
> **v3 changelog (vs v2).** Single complete document (no more Part I/II/III layering). New: (1) explicit **verification-philosophy split** — `TeeAttested` (confidential, 1-replica, attestation-as-verification) vs `Deterministic` (non-confidential, reproducible, byte-match), §3.4; (2) on-chain DCAP verified as a **SNARK-compressed proof** with **governance-anchored collateral** and a **TCB-level policy step**, §3.8; (3) **multi-root trust** `chip_root ∧ operator_root` (Proof-of-Cloud) and an explicit threat model post-**TEE.Fail**, §3.6, §6; (4) `attest_digest` circularity fixed and `nonce` made unpredictable, §3.5; (5) implementation reconciled — the shipped **interim trusted-key model** is documented and repurposed as the operator root, §3.8.2; (6) P2 fixes — gas target 20M, measurement 48-store/32-index, SGX rejected for attested modes, canonical TEE-type naming. Folds in v2: three-layer chain (§3.14), CIP-13 delegation (§3.15), canonical TEE types.
>
> **Refinement (2026-06-18):** §3.8.8 — **proving-latency split.** The chip-root SNARK is verified at **registration / renewal** (\~7 days), not per call: the binding captures the platform key (`bound_quote_key`), and per-call acceptance is a light path (quote-sig vs bound key + REPORTDATA + freshness/replay, no SNARK). Driven by zkVM DCAP proving (\~30 s–min) not fitting the 75-block freshness window.

***

## 1. Abstract

This proposal specifies **TEE Execution** for Cowboy — a hardware-backed, end-to-end verifiable path for off-chain AI and privacy-sensitive workloads. It standardizes a **Composite Attestation Envelope (CAE)** that binds an Intel TDX (or AMD SEV-SNP / AWS Nitro) CPU quote together with an NVIDIA NCC GPU report and a service signature into one on-chain-verifiable receipt, and upgrades the **TEE Verifier** system actor at `0x05` into a real attestation pipeline.

Three properties define v3:

* **Two verification philosophies, kept apart (§3.4).** `TeeAttested` is for **confidential** outputs: a single runner, where the attestation *is* the verification (no cross-runner reproduction). `Deterministic` is for **non-confidential, reproducible** outputs: a committee re-executes and byte-matches the *result*. v1/v2 conflated these; they are incompatible (a confidential output is never byte-identical across runners) and are now separated.
* **On-chain verification is a SNARK-compressed DCAP proof (§3.8).** The chain runs `verify(proof, public_inputs)`; the circuit performs quote parsing, the vendor X.509 chain, ECDSA, and **TCB-level evaluation**. This makes verification deterministic (time-dependent collateral becomes a circuit input anchored to a governance snapshot) and cheap (≈ 1 order of magnitude gas reduction vs a hand-rolled on-chain chain walk).
* **Multi-root trust (§3.6).** After **TEE.Fail (2025-10)** demonstrated physical extraction of attestation keys (and forged TDX attestations on a live chain), a single hardware root is insufficient. v3 requires `chip_root ∧ operator_root`, where the operator root is a Proof-of-Cloud / deployment endorsement.

This CIP does **not** require every runner to run in a TEE; non-TEE runners continue to serve `None`, `EconomicBond`, `MajorityVote`, `StructuredMatch`, and `SemanticSimilarity` jobs unchanged.

***

## 2. Motivation

CIP-2 introduces a `tee_required` flag and a `Deterministic` verification mode and reserves `0x05` for a TEE Verifier, but the original code was scaffolding (`verify()` returned `Ok(())`; the dispatcher filtered on a self-declared boolean). A large body of Cowboy design — PythonVM × TEE secure-kernel patterns, the whitepaper §5.5 "TEE option", CIP-10 §12.3 `BillingAttestation` — presumes a real attestation pipeline. This CIP defines it.

The 2026-Q2 landscape dictates concrete choices: Intel IAS/EPID reached EOL (2025-04-02), shifting the ecosystem to DCAP / Intel Trust Authority and to VM-level confidential computing (TDX, SEV-SNP); NVIDIA Confidential Compute (NCC) on H100/H200/Blackwell is the de facto GPU TEE for AI; Confidential Containers (CoCo) + Trustee are the deployable platform layer.

Two developments since v1 reshape the design (§3.4, §3.6, §3.8):

1. **TEE.Fail (2025-10)** — a \< \$1,000 DDR5 physical interposer extracts signing/attestation keys from Intel TDX/SGX and AMD SEV-SNP (deterministic AES-XTS, no replay protection), and was used to **forge TDX attestations on Ethereum BuilderNet**. A forged quote *can* chain to a vendor root. The single-hardware-root assumption is therefore unsafe → **multi-root trust** (§3.6).
2. **On-chain DCAP matured** — production SNARK-compressed DCAP verifiers (e.g. Automata) verify SGX/TDX attestations on EVM at \~1/10th the gas, and resolve the determinism problem by moving time-dependent checks into the circuit → **SNARK verification** (§3.8).

***

## 3. Specification

### 3.1 Terminology

* **CVM** — Confidential Virtual Machine (TDX Trust Domain, SEV-SNP VM, Nitro Enclave).
* **NCC** — NVIDIA Confidential Compute; per-GPU attestation signed by an NVIDIA-rooted key plus an EAT token from the NVIDIA Remote Attestation Service (NRAS).
* **Quote** — a signed, hardware-generated attestation bound to a measurement and caller-supplied `user_data`.
* **Measurement** — a hash of the TEE's initial state (TDX `MRTD`, SNP `LAUNCH_DIGEST`, Nitro `PCR0`; further Nitro PCRs ride in `pcr_extensions`).
* **CAE** — Composite Attestation Envelope (§3.5).
* **CollateralSnapshot** — a governance-published bundle of vendor collateral (root certs, TCBInfo, QEIdentity, CRLs, NRAS root) selected by block height; the *only* source of truth for time-dependent validity on-chain (§3.8.4).
* **Measurement Binding** — on-chain Registry record tying a runner to CPU/GPU measurements, a service pubkey, and an **operator-root reference**, established at registration (§3.9).
* **Chip root / Operator root** — the two trust roots of §3.6.
* **Freshness Anchor** — `(nonce, deadline, generated_at)` in a CAE (§3.5).

### 3.2 System Actor Map (authoritative)

| Address | Actor                  | Role                                                               |
| ------- | ---------------------- | ------------------------------------------------------------------ |
| `0x01`  | Runner Registry        | Stake, capabilities, **measurement binding** (+ operator-root ref) |
| `0x02`  | Job Dispatcher         | VRF selection, candidate filter (TEE filter, §3.10)                |
| `0x03`  | Result Verifier        | Commit-reveal, mode-aware verification dispatch (§3.11)            |
| `0x04`  | Secrets Manager / CBSS | TEE-gated secret release (wired to `0x05`)                         |
| `0x05`  | **TEE Verifier**       | CAE verification pipeline (§3.8)                                   |

This table matches `runner/src/system_actors.rs` (`TEE_VERIFIER = 0x05`, `SECRETS_MANAGER`/CBSS `= 0x04`, …); **that code is authoritative** and wins over any conflicting `CLAUDE.md` / README listing.

### 3.3 Hardware Stack

| Tier              | Choice                                                              | Role                                                                                                |
| ----------------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Primary CPU**   | Intel TDX                                                           | Confidential VM                                                                                     |
| **Primary GPU**   | NVIDIA NCC (H100 / H200 / Blackwell)                                | Confidential GPU for AI                                                                             |
| **Secondary CPU** | AMD SEV-SNP, AWS Nitro Enclaves                                     | Alternate backends, same CAE                                                                        |
| **Legacy**        | Intel SGX                                                           | `EconomicBond` only — **rejected** for `TeeAttested` / `Deterministic` (IAS/EPID EOL; no live root) |
| **Enablement**    | Confidential Containers (CoCo) + Trustee / KBS                      | Attestation agent, key broker                                                                       |
| **Attestation**   | Intel DCAP / Trust Authority; NVIDIA NRAS; AMD VCEK; AWS Nitro root | Quote collateral                                                                                    |

Canonical TEE-type strings: **`{"tdx","sev","nitro"}`** are eligible for `TeeAttested`/`Deterministic`; **`"sgx"`** is legacy (`EconomicBond` only). Struct enum variants and `registry.rs::CANONICAL_TEE_TYPES` MUST use these same lowercase strings.

Base CVM image: `cowboy/runner-tee-base:v1` (extends CIP-10 §5.5), reproducibly built so its measurement is publicly verifiable.

### 3.4 Verification modes — `TeeAttested` vs `Deterministic`

A confidential output (encrypted per-recipient) is **never byte-identical across runners**, so it cannot be committee-matched. v3 therefore defines two distinct modes:

| Mode                | Output                                                          | Committee | What verifies the result                                                                                                                    |
| ------------------- | --------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **`TeeAttested`**   | **confidential** (enclave-only / encrypted to requester)        | **1**     | The CAE *is* the verification — no reproduction, no byte-match                                                                              |
| **`Deterministic`** | **non-confidential** (public result or published `result_hash`) | ≥ 1       | Committee re-executes and **byte-matches the result** (CIP-2 commit-reveal, on the canonical form — §3.5.2); each result also carries a CAE |

Rules:

* A `tee_required` job **defaults to `TeeAttested`**. Selecting `Deterministic` requires the submitter to declare the output non-confidential.
* For both modes every TEE result MUST carry a `CompositeAttestation` that passes §3.8.
* `tee_type` eligibility: `tdx` / `sev` / `nitro` → both modes; `sgx` → neither (legacy, `EconomicBond` only).

> **Wire encoding (normative — matches `cowboy-protocol-codec::job_spec::VerificationConfig`).** `TeeAttested` and `Deterministic` name verification **philosophies**, not `VerificationMode` enum tags. There is **no `TeeAttested` tag** in the on-chain `VerificationMode` enum; its wire tags are `None=0, EconomicBond=1, MajorityVote=2, StructuredMatch=3, Deterministic=4, SemanticSimilarity=5` (single `u8`). TEE attestation is carried by the sibling **`tee_required: bool`** field of `VerificationConfig` (plus **`required_tee_type: Option<String>`**, ≤128 B). The two philosophies map to the wire form as: **`TeeAttested`** = `tee_required = true` with a **confidential single-runner** `VerificationMode` (`None` / `EconomicBond`); **`Deterministic`** = `VerificationMode::Deterministic` (tag 4) with `tee_required == true` (the node enforces that `Deterministic` requires `tee_required`).

### 3.5 Composite Attestation Envelope (CAE)

Encoded as D-CBOR (same determinism guarantees as CIP-3).

```rust theme={null}
pub struct CompositeAttestation {
    pub version:     u16,                         // = 1
    pub cpu:         CpuAttestation,
    pub gpu:         Option<GpuAttestation>,      // required for AI / GPU jobs
    pub service_sig: ServiceSignature,
    pub freshness:   FreshnessAnchor,
    pub extra:       BTreeMap<String, Vec<u8>>,   // reserved
}

pub struct CpuAttestation {
    pub tee_type:       CpuTeeType,               // Tdx | Sev | Nitro  (Sgx not eligible, §3.3)
    pub quote:          Vec<u8>,                  // raw quote bytes (verified inside the SNARK, §3.8)
    pub cert_chain_cid: Cid,                      // CIP-9 CID for the full cert chain
    pub measurement:    [u8; 48],                 // MRTD / LAUNCH_DIGEST / Nitro PCR0 (vendor-native width)
    pub pcr_extensions: Vec<[u8; 48]>,            // runtime/boot measts: TDX RTMR0..3 / Nitro PCR1..N (empty if none)
}

pub struct GpuAttestation {
    pub tee_type:          GpuTeeType,            // NvidiaNcc
    pub nras_token:        Vec<u8>,               // NRAS-signed EAT (JWT)
    pub gpu_measurement:   [u8; 48],
    pub bound_cpu_pubkey:  [u8; 32],              // CPU↔GPU binding
}

pub struct ServiceSignature {
    pub scheme:         SigScheme,                // Ed25519 only in v3 (see note); EcdsaP256 reserved
    pub service_pubkey: [u8; 32],                 // 32-byte Ed25519 key (see "Service-key scheme")
    pub sig:            Vec<u8>,                  // Sign(sk, task_id ‖ req_hash ‖ result_hash ‖ attest_digest)
}

pub struct FreshnessAnchor {
    pub nonce:        [u8; 32],
    pub deadline:     u64,                        // block height
    pub generated_at: u64,                        // block height at quote request
}
```

**`attest_digest` (circularity fix).** The digest MUST be computed over the CAE with the signature field cleared, so it does not contain the signature that signs it:

```
attest_digest := keccak256( D-CBOR(CompositeAttestation { service_sig: ∅, .. }) )
```

All CAE consumers (storage §3.7, secrets §3.12, billing) use this definition.

**`nonce` (unpredictability fix).** The nonce MUST incorporate an on-chain unpredictable beacon, not be derivable solely from public data:

```
nonce := keccak256( task_id ‖ req_hash ‖ submission_block_hash ‖ beacon )
```

**`beacon` is the CIP-11 §9.2 consensus threshold seed** (a BLS12-381 threshold signature over the round — grinding-resistant, unpredictable until the round is certified) for round `r_submit + SELECTION_SEED_DELAY_VIEWS` (currently 3). It MUST be read from the **committed, execution-readable `ConsumedSeedsV1`** (the same source the CIP-2 dispatcher uses for committee selection), **not** from the node's local, prunable `SeedStore` — only the committed path is deterministic under replay. The chosen round is in the future at submission time, so neither a key-extraction adversary nor a colluding proposer can precompute or grind the nonce; the runner generates the quote once that seed is certified (\~`SELECTION_SEED_DELAY_VIEWS` views later), within the remaining `MAX_QUOTE_AGE` window.

A nonce derived solely from publicly-known/predictable values (e.g. block hash alone) is non-conformant.

> **On-chain enforceability (normative status).** This nonce-derivation recipe is a **producer-side MUST**. The chain **cannot** verify the derivation — the verifier treats `nonce` as opaque, checking only its inclusion in `REPORTDATA` (§3.8.5) and non-replay. Beacon-binding is a well-formedness obligation on honest runners, not a chain-checked invariant; an adversary who can already extract attestation keys is out of scope for this control (that is the operator-root's job, §3.6).

**Binding rule (MANDATORY).** The CPU quote's `REPORTDATA` (SNP `REPORT_DATA`, Nitro `user_data`) MUST equal:

```
user_data = keccak256(nonce ‖ service_pubkey ‖ gpu_measurement_if_any)
```

This welds the CPU attestation, service key, GPU report, and task into one tamper-evident receipt.

**Service-key scheme (MANDATORY).** `service_pubkey` is a **32-byte field**, which fits an Ed25519 public key exactly but **cannot** encode a NIST P-256 (secp256r1) point — SEC1 compressed is 33 bytes (1 prefix + 32-byte x) and uncompressed is 65; an x-only 32-byte form loses the y parity and is not a recoverable public key. The field is not merely cosmetic: `service_pubkey` is hashed into `REPORTDATA` (binding rule above) **and** is the HPKE recipient key for secret delivery (§3.12), so both the quote-binding preimage and secret-wrapping require the *actual* key bytes. Therefore, in v3 **Ed25519 is the only conformant service-key scheme**; `SigScheme::EcdsaP256` is **reserved and MUST NOT appear in a conformant CAE**. Verifiers MUST reject `EcdsaP256`. Adding P-256 later is a breaking change requiring (a) widening `service_pubkey` to a SEC1-encoded point and (b) re-pinning the `REPORTDATA` / HPKE preimages over the new encoding; it is out of scope for v3.

#### 3.5.1 Per-path preimages (normative)

A CAE is produced on four paths; each pins its own `nonce` / `REPORTDATA` / `result_hash`. All follow the rules above (`attest_digest` excludes `service_sig`; `nonce` mixes the beacon `b` — the CIP-11 §9.2 committed threshold seed defined above; `REPORTDATA = keccak256(nonce ‖ service_pubkey ‖ gpu_measurement_if_any)`). `service_sig` always signs `scope_id ‖ req_hash ‖ result_hash ‖ attest_digest`.

**`req_hash` recipe (normative).** `req_hash` is computed once, by the Job Dispatcher (`0x02`) at job submission, over the job's `JobType` ONLY (not the whole `JobSpec`):

```
req_hash := keccak256( DOMAIN_TAG ‖ [JOB_SPEC_VERSION] ‖ JobType::encode() )
DOMAIN_TAG = b"cowboy/jobspec-req-digest/v1"
```

where `JobType::encode()` is the canonical commonware-codec bytes (defined by `cowboy-protocol-codec::job_spec`; the first byte is the CIP-11 §9.4 `job_kind` tag) and `JOB_SPEC_VERSION` is the JobSpec wire-format version byte — the single crate-owned constant shared with the `job_spec_hash` recipe (CIP-11 §12.6). It is stored verbatim in `JobSpec.req_hash`; every **job-bearing** path below consumes the stored value and MUST NOT recompute it (the no-request paths pin the `keccak(b"")` constant instead, as the table and prose below state). (Supersedes node's prior `keccak256(serde_json::to_vec(JobType))`, which was non-deterministic — HashMap header order + `f64` temperature — violating the canonical-encoding rule.)

| Path (§)                                | `scope_id`    | `req_hash`     | `nonce` preimage                                                | `result_hash`                |
| --------------------------------------- | ------------- | -------------- | --------------------------------------------------------------- | ---------------------------- |
| Job — TeeAttested/Deterministic (§3.11) | `task_id`     | request digest | `keccak(task_id ‖ req_hash ‖ submission_block_hash ‖ b)`        | `keccak(result_payload)`     |
| Registration (§3.9)                     | `runner_addr` | `keccak(b"")`  | `keccak(runner_addr ‖ registration_block_hash ‖ b)`             | `keccak(b"")`                |
| Secret release / CBSS (§3.12)           | `job_id`      | `keccak(b"")`  | `keccak(job_id ‖ runner_addr ‖ release_request_block_hash ‖ b)` | `keccak(b"")`                |
| Billing — CIP-10 §12.3                  | `billing_id`  | `keccak(b"")`  | `keccak(billing_fields_excl_sig ‖ submission_block_hash ‖ b)`   | `keccak(billing_fields_rlp)` |

Paths with no request (registration, secret release, billing) pin **`req_hash = keccak(b"")`** so the `service_sig` preimage `scope_id ‖ req_hash ‖ result_hash ‖ attest_digest` stays byte-exact on every path. For the billing path the GPU term in `REPORTDATA` is replaced by the billing body digest: `REPORTDATA = keccak256(nonce ‖ service_pubkey ‖ keccak(billing_fields_rlp))`. The billing CAE is generated **fresh per event** (the cert chain MAY be cached; the quote MUST be fresh within `MAX_QUOTE_AGE`). These definitions are normatively load-bearing and MUST be byte-exact across all producers and verifiers.

#### 3.5.1a `result_payload` canonicalization (normative — amendment, COW-2703)

The Job path's `result_hash := keccak256(result_payload)` (table above) requires a **canonical** `result_payload`. `result.data` is an unconstrained JSON value, and the shipped `result_payload = serde_json::to_vec(result.data)` is **non-deterministic** (object-key order depends on the map type; `f64` formatting is unpinned) — so two honest producers/verifiers can hash the same logical result to different bytes. This is the same class of defect the `req_hash` recipe above already fixed for `JobType`, and its consequence here is worse: a mismatch either rejects a valid `tee_required` result or, via the cross-runner byte-match (§3.4 / §3.5.2), **wrongfully slashes an honest runner**.

Introduce a flag-day-gated canonical form. Let `CANONICAL_RESULT_PREIMAGE_ACTIVATION_HEIGHT` be a governance-set activation height, **dormant `u64::MAX`** until governance lowers it (mirroring `TEE_REQUIRE_BINDING_ACTIVATION_HEIGHT`), evaluated at the result tx's **inclusion block height**. Then:

```
result_payload := if block_height >= CANONICAL_RESULT_PREIMAGE_ACTIVATION_HEIGHT
                      [CANONICAL_RESULT_VERSION] ‖ canonical_result_cbor(result.data)
                  else
                      serde_json::to_vec(result.data)          // legacy — byte-identical to today
```

* **`canonical_result_cbor`** is the **CIP-11 §12.6 CBOR-float64 profile** applied to arbitrary JSON: recursively, object → CBOR map with **sorted text keys**; array → array; a number that is integral and in range → shortest CBOR int (non-negative major-type-0 **and negative major-type-1**), otherwise IEEE-754 **binary64** (an 8-byte binary form — no float→string non-determinism). ("In range" = the value the parser produced fits the CBOR integer major-types the codec emits; the exact `f64`-that-is-integral-but-outside-`i64`/`u64` boundary is **fixed by the single-source codec + fixture**, not by this prose, so third-party re-implementers must derive it from the codec, not paraphrase it.) A value that cannot be represented — **NaN / ±Inf**, or one exceeding the **result-domain depth/size caps** (§below) — is **INVALID**. Disposition of an INVALID result: for the cross-runner `Deterministic` mode, §3.5.2; for a **single-runner `tee_required` result** (committee = 1 — `TeeAttested`, or `None`/`EconomicBond` with `tee_required`), the CAE preimage cannot be formed, so **the result is rejected and the job fails verification** (there is no candidate group to exclude from, and no cross-runner slash set); any bond disposition follows the mode's existing single-runner rule (`EconomicBond` forfeits its bond on a failed result exactly as today; `None` has no economic consequence). NaN/±Inf here is producer-controllable, but a single-runner failure is already fully accounted by the existing mode rule — this amendment adds no new slash on that path.
* **`CANONICAL_RESULT_VERSION`** (`u8`) prefixes the CBOR so any future bytes-changing edit is an explicit version bump, not a silent fork. It and `canonical_result_cbor` are **single-sourced in `cowboy-protocol-codec`**; every node and runner MUST pin the **identical** codec rev, guarded by a checked-in frozen `(result.data, block_height) → result_payload` cross-stack fixture that fails loud on any bytes-changing upgrade.
* **Result-domain caps.** The canonical form imposes a maximum nesting depth and encoded size, set **above any plausible legitimate result** so the INVALID rule can never reject/slash an honest runner. Because a cap change moves the INVALID boundary (and hence the grouping/slash set) **without changing any within-cap bytes**, the caps are **not** a free governance parameter: they are a **`CANONICAL_RESULT_VERSION`-bumped constant of the single-source `cowboy-protocol-codec`** (a cap change is a version bump, exactly as any bytes-changing edit is), and the frozen cross-stack fixture **MUST carry over-cap boundary vectors** (a result at cap−1, at cap, and at cap+1) so any node/runner reading a different cap fails the golden loudly. No node may read a cap value that is not pinned by the codec rev in force at the evaluated height.
* **No-op while dormant.** Every live block is below `u64::MAX`, so `result_payload` is byte-identical to today until governance activates; all validators at a given inclusion height apply the same branch, so activation is fork-free (a result whose CAE was produced under the pre-activation rule but included at/above the height fails verification and is resubmitted — a brief, announced liveness window, not a safety split).

> **Open governance item (blocks activation, not merge) — `binary64` in a canonical trust-boundary encoding vs the whitepaper no-floats rule.** §3.5.1a admits non-integral `binary64` into `canonical_result_cbor`, which is hashed into `result_hash` / the CAE `service_sig` — a **canonical, consensus-verified** quantity crossing the runner→verifier boundary. The **whitepaper (WP §207) forbids floats in *any* canonical encoding** (note the ban is specifically on *canonical encodings*; float **computation** is permitted via deterministic softfloat, WP §541 — which is why the determinism-motivated reading is plausible). This amendment applies no-floats only to the cross-runner `Deterministic` match (§3.5.2); it **exempts** the CAE preimage on the rationale that it binds *one runner's own bytes* and is never cross-reproduced (satisfying the determinism *spirit*). Whether WP §207 is determinism-motivated (spirit satisfied) or categorical (letter violated) is a **governance call, not adjudicated here.** The tension pre-exists in the CIP-11 §12.6 profile (`cip-11:983`); this amendment is the first to route arbitrary-JSON floats (where floats genuinely occur — `{"price":19.99}`) through a consensus preimage. **Before governance lowers `CANONICAL_RESULT_PREIMAGE_ACTIVATION_HEIGHT`, WP §207 MUST be reconciled** — either the WP is amended to state the rule is determinism-motivated (and single-runner-bound floats are permitted), or floats are dropped from the CAE preimage too (making all `result.data` no-floats, at the cost of confidential float outputs). Merging this dormant amendment does not require the decision; **activating it does.**

#### 3.5.2 `Deterministic` cross-runner byte-match (normative — amendment, COW-2703)

**Scope.** This subsection amends **only** `VerificationMode::Deterministic` — the sole mode CIP-2 §9 defines as a **full-`result.data` byte-identical match** across runners. `MajorityVote` (`{field}` vote extraction) and `StructuredMatch` (`{fields}` tolerant `VerifierCheck` pipeline, CIP-2 §9.1) are **field-scoped / tolerance-based**, not full-result byte-matches, and are **out of scope** — see the note below. The **primary normative home for this rule is CIP-2 §9** (the Deterministic row); this subsection is the CIP-23 CAE-side statement of the same requirement.

At/above `CANONICAL_RESULT_PREIMAGE_ACTIVATION_HEIGHT`, the `Deterministic` grouping/comparison/slash decision MUST key on the **bare** `canonical_result_cbor(result.data)` (§3.5.1a) — the bare CBOR, **not** the `[VERSION]‖CBOR` CAE preimage; a constant version prefix at a given height preserves the equality partition, and keying on the bare form avoids a spurious dependency of the comparison on the preimage framing.

* **No-floats (per CIP-2 §9.1 / the whitepaper no-floats rule).** `Deterministic` is a *cross-runner reproducibility* mode: `canonical_result_cbor` pins **serialization**, not **computation**, so two honest runners whose `f64` math differs by 1 ULP would still hash to different bytes. Consensus-reproducible outputs therefore MUST be scaled-integer / fixed-point (`Nano`), never native floats — exactly as CIP-2 §9.1 already requires of canonical-wire values. A `Deterministic` `result.data` whose canonical form contains a **non-integral `binary64`** is **non-conformant → INVALID**. (This restriction is *specific to the byte-match*; the single-runner CAE preimage of §3.5.1a still admits `binary64`, since it binds one runner's own bytes and is never cross-reproduced.)
* **Height reference — split: grouping vs slash-eligibility.** The **grouping/equality** branch (which encoding the comparison keys on) is decided **once** for the whole `verify_results` call at its **verification/finalizing block height** — all results grouped under one branch on every validator, so the equality partition is identical network-wide across the flag day (a per-result-height reading would diverge the partition → consensus split). But the **`INVALID ⇒ slashed` disposition** is gated **per result on that result's own inclusion block height ≥ activation** (also a deterministic consensus quantity, so still no partition split). A runner who conformed to the rules **in force at its submission** (result included **below** activation, under the §3.5.1a legacy `serde_json` branch, where no no-floats rule applied) is therefore **never retroactively slashed** when settlement crosses the flag day — a slash is not a resubmit. Its uncanonicalizable form may still exclude it from the equality group (a liveness outcome), but it is not added to the slash set.
* **INVALID disposition — no new predicate; reuse the existing disagreement rule.** A result whose canonical form is INVALID (NaN/±Inf, non-integral `binary64`, or over the §3.5.1a caps) has **no canonical key**, so it can neither form nor join an equality group nor serve as the comparison reference (it is filtered from reference selection, so a single invalid submission cannot veto the round). It is **not** slashed by a new amendment rule — and in particular **not** by parsing the job `result_schema` (`data_format` is free-form per CIP-2 §1; gating a *slash* on an unpinned predicate would itself split consensus, the very defect this amendment removes). Instead it is dispositioned by the **existing `Deterministic` byte-mismatch slash**, which keys only on canonical quantities — and which is itself **threshold-gated** (`matching_count < threshold ⇒ ThresholdNotMet`, on which the shipped verifier **discards `runners_to_slash` and slashes no one**). So the disposition is the mechanism's real **three-way**, not a binary:

  * **Valid cohort reaches `threshold`** (the reference result's identical-canonical group is ≥ `threshold`): every INVALID result **fails to match** that cohort and is slashed exactly as any byte-mismatching result is — a `0 + slash` **invalid reveal** per **CIP-2 §6.1** (not §6.2, which is *non*-reveal; here the runner did reveal) — the slash gated per-result on **inclusion height ≥ activation** (F2 above).
  * **Valid results exist but the largest canonical cohort is sub-`threshold`**: `ThresholdNotMet` ⇒ the job **fails with no slash**, exactly as the shipped verifier discards `runners_to_slash` on that path. INVALID results are **not** slashed here — matching the pre-existing behavior for *any* sub-threshold disagreement (a wrong-integer defector that denies quorum is likewise unslashed today).
  * **Every result is INVALID** (no canonicalizable result): job **fails with no slash** — an all-fractional honest committee (a job-`result_schema` fault every runner reproduces — F1) and an all-garbage round both fail cleanly, **without slashing the whole committee**.

  Both inputs — "is this result canonicalizable" and "which results share the reference's canonical cohort" — are deterministic consensus quantities (the `canonical_result_cbor` Err is single-sourced + fixture-guarded; the threshold is the stored spec's), so the slash set is identical on every validator: **no undefined predicate, no split**. The float route is therefore **slashable exactly when a disagreeing integer would be** — above threshold, slashed; sub-threshold, not — so it grants an attacker **no advantage** over the pre-existing `Deterministic` threshold guarantee (no better, no worse); it does not manufacture a new penalty-free dodge. **Nor the inverse:** the threshold gate is what stops a lone integral-outputting defector from *inverting* an all-honest-fractional round into a slash of the honest majority. If R1/R2 honestly return `{"rate":3.333}` (INVALID) and a deviating R3 returns `{"rate":3}` (VALID), R3's valid cohort has size 1 \< `threshold` → `ThresholdNotMet` → the job fails and **no one is slashed** — the honest fractional majority is never slashed just because a single below-quorum valid result exists. (The status-quo outcome — the fractional pair being the winning cohort — becomes a clean job-fail post-activation, a documented liveness narrowing, not a wrongful slash.) A committee that adaptively **degrades to one** `Deterministic` survivor is the all-INVALID (or sub-threshold) case → job-fails, no slash — consistent with the §3.5.1a single-runner path.
* Below the activation height, comparison uses the legacy `serde_json::to_vec(result.data)` (byte-identical to today).

> **Out of scope — `MajorityVote` / `StructuredMatch` code divergence.** The shipped node verifier groups `MajorityVote` **and** `StructuredMatch` by a full-`result.data` `serde_json::to_string` fingerprint, which already **diverges from CIP-2 §9/§9.1** (field-scoped vote / tolerant pipeline) independently of canonicalization. This amendment deliberately does **not** canonicalize — and thereby cement — that divergence; reconciling `verifier.rs` `MajorityVote`/`StructuredMatch` to their §9/§9.1 models is tracked as a **separate issue**. Being out of scope does **not** mean those two modes are sound: until that issue lands they still group/slash on the same non-canonical `serde_json::to_string` this amendment fixes for `Deterministic`, so they carry the **same live wrongful-slash exposure** — a tracked, not a resolved, gap.

### 3.6 Trust roots — multi-root (`chip_root ∧ operator_root`)

TEE.Fail shows a chip-vendor root alone can be defeated by an adversary with **physical access + root**. v3 therefore requires two independent roots:

* **`chip_root`** — the vendor attestation, verified via the SNARK pipeline (§3.8).
* **`operator_root`** — a **Proof-of-Cloud / deployment endorsement** (cf. Flashbots Proof-of-Cloud, Intel POE) proving the CVM runs in an attested operational environment, not a bench rig under interposition. One conformant implementation is the shipped governance-trusted-key model (§3.8.2).

Acceptance of a `TeeAttested` or `Deterministic + tee_required` result requires **both roots to pass**. `MeasurementBinding` (§3.9) carries `operator_root_ref`.

> **Current-state vs target-state (normative status).** The `chip_root ∧ operator_root` MUST is **target-state**. Today **only the `operator_root` ships** (the interim trusted-key model, §3.8.2); the `chip_root` SNARK pipeline is a roadmap item (§7). Until the chip-root activation block, acceptance runs on the operator root alone (single-root), with `chip_root` in `--tee-soft-fail` (warn-only) mode; the AND-rule is enforced from chip-root activation onward. This is a known, human-accepted Draft gap — it is **not** a claim that both roots are enforced today.

**Threat model (explicit).** The chip root does **not** defend against physical access + root (TEE.Fail class); vendors classify this out of scope. The operator root is the mitigation. Remote/software adversaries without physical access are covered by the chip root alone.

### 3.7 On-chain Storage Strategy

| Field                                                                    | Where                                                  | Rationale                                                 |
| ------------------------------------------------------------------------ | ------------------------------------------------------ | --------------------------------------------------------- |
| `attest_digest = keccak(cae_cbor_without_sig)`                           | `task/result/<task_id>.cae_digest`                     | Audit handle                                              |
| `keccak256(measurement)` (32B), `gpu_measurement` hash, `service_pubkey` | Runner Registry `measurement_binding`                  | On-chain index key; 48-byte native value lives in the CAE |
| Full CAE (30–100 KiB)                                                    | CIP-9 Relay Nodes / IPFS; chain stores CID only        | Avoid state bloat                                         |
| `seen_nonces[task_id]`                                                   | TEE Verifier state, GC'd after `DISPUTE_WINDOW_BLOCKS` | Replay protection                                         |
| `CollateralSnapshot` (root certs, TCBInfo, QEIdentity, CRL, NRAS root)   | TEE Verifier state, versioned by `effective_at`        | Deterministic time anchor (§3.8.4)                        |

### 3.8 TEE Verifier Actor (`0x05`)

#### 3.8.1 State

```rust theme={null}
pub struct TeeVerifierState {
    pub collateral: BTreeMap<u64, CollateralSnapshot>,   // governance-published, keyed by effective_at
    pub seen_nonces: BTreeMap<JobId, BTreeSet<[u8; 32]>>,
    pub last_gc_block: u64,
}
```

Per-runner measurement whitelists live in the Runner Registry `measurement_binding` (§3.9), not here.

#### 3.8.2 Implementation status — interim model vs target

The **shipped implementation** (`node/execution/src/cbss.rs`) is an **interim model**, not the SNARK CAE pipeline:

* Instructions (**opcodes 60–63**): `RegisterTeeTrustedKey` / `RevokeTeeTrustedKey` / `SubmitTeeAttestation` / `RevokeTeeAttestation`. Governance registers a trusted quote-signing key per `(tee_type, measurement_hash)`; a runner submits a per-job ECDSA-signed attestation record; the chain verifies the **signature against the governance-registered key** (not a vendor cert chain). Consumed today only by CBSS secret release (`validate_runner_tee_for_release`).
* This model does **not** verify a hardware cert chain; its trust root is "governance vouches for this measurement→key".

v3 **repurposes the interim model as one implementation of the `operator_root`** (§3.6) rather than discarding it. The **target** `chip_root` is the SNARK pipeline below. Once the SNARK path lands, both roots run (chip ∧ operator). A one-release `--tee-soft-fail` transition applies to the chip-root rollout.

#### 3.8.3 Instructions

| Opcode | Instruction                                                                                       | Caller                            | Behavior                                                                                                                                                                                                                    |
| :----: | ------------------------------------------------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|  60–63 | `RegisterTeeTrustedKey` / `RevokeTeeTrustedKey` / `SubmitTeeAttestation` / `RevokeTeeAttestation` | governance / runner               | Interim operator-root model (§3.8.2) — **shipped**                                                                                                                                                                          |
|   124  | `DeprecateBinding { service_pubkey, reason }`                                                     | Governance `0x09`                 | **Emergency force-deprecate** — set a `measurement_binding` to `Deprecated`/`Revoked` **immediately**, before its renewal, on mid-epoch key revocation / CVE (§3.8.8 mid-epoch revocation). Takes effect at the next block. |
|   125  | `VerifyCae { cae, job_id, req_hash, result_hash }`                                                | Result Verifier `0x03` / Registry | Target chip-root SNARK pipeline (§3.8.5)                                                                                                                                                                                    |
|   126  | `UpdateCollateral { snapshot, effective_at }`                                                     | Governance `0x09`                 | Publish a `CollateralSnapshot` (root certs **+ TCBInfo + QEIdentity + CRL + NRAS root**); ≥ `ROOT_UPDATE_DELAY` before `effective_at`                                                                                       |
|   127  | `GcNonces { upto_block }`                                                                         | Permissionless                    | GC `seen_nonces` older than `upto_block`                                                                                                                                                                                    |

The 124–127 block is the in-code allocation. Earlier drafts placed these at 65–68 / "≥ 87"; that is superseded — 65–67 are earmarked for CIP-14/16 v2 in the CIP-13 master table and 68 is the shipped CIP-24 `SYS_SET_SECRET`, so the four CIP-23 v3 opcodes take the next free top-of-range slots, alongside CIP-13 delegation (118–123). `contract.sys_opcode_uniqueness` guards uniqueness at implementation.

> Collateral (TCBInfo / QEIdentity / CRL) is published as **one snapshot** via `UpdateCollateral`, replacing v1/v2's root-only `UpdateCpuRoot`/`UpdateNrasRoot`. This is what lets governance blacklist a vulnerable microcode/TCB level (§6) — by publishing a snapshot whose TCBInfo marks that level unacceptable.

#### 3.8.4 Determinism via governance-anchored collateral

All time-dependent validity (cert `notAfter`, CRL `nextUpdate`, TCBInfo dates) is evaluated against the `CollateralSnapshot` selected by `block_height` — **never wall-clock**. The snapshot is published by governance with a ≥ 7-day delay (`ROOT_UPDATE_DELAY`). Every validator replaying a transaction selects the same snapshot for the same block, so verification is deterministic.

#### 3.8.5 Verification pipeline (`verify_cae`)

This is the **full** verification used at **registration / renewal** (§3.8.8); steps 3–4 (the SNARK) are skipped on the **per-call light path**, which instead verifies the quote signature against the binding's `bound_quote_key`. The chain verifies a **SNARK proof** whose circuit performs steps 3–4 internally; the remaining steps are cheap on-chain checks. Conceptually:

`verify_cae` runs in one of two **modes** (§3.8.8). `Full` (registration / renewal, low frequency) runs the chip-root SNARK (steps 3–4) and captures the binding. `Light` (per-call, the hot path on every job result) **skips steps 3–4** and instead verifies the per-job quote signature against the binding's `bound_quote_key` — the key the SNARK already verified at registration. The mode is selected by the presence of `proof`: `Full` carries one; `Light` does not.

```text theme={null}
enum Mode { Full { proof }, Light }   // Full ⇔ proof present; Light ⇔ no proof

fn verify_cae(mode, cae, job_id, req_hash, result_hash, registry, snapshot) -> Result<(), TeeVerifyError>:
    1. Freshness:   now ≤ deadline AND (now − generated_at) ≤ MAX_QUOTE_AGE (= 75 blocks)
    2. Replay:      cae.freshness.nonce ∉ seen_nonces[job_id]

    match mode:
      Full { proof }:                 # registration / renewal — §3.8.8
        3. SNARK/chain: verify(proof) — circuit checks quote parse + vendor X.509 chain
                        (TDX→PCS, SNP→VCEK) + ECDSA, against snapshot roots;
                        (Nitro is the exception — verified directly on-chain, no SNARK, §3.8.9)
                        journal ⇒ ChipRootPublicInputs (measurement, tcb_status, bound_quote_key, …)
        4. TCB level:   tcb_status ∈ governance-allowed set (default {UpToDate})   → else TcbOutOfDate
        4b. Capture:    persist Active binding(service) ← { measurement(s), bound_quote_key := AK
                        from the verified quote, operator_root, tcb_epoch }                 # §3.9

      Light:                          # per-call hot path — NO SNARK, NO per-call CRL/TCB — §3.8.8
        3'. Binding:    b := registry.binding(service); b.status == Active AND now ≤ b.expires_at
                        → else RegistryMismatch
        4'. Quote sig:  verify(b.bound_quote_key, quote.header ‖ quote.report_body, quote.signature)
                        — a single ECDSA against the AK captured at registration  → else SigInvalid

    # common tail (both modes)
    5. Measurement: keccak(cae.cpu.measurement) ∈ registry.binding(service).allowed_cpu
                AND cae.gpu?.gpu_measurement ∈ registry.binding(service).allowed_gpu
    6. Binding:     quote.REPORTDATA == keccak(nonce ‖ service_pubkey ‖ gpu_measurement_if_any)
    7. Service sig: verify(service_pubkey, task_id ‖ req_hash ‖ result_hash ‖ attest_digest, sig)
    8. NRAS (GPU):  verify EAT(JWT) against snapshot.nras_root AND token.measurement == gpu_measurement
    9. Operator root (Full only): operator_root endorsement valid (§3.6, §3.8.2)  → else OperatorRootFail
   10. Accept → insert nonce into seen_nonces[job_id]; else → TeeVerifyError::<kind>
```

An implementer MUST NOT run step 3 (SNARK) on the per-call `Light` path: per-call SNARK verification is infeasible within the freshness window (§3.8.8) and is the explicit motivation for the split.

#### 3.8.6 Error codes

```rust theme={null}
pub enum TeeVerifyError {
    AttestFail,         // SNARK/cert-chain failure
    TcbOutOfDate,       // TCB level not accepted (v3)
    RegistryMismatch,   // measurement not whitelisted
    SigInvalid,         // service signature invalid
    TaskExpired,        // deadline or MAX_QUOTE_AGE exceeded
    TaskReplayed,       // nonce already seen
    InvalidResultHash,  // result_hash mismatch
    UnsupportedTeeType, // no root for this TEE type (e.g. SGX legacy)
    NonceBindingFail,   // REPORTDATA mismatch
    OperatorRootFail,   // operator/Proof-of-Cloud root failed (v3, multi-root)
    TeeUnavailable,     // runner couldn't obtain a quote
}
```

#### 3.8.7 Gas

| Operation                                                                                     |      Cycles      |  Cells  |
| --------------------------------------------------------------------------------------------- | :--------------: | :-----: |
| `verify_cae` **full** (registration/renewal — SNARK proof verify + steps 1–2,5–9)             | \~40,000–120,000 |    64   |
| `verify_cae` **light** (per-call — ECDSA vs `bound_quote_key` + REPORTDATA + lookups; §3.8.8) |  \~6,000–12,000  |    32   |
| `UpdateCollateral`                                                                            |       8,000      | \~1,000 |
| `GcNonces` (per 1,000 entries)                                                                |      10,000      |    0    |

Against the CIP-3 basefee budget — block **cycle capacity 20,000,000**, sustained **cycle target `T_c` = 10,000,000** (50% of capacity) — the SNARK path verifies on the order of **\~80–250 CAEs at the sustained target** (≈ 165–500 at capacity), depending on the proof system. The dominant historical cost (a 7-layer on-chain ECDSA/X.509 walk, \~120k cycles in v1) is moved into the proof; the chain pays proof-verification only.

> *Terminology (per marshal review).* CIP-3 defines `20,000,000` as the block-cycle **capacity** and `T_c = 10,000,000` (50%) as the **target**; the node constant historically named `BLOCK_CYCLES_TARGET` actually holds the **capacity** value. This CIP uses CIP-3's capacity/target terms and states headroom against the sustained target to avoid overstating.

#### 3.8.8 Where the SNARK runs — registration vs per-call (proving-latency split)

zkVM proving of a DCAP quote is **30 s – minutes** (RISC Zero / SP1 / Bonsai), far above `MAX_QUOTE_AGE`. Verifying a fresh chip-root SNARK **on every job** is therefore infeasible. CIP-23 splits the chip root across two cadences:

* **Registration / renewal (low frequency, \~7 days, §3.9).** The full chip-root SNARK (§3.8.5 steps 3–4: vendor cert chain + TCB level) is generated and verified **once per binding**. On success the binding captures the **ECDSA attestation key (AK)** that signs the quote body (`bound_quote_key`) and the accepted measurements / TCB epoch. Proving latency is irrelevant here.

**`bound_quote_key` (normative — which key, and the stability assumption).** In ECDSA-DCAP the **quote body is signed by the attestation key (AK)**; the AK is certified by the Quoting Enclave, whose report is signed by the **PCK** (PCK does **not** sign quote bodies). Therefore `bound_quote_key` MUST be the **AK** — specifically the `ecdsa_attestation_key` carried in the quote's signature section — and the per-call `Light` check (§3.8.5 step 4′) is a single ECDSA of `quote.header ‖ quote.report_body` against that AK. At registration the SNARK verifies the **full chain AK → QE → PCK → Intel root** and the QE-report binding of the AK; the per-call path trusts the captured AK directly and does **no** per-call AK→QE re-verification. Soundness rests on one stated assumption: **the AK is stable for the whole renewal period.** If the QE rotates the AK mid-period, quotes signed by the new AK fail step 4′ (verify against the stale bound AK) and those results are **rejected** until renewal re-captures the new AK — i.e. AK rotation fails closed, never open. (Implementations capture the AK at registration; per-call they MUST verify against the *bound* AK, not against the AK embedded in the incoming quote.)

* **Per call (high frequency).** Result acceptance does **not** re-verify the SNARK. The runner's per-job quote is checked with the **light path**: the quote signature verifies against the binding's `bound_quote_key` (a single ECDSA), plus REPORTDATA binding (step 6), service signature (step 7), freshness (step 1) and replay (step 2). The cert chain / TCB were already proven at registration and are invariant within the TCB epoch.

The guarantee is preserved: a per-job result is accepted only if its quote chains (via `bound_quote_key`) to a binding whose chip-root SNARK **and** operator root passed at registration, the binding is `Active` / unexpired, and the per-job freshness / replay / REPORTDATA checks hold. A platform TCB change (microcode update / revocation) makes the binding fail renewal and drop out — bounding staleness to one renewal period.

**Mid-epoch revocation staleness (explicit governance-accepted risk).** Because the `Light` path does **no** per-call CRL / TCBInfo check, a platform key revoked *after* a binding was captured keeps producing **accepted** results until the binding next fails renewal — a window of up to `BINDING_RENEWAL_PERIOD` (\~7 days), versus \~75 s under a hypothetical per-call full check. This is the deliberate cost of moving the SNARK off the hot path (§3.8.8) and is an **accepted risk recorded by governance**, mitigated two ways: (1) shortening `BINDING_RENEWAL_PERIOD` trades proving overhead for a tighter staleness bound; (2) **emergency force-deprecate** — governance MAY call `DeprecateBinding { service_pubkey }` (opcode 124) to set a binding to `Deprecated`/`Revoked` **immediately**, before its renewal, on a mid-epoch revocation or disclosed CVE. After force-deprecation the dispatcher drops the runner at the next block and per-call `Light` verification fails step 3′ (`status == Active`). `UpdateCollateral` (publishing a snapshot whose TCBInfo blacklists the level) only bites at the *next* renewal, so `DeprecateBinding` is the fast path for the up-to-7-day window.

`verify_cae` (§3.8.5) thus has two entry modes: **full** (registration / renewal, carries `proof`) and **light** (per-call, no `proof`). The dispatcher (§3.10) and Result Verifier (§3.11) use the light mode; only `RegisterRunner` / renewal uses the full mode.

> *Rationale.* Proving-latency analysis (RISC Zero/SP1 DCAP ≈ 30 s–min) showed a per-call SNARK cannot fit the 75-block freshness window. Anchoring the heavy proof to the \~7-day binding cadence and binding the platform key keeps per-call acceptance cheap and latency-free while the cert chain / TCB are proven once per renewal period.

#### 3.8.9 Nitro (P3) backend verification — COSE\_Sign1, direct (no SNARK)

AWS Nitro Enclaves do not produce a DCAP quote. The platform attestation is a **`COSE_Sign1` document** (CBOR) signed **ES384** by a per-enclave leaf key, certified by an X.509 chain to the **AWS Nitro root** (Root-G1). There is **no microcode TCB** to evaluate. Consequently Nitro does **not** use the chip-root SNARK (§3.8.5 steps 3–4); its verification is a handful of signature / certificate checks performed **directly on-chain**. This subsection specializes §3.8.5 for `cae.cpu.tee_type == Nitro`.

**Why no SNARK.** The chip-root SNARK exists to keep a *heavy* verification (DCAP cert chain + QE identity + TCBInfo / CRL evaluation, historically \~120k cycles) off the hot path and succinct. Nitro verification is light — one ES384 signature, a 5-certificate ECDSA chain walk, and CBOR field extraction, with no TCB / CRL logic — so proving it would add a Nitro guest + prover + proving latency for no benefit. Direct on-chain verification is cheaper end-to-end and removes a build/maintain surface. (Verified end-to-end on real Nitro hardware: a 4.5 KB COSE doc, ES384 leaf sig + 5-cert chain to the AWS root, full validation under a few ms.)

**Document shape (normative).** `cae.cpu.quote` carries the raw `COSE_Sign1` = `[protected, unprotected, payload, signature]`. `protected` MUST decode to `{1: -35}` (alg = ES384); `signature` is a 96-byte raw `r ‖ s`. `payload` is the CBOR attestation document with at least: `module_id`, `digest = "SHA384"`, `timestamp`, `pcrs` (map `u8 → 48 B`), `certificate` (leaf DER), `cabundle` (DER chain, root-first), and the binding field **`user_data`** carrying the CIP-23 **REPORTDATA** (§3.5 — Nitro has no dedicated REPORTDATA slot; `user_data` is it). The native `nonce` / `public_key` fields MAY additionally mirror the freshness nonce / service key but are not the normative binding.

**Full path (registration / renewal).** For `tee_type == Nitro`, step 3 of §3.8.5 is replaced by:

```text theme={null}
3-Nitro. Direct COSE verification (no proof; cae.cpu.quote = COSE_Sign1 doc):
   a. Decode COSE_Sign1; require protected.alg == ES384.
   b. Leaf sig:   verify ES384 over Sig_structure
        ["Signature1", protected, b"", payload] with the leaf cert's P-384 key  → else AttestFail
   c. Chain:      walk cabundle ‖ leaf; each cert ECDSA-verified by its issuer;
        the chain root MUST equal a cpu_root in snapshot.cpu_roots              → else AttestFail
        (the AWS Nitro root is governance-published collateral, §3.8.4 — the
         doc's self-carried root is NEVER trusted)
   d. Measurement: doc.pcrs[0] == cae.cpu.measurement   (PCR0 = launch digest)  → else RegistryMismatch
        AND doc.pcrs[i] == cae.cpu.pcr_extensions[k] for the bound PCR set
        (e.g. PCR1, PCR2), each 48 B
   e. Binding:    doc.user_data == REPORTDATA
        == keccak(cae.freshness.nonce ‖ service_pubkey ‖ gpu_measurement?)       → else NonceBindingFail
        (§3.5 / §3.8.5 step 6 — pins the freshness nonce AND the Ed25519
         service key inside the AWS-signed doc; the GPU term covers P2 jobs)
   f. tcb_status := UpToDate   (Nitro exposes no TCB; §3.8.5 step 4 is a no-op)
   g. Capture:    persist Active binding(service) ← { measurement(s),
        bound_quote_key := service_pubkey, operator_root, tcb_epoch }           # §3.9
```

The captured **`bound_quote_key` for Nitro is the Ed25519 `service_pubkey`**, not a platform attestation key. This differs from DCAP (where it is the AK, §3.8.8) and is the crux of the per-call path below. The service key is bound into the AWS-signed document **transitively**, as a term of the `user_data` REPORTDATA preimage (step e).

**Per-call path (light) — service key only.** The Nitro leaf signing key is **ephemeral**: a fresh key per enclave boot with a short-lived certificate. It is therefore unusable as a stable per-call verification key (unlike a DCAP AK, which §3.8.8 assumes stable for the renewal period). Nitro per-call verification MUST NOT re-verify the COSE document. Registration step (e) already bound the **stable Ed25519 service key** into the AWS-signed document (as a REPORTDATA term); per-call acceptance then rests on:

* §3.8.5 step 7 — the **service signature** over `task_id ‖ req_hash ‖ result_hash ‖ attest_digest`, verified against `service_pubkey` (== `binding.bound_quote_key`);
* §3.8.5 step 5 / step 3′ — measurement whitelist; binding `Active` and unexpired.

Step 4′ (quote-sig vs `bound_quote_key`) and step 6 (REPORTDATA) **do not apply** per-call to Nitro: there is no per-call platform quote, and the REPORTDATA (`user_data`) binding was already checked once, at registration (step e), inside the AWS-signed document. The §3.8.8 guarantee holds unchanged: a per-job result is accepted only if it carries a service signature from a key that an AWS-rooted document bound to a whitelisted enclave measurement at registration, the binding is `Active` / unexpired, and freshness / replay hold.

**Staleness & rotation.** Leaf-cert / enclave-key rotation does not affect the per-call path (it never trusts that key). A re-launched enclave with a different PCR0 yields a different measurement and fails the whitelist until re-registration. Mid-epoch revocation of the AWS root or governance distrust of a measurement uses the same controls as DCAP: `DeprecateBinding` (immediate) and `UpdateCollateral` (next renewal) — §3.8.8.

**Error mapping.** Nitro reuses §3.8.6 codes: COSE decode / ES384 / chain failure → `AttestFail`; `user_data` REPORTDATA mismatch → `NonceBindingFail`; PCR not whitelisted → `RegistryMismatch`; no AWS root in the snapshot → `UnsupportedTeeType`.

**Gas.** Nitro **full** replaces the SNARK-verify term with on-chain COSE + X.509 work: one ES384 verify + a 5-cert ECDSA chain walk + CBOR extraction — on the order of the DCAP **light** path, and bounded likewise. There is **no** per-call Nitro cost beyond §3.8.5's common tail. A full cycle accounting is deferred to implementation.

### 3.9 Runner Registry Extension

```rust theme={null}
pub struct MeasurementBinding {
    pub cpu_tee_type:             CpuTeeType,
    pub allowed_cpu_measurements: BTreeSet<[u8; 32]>,    // keccak(48-byte measurement)
    pub allowed_gpu_measurements: BTreeSet<[u8; 32]>,    // empty → GPU not required
    pub service_pubkey:           [u8; 32],
    pub bound_quote_key:          Vec<u8>,                // v3: ECDSA attestation key (AK) that signs the quote body; AK→QE→PCK→root chain verified by the SNARK at registration; per-call quote sigs verify against it (§3.8.8)
    pub operator_root_ref:        OperatorRootRef,        // v3: Proof-of-Cloud / deployment endorsement
    //   wire: OperatorRootRef { kind: u8, data: Vec<u8> } (matches cowboy_types::tee). `kind`
    //   discriminates the operator-root scheme (kind=1 = shipped governance-trusted-key model,
    //   §3.8.2); `data` is scheme-specific and opaque in P1 — a deliberate extension point,
    //   its layout defined per-`kind` as each Proof-of-Cloud scheme is specified.
    pub bound_at:                 u64,
    pub expires_at:               u64,                    // default bound_at + BINDING_RENEWAL_PERIOD
    pub status:                   BindingStatus,          // Active | Deprecated | Revoked
}
```

Registration: a runner boots a CVM, derives a service keypair, submits `RegisterRunner(stake, rate_card, initial_cae, service_pubkey, claimed_measurements, operator_root)`; the Registry cross-calls `0x05::VerifyCae` in **full** mode (chip-root SNARK, §3.8.8) and validates `operator_root`; on success it persists an `Active` binding **capturing `bound_quote_key`** (the **attestation key (AK)** whose AK→QE→PCK→root chain the SNARK verified) for the per-call light path. A binding MUST be renewed every `BINDING_RENEWAL_PERIOD` (default 604,800 blocks ≈ 7 days at 1 s); expired → `Deprecated` (leaves the candidate pool; historical results stay verifiable). Renewal re-runs the full SNARK, so a TCB/microcode change is caught within one renewal period.

**Attestation-first registration** closes the swap-binary race: binding measurements at registration **plus** per-job CAE nonce binding means any binary swap invalidates both.

### 3.10 Dispatcher Filter (amends CIP-2 §5.4)

For `tee_required` jobs, the runner's Registry record MUST have a `measurement_binding` with `status == Active`, `expires_at > submission_block`, and `cpu_tee_type` matching the job's `required_tee_type` (if set). Self-declared `capabilities.tee_support` MUST NOT be used as proof of TEE capability for acceptance.

> *Implementation note:* the shipped dispatcher still filters on self-declared `tee_support` (type-aware). Migrating the filter to `measurement_binding` is part of landing the chip-root path.

### 3.11 Result Verifier (amends CIP-2 §9)

Per-call acceptance uses the **light** `verify_cae` (§3.8.8) — no per-call SNARK; the quote signature is checked against the binding's `bound_quote_key` plus REPORTDATA / service-sig / freshness / replay.

* **`TeeAttested`:** committee = 1; the result MUST carry a CAE; `0x03` runs the light `verify_cae` and accepts on pass. No byte-match (the output is confidential).
* **`Deterministic`:** every result MUST carry a CAE; `0x03` runs the light `verify_cae` per result, then performs the byte-identical match **on the (non-confidential) result payload** — at/above the activation height this match keys on the canonical `canonical_result_cbor(result.data)` and applies the INVALID⇒excluded∧slashed rule of **§3.5.1a / §3.5.2** (which govern the canonical form, the no-floats constraint, and the single verification-height reference). Any failure fails the whole job.
* Other modes MAY attach a CAE; if present it MUST pass the light path, except `sgx` CAEs which are rejected (`UnsupportedTeeType`).

### 3.12 Secrets Manager / CBSS Integration

`Secrets Manager (0x04)` / CBSS gates a secret release on a valid CAE when the secret's policy sets `tee_required`. Secrets are delivered HPKE-wrapped to the runner's `service_pubkey`; cleartext exists only inside the CVM.

> CIP-24 coordination: the release gate now prefers the v3 `measurement_binding` (registered via `VerifyCae`, which checked **chip ∧ operator**) over the interim governance-key record — an Active, unexpired binding for the runner authorizes release, and re-checking it at release (not only at dispatch) enforces mid-epoch revocation: a `DeprecateBinding` (CVE / key compromise, §3.8.8) blocks every further release with no fall-through to the interim record. The interim record remains the fallback only while a runner has no v3 binding yet (chip-root transition). Requiring a **fresh per-release CAE** (the §3.5.1 secret-release preimages, carried alongside the runner-signed request) is the remaining hardening.

### 3.13 `tee_call` SDK Helper

No new PVM syscall. `cowboy_sdk.tee.tee_call(...)` is a Python wrapper that emits a `SubmitJob` with `tee_required = true` (mode defaults to `TeeAttested`; pass `deterministic=True` for the reproducible path). PVM determinism is unaffected.

### 3.14 The three-layer chain (defense in depth)

A TEE-required job touches three independent layers, each at a different lifecycle moment:

| Layer          | Field / record                             | When set               | Authority   |
| -------------- | ------------------------------------------ | ---------------------- | ----------- |
| Actor manifest | `sec.tee_required` entitlement             | Actor deploy           | Actor owner |
| Job spec       | `VerificationConfig.tee_required` (+ mode) | `submit_task`          | Submitter   |
| Runner record  | `measurement_binding` (§3.9)               | Registration / renewal | Runner      |

If the manifest declares `sec.tee_required`, all the actor's jobs MUST set `tee_required = true` (submitter-side validation rejects violations). The dispatcher (§3.10) selects only bound runners; the Result Verifier (§3.11) requires a passing CAE.

### 3.15 CIP-13 (delegation) interaction

TEE eligibility is a **categorical capability check** (`measurement_binding.status`), not stake-thresholded. Delegated stake raises VRF weight but does **not** confer eligibility. Slashing for forged attestation: self-stake uncapped; delegator slash capped at `MAX_DELEGATION_SLASH_PER_EPOCH_BPS = 500` (5%) per CIP-13 §3.6. Delegators have no claim on or visibility into secrets the runner accesses.

### 3.16 Parameters

| Parameter                | Default                        | Notes                                                       |
| ------------------------ | ------------------------------ | ----------------------------------------------------------- |
| `MAX_QUOTE_AGE`          | 75 blocks (≈75 s @ 1 s)        | Quote freshness window                                      |
| `BINDING_RENEWAL_PERIOD` | 604,800 blocks (≈7 days @ 1 s) | Re-attestation                                              |
| `ROOT_UPDATE_DELAY`      | 1 week                         | Governance delay for `UpdateCollateral`                     |
| `SEEN_NONCE_GC_WINDOW`   | = `DISPUTE_WINDOW_BLOCKS` (75) | Nonce GC                                                    |
| `MAX_CAE_ON_CHAIN_BYTES` | 64 (digest only)               | Full CAE off-chain (CIP-9)                                  |
| Block cycle **capacity** | 20,000,000                     | CIP-3 (the `BLOCK_CYCLES_TARGET` constant holds this value) |
| Cycle **target** `T_c`   | 10,000,000                     | CIP-3 — 50% of capacity; sustained per-block target         |

***

## 4. Rationale

* **Two modes, not one.** Confidential output (the whole point of TEE for privacy) and committee byte-match are mutually exclusive; conflating them (v1 §3.9) was unimplementable for the privacy case. `TeeAttested` makes attestation the verification; `Deterministic` keeps reproduction for public outputs.
* **SNARK over hand-rolled chain.** Moving X.509 + ECDSA + TCB evaluation into a circuit solves two problems at once: the on-chain **non-determinism of time** (collateral becomes a snapshot-anchored circuit input) and **gas** (verify a proof, not a 7-layer walk). It also reuses an industry-proven pattern.
* **SNARK off the hot path (§3.8.8).** zkVM DCAP proving is \~30 s–minutes — it cannot fit the 75-block freshness window per call. Verifying the chip-root SNARK at registration / renewal (\~7 days) and binding the platform key (`bound_quote_key`) lets per-call acceptance be a single ECDSA + REPORTDATA / freshness / replay — cheap and latency-free — while the cert chain / TCB are proven once per renewal period.
* **Multi-root.** TEE.Fail proved a forged-but-root-valid quote is achievable with physical access. A second, operationally-rooted endorsement (Proof-of-Cloud) is the pragmatic mitigation; it also gives the already-shipped governance-key model a principled home.
* **CPU + GPU via REPORTDATA binding.** Without cross-binding, CPU and GPU attestations can be mixed and matched; the `user_data` rule makes the CPU quote worthless without the matching GPU report and service key.
* **On-chain digest, off-chain body.** Anchoring only `attest_digest` keeps state growth independent of attestation size; the full CAE lives on CIP-9.
* **`attest_digest` excludes the signature.** Otherwise the digest contains the signature that signs it — a circular definition that cannot be implemented.
* **Unpredictable nonce.** A publicly-derivable nonce offers no freshness against a key-extraction adversary; an on-chain beacon does.

***

## 5. Backwards Compatibility

* Non-TEE runners are unaffected; they cannot be selected for `tee_required` jobs.
* The **interim trusted-key model (opcodes 60–63) keeps running** and is reframed as the operator root — no migration needed for what exists today.
* The chip-root SNARK path and the dispatcher's switch from self-declared `tee_support` to `measurement_binding` land incrementally; a one-release `--tee-soft-fail` flag warns instead of rejecting during cutover.
* CIP-10 `BillingAttestation.tee_signature` is `Option<CompositeAttestation>`, **freshly generated per billing event** with the byte-exact `nonce`/`REPORTDATA`/`result_hash` pinned in **§3.5.1** (cert chain MAY be cached; the quote MUST be fresh within `MAX_QUOTE_AGE`).

***

## 6. Security Considerations

* **Threat model (§3.6):** chip-vendor root does **not** cover physical access + root (TEE.Fail). Mitigated by the **operator root**; remote/software adversaries are covered by the chip root.
* **TEE.Fail / physical extraction:** a forged quote can chain to a vendor root. Single-root acceptance is therefore disallowed; `chip_root ∧ operator_root` is required.
* **Forged quotes (remote):** rejected by the SNARK chip-root verification.
* **Replay:** `nonce` (with beacon) is bound inside `REPORTDATA` and checked against `seen_nonces[task_id]` for the dispute window.
* **Vulnerable microcode / TCB:** governance publishes a `CollateralSnapshot` whose TCBInfo marks the level unacceptable → step 4 (`TcbOutOfDate`). (This is what v1 §6 *intended*; it is done via `UpdateCollateral`/TCBInfo, **not** root-cert updates.)
* **NRAS centralization:** verified against `snapshot.nras_root` (no live call); a threshold multi-provider fallback is a follow-up.
* **CAE availability:** the on-chain `attest_digest` proves the receipt exists even if the CIP-9 body is unreachable; runners SHOULD pin to ≥ k Relay Nodes.
* **GPU memory residue:** addressed by CIP-10 §14.6.

***

## 7. Implementation Roadmap

|     Phase     | Deliverable                                                                                                           | Exit criteria                                                                   |
| :-----------: | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **P0 (done)** | Interim trusted-key model (opcodes 60–63), quote collection (`runner-tee`), CBSS `tee_required` gate                  | Shipped; serves as operator root                                                |
|     **P1**    | Chip root: SNARK-compressed TDX DCAP + `UpdateCollateral` + TCB step; dispatcher → `measurement_binding`; modes split | TDX `TeeAttested` end-to-end; red-team forged/replayed/expired/TCB all rejected |
|     **P2**    | CPU+GPU composite (NCC + NRAS) + CoCo/Trustee; multi-root acceptance enforced                                         | Confidential LLM inference with composite CAE; both roots required              |
|     **P3**    | SEV-SNP + Nitro backends; CAE off-chain (CIP-9); governance collateral exercised                                      | ≥ 2 backends; dispute window exercised against a malicious runner               |

Net effort to reach the full chip-root + multi-root design is estimated at ≈ 20–30 engineer-weeks, dominated by the SNARK DCAP verifier.

***

## Appendix A: Reference Mapping (PythonVM × TEE prior art ↔ Cowboy)

| Prior-art term       | Cowboy equivalent                                 |
| -------------------- | ------------------------------------------------- |
| `tee_call` syscall   | `cowboy_sdk.tee.tee_call()` → `SubmitJob`         |
| CAE                  | `cowboy_types::tee::CompositeAttestation`         |
| Service Registry     | CIP-2 Runner Registry + `measurement_binding`     |
| verify / replay path | Cowboy speculative execution + cached batch apply |

## Appendix B: TEE landscape (snapshot, informative)

**CPU/GPU backends.** Intel **TDX** (primary, Azure/Google/Alibaba), **NVIDIA NCC** (primary GPU; H100/H200/Blackwell; \~2–5% LLM overhead), **AMD SEV-SNP** and **AWS Nitro** (secondary), **Intel SGX** (legacy — IAS/EPID EOL 2025-04-02, `EconomicBond` only), ARM CCA (tracked, immature). Enablement: **Confidential Containers (CoCo) + Trustee/KBS** (primary platform layer, reused from CIP-10). Not used: Gramine/Occlum/Open Enclave (SGX LibOS/SDK — superseded by VM-level CC).

**2026-06 update (informs §3.4/§3.6/§3.8).**

* **TEE.Fail (2025-10):** \< \$1,000 DDR5 interposer extracts keys from TDX/SGX/SEV-SNP (deterministic AES-XTS); forged TDX attestations on Ethereum BuilderNet. Vendors deem physical attacks out-of-scope → drives **multi-root** (§3.6).
* **On-chain SNARK DCAP (Automata et al.):** production SGX/TDX attestation on EVM with SNARK compression (\~1/10 gas) and decentralized collateral caching → drives **§3.8 SNARK pipeline**.
* **Proof-of-Cloud (Flashbots) / Intel POE:** chip-root + operator-root composition → the `operator_root` of §3.6.
* **NVIDIA Blackwell CC:** B200/GB200 confidential mode (encrypted PCIe 5.0 / NVLink); CC deployment guide v7.1 (2026-04).

**Sources:** TEE.Fail — bleepingcomputer.com/news/security/teefail-attack-breaks-confidential-computing-on-intel-amd-nvidia-cpus/ ; Proof-of-Cloud — writings.flashbots.net/mind-the-gap-tee-poc ; on-chain SNARK DCAP — medium.com/@oraclethaacat/beyond-dcap-... ; NVIDIA CC — docs.nvidia.com/cc-deployment-guide-tdx.pdf
