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

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

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: 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).
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:
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:
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:
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):
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.) 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:
  • 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 < thresholdThresholdNotMet → 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

3.8 TEE Verifier Actor (0x05)

3.8.1 State

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

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_heightnever 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.
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

3.8.7 Gas

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:
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

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: 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


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

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)

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