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)
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-pathresult_payloadcanonical encoding (§3.5.1a) and theDeterministiccross-runner byte-match (§3.5.2; primary home CIP-2 §9):result.datawas hashed/compared via non-canonicalserde_json(unpinned object-key order +f64formatting), 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 incowboy-protocol-codecwith a frozen cross-stack fixture, gated by a dormantCANONICAL_RESULT_PREIMAGE_ACTIVATION_HEIGHT(no-op until governance activates; fork-free). Scope: the CAE preimage (§3.5.1a, alltee_requiredresults —binary64admitted) and theDeterministicfull-result byte-match (§3.5.2, no-floats), with grouping keyed at a single verification-block height. An INVALID result (NaN/±Inf/non-integralbinary64/over-cap) has no canonical key → cannot join the equality group and is dispositioned by the existing threshold-gated byte-mismatch slash — no newresult_schemapredicate (gating a slash ondata_format, free-form per CIP-2 §1, would itself split consensus): slashed as a §6.1 invalid reveal only when the valid cohort reachesthreshold(gated per-result on inclusion ≥ activation); a sub-thresholdvalid 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-runnertee_requiredINVALID result is the all-INVALID case at N=1 → rejected, no slash. Thebinary64-in-canonical-preimage vs whitepaper-no-floats (WP §207) tension is an open governance item that blocks activation, not merge. Caps are aCANONICAL_RESULT_VERSION-bumped codec constant (not a free parameter).MajorityVote/StructuredMatchare 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) vsDeterministic(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 trustchip_root ∧ operator_root(Proof-of-Cloud) and an explicit threat model post-TEE.Fail, §3.6, §6; (4)attest_digestcircularity fixed andnoncemade 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 at0x05 into a real attestation pipeline.
Three properties define v3:
- Two verification philosophies, kept apart (§3.4).
TeeAttestedis for confidential outputs: a single runner, where the attestation is the verification (no cross-runner reproduction).Deterministicis 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.
None, EconomicBond, MajorityVote, StructuredMatch, and SemanticSimilarity jobs unchanged.
2. Motivation
CIP-2 introduces atee_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):
- 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).
- 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, SNPLAUNCH_DIGEST, NitroPCR0; further Nitro PCRs ride inpcr_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_requiredjob defaults toTeeAttested. SelectingDeterministicrequires the submitter to declare the output non-confidential. - For both modes every TEE result MUST carry a
CompositeAttestationthat passes §3.8. tee_typeeligibility:tdx/sev/nitro→ both modes;sgx→ neither (legacy,EconomicBondonly).
Wire encoding (normative — matchescowboy-protocol-codec::job_spec::VerificationConfig).TeeAttestedandDeterministicname verification philosophies, notVerificationModeenum tags. There is noTeeAttestedtag in the on-chainVerificationModeenum; its wire tags areNone=0, EconomicBond=1, MajorityVote=2, StructuredMatch=3, Deterministic=4, SemanticSimilarity=5(singleu8). TEE attestation is carried by the siblingtee_required: boolfield ofVerificationConfig(plusrequired_tee_type: Option<String>, ≤128 B). The two philosophies map to the wire form as:TeeAttested=tee_required = truewith a confidential single-runnerVerificationMode(None/EconomicBond);Deterministic=VerificationMode::Deterministic(tag 4) withtee_required == true(the node enforces thatDeterministicrequirestee_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:
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 treatsBinding rule (MANDATORY). The CPU quote’snonceas opaque, checking only its inclusion inREPORTDATA(§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).
REPORTDATA (SNP REPORT_DATA, Nitro user_data) MUST equal:
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 ownnonce / 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):
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_cboris 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 exactf64-that-is-integral-but-outside-i64/u64boundary 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-runnerDeterministicmode, §3.5.2; for a single-runnertee_requiredresult (committee = 1 —TeeAttested, orNone/EconomicBondwithtee_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 (EconomicBondforfeits its bond on a failed result exactly as today;Nonehas 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 andcanonical_result_cborare single-sourced incowboy-protocol-codec; every node and runner MUST pin the identical codec rev, guarded by a checked-in frozen(result.data, block_height) → result_payloadcross-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-sourcecowboy-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, soresult_payloadis 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) —binary64in a canonical trust-boundary encoding vs the whitepaper no-floats rule. §3.5.1a admits non-integralbinary64intocanonical_result_cbor, which is hashed intoresult_hash/ the CAEservice_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-runnerDeterministicmatch (§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 lowersCANONICAL_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 allresult.datano-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).
Deterministicis a cross-runner reproducibility mode:canonical_result_cborpins serialization, not computation, so two honest runners whosef64math 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. ADeterministicresult.datawhose canonical form contains a non-integralbinary64is non-conformant → INVALID. (This restriction is specific to the byte-match; the single-runner CAE preimage of §3.5.1a still admitsbinary64, 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_resultscall 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 theINVALID ⇒ slasheddisposition 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 legacyserde_jsonbranch, 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 jobresult_schema(data_formatis 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 existingDeterministicbyte-mismatch slash, which keys only on canonical quantities — and which is itself threshold-gated (matching_count < threshold ⇒ ThresholdNotMet, on which the shipped verifier discardsrunners_to_slashand 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 — a0 + slashinvalid 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 discardsrunners_to_slashon 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_schemafault every runner reproduces — F1) and an all-garbage round both fail cleanly, without slashing the whole committee.
canonical_result_cborErr 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-existingDeterministicthreshold 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 oneDeterministicsurvivor is the all-INVALID (or sub-threshold) case → job-fails, no slash — consistent with the §3.5.1a single-runner path. - Valid cohort reaches
-
Below the activation height, comparison uses the legacy
serde_json::to_vec(result.data)(byte-identical to today).
Out of scope —MajorityVote/StructuredMatchcode divergence. The shipped node verifier groupsMajorityVoteandStructuredMatchby a full-result.dataserde_json::to_stringfingerprint, 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; reconcilingverifier.rsMajorityVote/StructuredMatchto 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-canonicalserde_json::to_stringthis amendment fixes forDeterministic, 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).
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). TheThreat 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.chip_root ∧ operator_rootMUST is target-state. Today only theoperator_rootships (the interim trusted-key model, §3.8.2); thechip_rootSNARK pipeline is a roadmap item (§7). Until the chip-root activation block, acceptance runs on the operator root alone (single-root), withchip_rootin--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.
3.7 On-chain Storage Strategy
3.8 TEE Verifier Actor (0x05)
3.8.1 State
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”.
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 viaUpdateCollateral, replacing v1/v2’s root-onlyUpdateCpuRoot/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 (certnotAfter, 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.
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 defines20,000,000as the block-cycle capacity andT_c = 10,000,000(50%) as the target; the node constant historically namedBLOCK_CYCLES_TARGETactually 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 aboveMAX_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.
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 aCOSE_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:
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 againstservice_pubkey(==binding.bound_quote_key); - §3.8.5 step 5 / step 3′ — measurement whitelist; binding
Activeand unexpired.
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
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)
Fortee_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-declaredtee_support(type-aware). Migrating the filter tomeasurement_bindingis part of landing the chip-root path.
3.11 Result Verifier (amends CIP-2 §9)
Per-call acceptance uses the lightverify_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;0x03runs the lightverify_caeand accepts on pass. No byte-match (the output is confidential).Deterministic: every result MUST carry a CAE;0x03runs the lightverify_caeper result, then performs the byte-identical match on the (non-confidential) result payload — at/above the activation height this match keys on the canonicalcanonical_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
sgxCAEs 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 v3measurement_binding(registered viaVerifyCae, 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: aDeprecateBinding(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.
TeeAttestedmakes attestation the verification;Deterministickeeps 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_datarule makes the CPU quote worthless without the matching GPU report and service key. - On-chain digest, off-chain body. Anchoring only
attest_digestkeeps state growth independent of attestation size; the full CAE lives on CIP-9. attest_digestexcludes 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_requiredjobs. - 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_supporttomeasurement_bindingland incrementally; a one-release--tee-soft-failflag warns instead of rejecting during cutover. - CIP-10
BillingAttestation.tee_signatureisOption<CompositeAttestation>, freshly generated per billing event with the byte-exactnonce/REPORTDATA/result_hashpinned in §3.5.1 (cert chain MAY be cached; the quote MUST be fresh withinMAX_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_rootis required. - Forged quotes (remote): rejected by the SNARK chip-root verification.
- Replay:
nonce(with beacon) is bound insideREPORTDATAand checked againstseen_nonces[task_id]for the dispute window. - Vulnerable microcode / TCB: governance publishes a
CollateralSnapshotwhose TCBInfo marks the level unacceptable → step 4 (TcbOutOfDate). (This is what v1 §6 intended; it is done viaUpdateCollateral/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_digestproves 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_rootof §3.6. - NVIDIA Blackwell CC: B200/GB200 confidential mode (encrypted PCIe 5.0 / NVLink); CC deployment guide v7.1 (2026-04).

