diff --git a/ERCS/erc-8274.md b/ERCS/erc-8274.md new file mode 100644 index 00000000000..03f0db3b173 --- /dev/null +++ b/ERCS/erc-8274.md @@ -0,0 +1,645 @@ +--- +eip: 8274 +title: AI Inference Proof Verification Interfaces +description: Two-layer interface for on-chain AI inference proof verification, separating IAgentVerifier (application) from IProofVerifier (algorithm) +author: JimmyShi22 (@JimmyShi22) , Damonzwicker (@damonzwicker), TMerlini (@Echo-Merlini), babyblueviper1 (@babyblueviper1) +discussions-to: https://ethereum-magicians.org/t/draft-erc-universal-ai-inference-verification-registry/28083 +status: Draft +type: Standards Track +category: ERC +created: 2026-05-26 +requires: 165, 712 +--- + +## Abstract + +On-chain AI inference can be verified through multiple proof paradigms — zkML, opML, TEE, oracle, multisig, and others — but no common interface exists between them. Every protocol that needs verified AI inference today must write a separate adapter per proof system, resulting in N×M integration complexity and vendor lock-in. + +This ERC defines three minimal abstract interfaces organized into two layers: + +- **`IProofVerifier`** — the inner algorithm layer, implemented by proof system providers (ZK teams, TEE vendors, oracle networks). Stateless. Answers: "is this proof cryptographically valid for this input and output?" +- **`IAgentVerifier`** — the outer application layer, implemented by application developers. Stateful. Wraps an `IProofVerifier`. Answers: "for this task, was this agent authorized to make this claim, and does the proof confirm it?" +- **`IAgentVerifiable`** — implemented by settlement contracts to declare which `IAgentVerifier` they use. + +The separation allows proof system providers and application developers to work independently: neither needs to know the other's internals. + +## Motivation + +Verification infrastructure already exists, but it is highly fragmented. + +**Verification paradigms already live:** + +| Paradigm | Description | +| ----------- | ---------------------------------------------------------------------------------------- | +| zkML | Cryptographic proof that a specific model produced a given output from a given input | +| opML | Optimistic AI inference execution with a challenge window for fraud proofs | +| TEE | AI inference run inside a hardware-isolated enclave with a verifiable attestation report | +| Attestation | Off-chain inference result certified by one or more authorized signers — covers oracle gateways (e.g. Chainlink), multisig / AVS validator networks (e.g. EigenLayer), and judgment validators | + +**ERCs that need inference verification:** + +- **[ERC-8183](./eip-8183.md)** (Agentic Commerce) — evaluator "may verify a ZK proof" before releasing payment, but no verifier interface is defined +- **[ERC-8004](./eip-8004.md)** (Trustless Agents) — Validation Registry lists zkML/TEE verifiers as examples, but specifies no contract interface +- **[ERC-8001](./eip-8001.md)** (Agent Coordination) — routes multi-agent task results through opaque `executionData` bytes with no inference verification module +- **[ERC-7007](./eip-7007.md)** (Verifiable AIGC Token) — accepts opaque `bytes proof` with no model identifier or proof-system identifier + +The problem: these two sides cannot talk to each other. Every protocol that needs verified AI inference today must write a separate adapter per proof system — resulting in vendor lock-in and N×M integration complexity. What's missing is a standard verifier interface that sits in the middle. This ERC provides exactly that: + +``` +Verification Backends This ERC ERCs + + ╔══════════════════╗ + zkML ─┐ ║ AI Inference ║ ┌─ ERC-8183 (Agentic Commerce) + opML ─┤ ║ Proof ║ ├─ ERC-8004 (Trustless Agents) + TEE ─┼─►║ Verification ║◄─┼─ ERC-8001 (Agent Coordination) + Attestation ─┘ ║ Interfaces ║ └─ ERC-7007 (AIGC Token) + ╚══════════════════╝ +``` + +## Specification + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. + +--- + +### IProofVerifier — Inner Algorithm Layer + +`IProofVerifier` is the inner algorithm layer. Three defining properties: + +- **Stateless** — holds no configuration about which agent, model, or session is being verified; all context is passed through parameters on every call +- **Verification algorithm specific** — encapsulates the cryptographic verification logic of one proof system (ZK, TEE, optimistic, or attestation) +- **Implemented by proof system providers** — the parties who implement cryptographic verification for a specific proof system (e.g. ZK teams, TEE vendors, oracle networks, attestation gateway operators); called by agent developers from within `IAgentVerifier`, with no knowledge of the agent context above + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title IProofVerifier +/// @notice Stateless cryptographic verification interface for AI inference proof backends. +/// Implemented by proof system providers independently of any calling context. +/// Answers: "is this proof cryptographically valid for this input and output?" +interface IProofVerifier { + /// @notice Verify an AI inference proof. + /// @param inputHash Commitment to the model input (hash scheme is backend-specific). + /// @param outputHash Commitment to the model output (hash scheme is backend-specific). + /// @param metadata Invariant deployment configuration — fields stable across calls + /// for this verifier deployment (e.g. ZK circuit key, TEE enclave hash, + /// signer registry address). Stored by IAgentVerifier; callers never + /// encode or see this field. + /// @param proof Per-inference cryptographic evidence (e.g. ZK proof bytes, + /// EIP-712 signature, TEE attestation report). + /// @return True if the proof is cryptographically valid for the given input and output. + function verify( + bytes32 inputHash, + bytes32 outputHash, + bytes calldata metadata, + bytes calldata proof + ) external returns (bool); + + /// @notice Proof system identifier in "{paradigm}/{variant}" format. + function proofSystem() external view returns (string memory); + + /// @notice Collision-resistant identifier for this specific deployment. + /// SHOULD equal keccak256(abi.encodePacked(proofSystem(), deploymentSalt)) where + /// deploymentSalt is a backend-specific constant (e.g. circuit commitment hash, + /// enclave measurement). + function proofProfile() external view returns (bytes32); +} +``` + +#### `verify()` + +`verify()` takes four parameters with distinct roles: + +- **`inputHash`** — commitment to the model input. The hash scheme is backend-specific and MUST be documented by each implementation. +- **`outputHash`** — commitment to the model output. Hash scheme is backend-specific. +- **`metadata`** — invariant configuration that does not change between calls for a given deployment: ZK circuit verification key, TEE enclave hash, signer registry address, etc. This is deployment-time configuration owned by `IAgentVerifier`; the settlement contract never encodes or sees it. +- **`proof`** — per-inference cryptographic evidence specific to this inference event: ZK proof bytes, [EIP-712](./eip-712.md) signature, TEE attestation report, or multisig aggregate. + +#### `proofSystem()` + +Identifies what kind of proof system this verifier implements. Returns a `{paradigm}/{variant}` string: + +- **`paradigm`** — the verification paradigm, determining the trust model and security assumptions (see table below) +- **`variant`** — the specific implementation within that paradigm + +| Paradigm | Trust model | Security assumptions | `proofSystem()` examples | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `zk` | Mathematical: ZK circuit proves a specific model produced a given output from a given input | Soundness of the ZK proving system and correctness of the circuit | `zk/sp1`, `zk/ezkl` | +| `op` | Economic: execution is assumed correct within a challenge window; fraud proofs slash the submitter | At least one honest challenger is watching within the challenge window | `op/ora` | +| `tee` | Hardware: a trusted enclave attests that execution occurred in an isolated environment | Trustworthiness of the hardware manufacturer; no side-channel or supply-chain compromise | `tee/nitro`, `tee/marlin` | +| `attestation` | Authoritative: one or more authorized signers certify the result — covers oracle gateways, multisig validator networks, and judgment validators | Authorized signer set is not compromised; accountability is reputational or stake-based depending on the variant | `attestation/multisig`, `attestation/wyriwe`, `attestation/judgment` | + +#### `proofProfile()` + +Returns a collision-resistant `bytes32` identifier for this specific verifier deployment. Where `proofSystem()` identifies the proof system type, `proofProfile()` identifies the exact deployment — allowing consumers to distinguish between, for example, two `zk/sp1` verifiers compiled against different circuit versions, or two `tee/nitro` verifiers with different enclave measurements. + +`proofProfile()` is included in `verificationDigest`: the same inputs verified by different proof system deployments will produce different digests, making the audit trail unambiguous. + +--- + +### IAgentVerifier — Outer Application Layer + +`IAgentVerifier` is the outer application layer. Three defining properties: + +- **Stateful** — stores deployment-time configuration: which `IProofVerifier`(s) to use, agent authorization rules, `metadata` to pass to the inner verifier, and optionally task tracking state +- **Application layer** — orchestrates agent-specific authorization and verification logic; shields settlement contracts from proof-system differences so callers invoke the same `verify()` interface regardless of whether the backend is ZK, TEE, or attestation +- **Implemented by agent developers** — the parties who deploy verification services for their agents, configuring authorization rules and `IProofVerifier` backends; called by settlement protocols (e.g. ERC-8183) via `IAgentVerifiable`, with the cryptographic internals remaining the exclusive concern of proof system providers + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IProofVerifier.sol"; + +/// @title IAgentVerifier +/// @notice Stateful application-layer verification interface. +/// Implemented by application developers. Wraps one or more IProofVerifier(s). +/// Answers: "for this task, was this agent authorized to make this claim, +/// and does the proof confirm it?" +interface IAgentVerifier { + /// @notice SHOULD be emitted on every verify() call — both passing and failing. + /// Carries the raw preimage fields of verificationDigest, enabling any observer + /// to independently recompute the digest and confirm inclusion per OCP + /// (recompute → compare → confirm). + event VerificationCompleted( + bytes32 indexed taskId, + bytes32 indexed agentId, + bytes32 indexed verificationDigest, + bool valid, + bytes32 inputHash, + bytes32 outputHash, + bytes32 agentProofProfile, // fingerprint of the verification logic used for this event + uint256 timestamp // block.timestamp at verification time; included in preimage + ); + + /// @notice Verify an agent's inference claim for a specific task. + /// @param taskId Task identifier. Binds verification to a single task. + /// Callers SHOULD encode uint256 IDs as bytes32 + /// (e.g. ERC-8183 jobId, settlement periodId). + /// @param agentId Identity of the agent that performed the inference. + /// Checked against the implementation's stored authorization config. + /// @param inputHash Commitment to the model input. + /// @param outputHash Commitment to the model output. + /// @param proof Cryptographic evidence, passed unchanged to the inner IProofVerifier(s). + /// @return valid Whether the agent is authorized and the proof is valid. + /// @return verificationDigest Digest fingerprinting this verification event, computed from + /// taskId, agentId, inputHash, outputHash, valid, agentProofProfile, + /// and block.timestamp. Returned regardless of outcome. MAY be used as an on-chain + /// audit anchor (e.g. ERC-8183 job.reason). + /// @dev NOT view — implementations MAY write state (e.g. single-use taskId tracking). + function verify( + bytes32 taskId, + bytes32 agentId, + bytes32 inputHash, + bytes32 outputHash, + bytes calldata proof + ) external returns (bool valid, bytes32 verificationDigest); + +} +``` + +#### Stateful Design + +`IAgentVerifier` is stateful by design. Its state falls into two categories: + +**Application state** — deployment-time application rules specific to the agent. What to store here is left to the implementation; typical examples include agent authorization configuration (allowlist, registry reference, on-chain role), task state for single-use enforcement, model or policy requirements, and accepted time windows. + +**`IProofVerifier` reference(s) and `metadata`** — which proof backend(s) to call and the invariant `metadata` to pass each one. Typical examples include a single `IProofVerifier` address with its corresponding circuit key or enclave hash encoded as `metadata`, or a keyed mapping of verifiers for multi-paradigm deployments. Both are set at deployment and SHOULD be immutable; `metadata` encoding is specific to each `IProofVerifier` and opaque to callers above. + +This ERC does not mandate the internal structure of `IAgentVerifier` implementations. Implementations MAY use a single `IProofVerifier`, an array, a mapping, or any other arrangement. The only normative requirement is the `verify()` signature above. + +#### Verification Commitment + +`verify()` returns `(bool valid, bytes32 verificationDigest)`. The `verificationDigest` is a commitment to the full verification event, computed from: + +``` +keccak256(abi.encode(taskId, agentId, inputHash, outputHash, valid, agentProofProfile, block.timestamp)) +``` + +Including `valid`, `agentProofProfile`, and `block.timestamp` ensures that the same inputs verified by different proof systems, at different times, or with different outcomes, produce different digests. `block.timestamp` makes the digest self-contained — the verification time is encoded directly rather than requiring block lookup. + +`VerificationCompleted` SHOULD be emitted on every call — passing and failing — carrying the same fields as the preimage. This follows OCP's commitment model: any external observer can independently verify a `VerificationCompleted` record by applying the recompute → compare → confirm inclusion procedure, without calling any contract and trusting any intermediary. + +`verificationDigest` MAY be stored as an on-chain audit anchor by the settlement contract (e.g. as ERC-8183's `job.reason`). + +--- + +### IAgentVerifiable — Declaration Layer + +Settlement contracts MUST implement `IAgentVerifiable` to declare which `IAgentVerifier` they use and MUST emit `AgentVerifierUpdated` on any change. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IAgentVerifier.sol"; + +/// @title IAgentVerifiable +/// @notice Interface for settlement contracts that declare which agent verifier they use. +interface IAgentVerifiable { + /// @notice Returns the agent verifier used by this contract. + function getAgentVerifier() external view returns (IAgentVerifier); + + /// @notice MUST be emitted when the agent verifier address changes. + event AgentVerifierUpdated( + address indexed previousVerifier, + address indexed newVerifier + ); +} +``` + +--- + +### Interface Detection + +All implementations SHOULD support [ERC-165](./eip-165.md): + +```solidity +// IProofVerifier +function supportsInterface(bytes4 interfaceId) external view returns (bool) { + return interfaceId == type(IProofVerifier).interfaceId + || interfaceId == type(IERC165).interfaceId; +} + +// IAgentVerifier +function supportsInterface(bytes4 interfaceId) external view returns (bool) { + return interfaceId == type(IAgentVerifier).interfaceId + || interfaceId == type(IERC165).interfaceId; +} +``` + +--- + +## Rationale + +### Primitive: OCP as the Unifying Model + +OCP (Observation Commitment Protocol) is a system-agnostic verification primitive: given the emitted event data, any observer can independently verify a commitment without trusting the system that produced it or relying on any contract. It defines the minimum procedure any proof system must satisfy: + +``` +recompute → compare → confirm inclusion +``` + +This ERC adopts OCP as the unifying model for `IAgentVerifier` events — not only because every proof backend satisfies the primitive, but because `VerificationCompleted` maps directly onto it: the event is the observation, `verificationDigest` is the commitment, and the emitted fields are the preimage. + +`verificationDigest` is computed from `(taskId, agentId, inputHash, outputHash, valid, agentProofProfile, block.timestamp)`. The event carries those same fields alongside the digest, making every record self-describing and independently auditable: + +- **On-chain**: recompute the digest from held parameters and compare against what was emitted +- **Off-chain**: apply all three steps — recompute from emitted fields, compare, confirm event inclusion + +`confirm inclusion` requires reading event logs, which is off-chain only. `IProofVerifier.verify()` handles the first two steps on-chain; external observers handle the third via `VerificationCompleted` records. The same primitive thus covers both contexts without requiring contracts to fetch their own logs. + +`agentProofProfile` in the digest ensures that the same inputs verified by different deployments produce different digests. + +### Architecture: Two Composable, Opaque Layers + +The split between `IAgentVerifier` (outer application layer) and `IProofVerifier` (inner algorithm layer) exists because two independent parties must implement the standard: + +- **Proof system providers** implement `IProofVerifier`. They know their cryptographic details but not the calling application context. `IProofVerifier` is **stateless** — one deployed instance serves many `IAgentVerifier` deployments without redeployment; `metadata` carries all invariant configuration and is owned by `IAgentVerifier`. +- **Agent developers** implement `IAgentVerifier`. They know their application rules but should not be coupled to a specific proof system. `IAgentVerifier` is **stateful** — authorization rules, model requirements, and task tracking belong in state. + +Settlement contracts MUST NOT call `IProofVerifier` directly — doing so would require knowing the backend's `metadata` encoding. The two-layer boundary keeps each party isolated: + +``` +IAgentVerifier.verify(taskId, agentId, inputHash, outputHash, proof) + → check taskId (if single-use) + → check agentId authorization (if needed) + → delegate to IProofVerifier(s) internally + → compute verificationDigest + → return (valid, verificationDigest) +``` + +**Opacity.** The two-layer design achieves opacity at every level: + +- Settlement contracts are **opaque to the proof backend** — they call `IAgentVerifier.verify()` the same way regardless of whether the backend is ZK, TEE, or attestation. +- `IAgentVerifier` is **opaque to the settlement contract** — its internal verifier configuration, `metadata` encoding, and authorization rules are not exposed to callers. + +### Query: Input Provenance + +`inputHash` commits to whatever input was provided but says nothing about its origin. A verifier confirms "this model produced this output from this input" — not whether the input was legitimate. + +WYRIWE fills this gap with a three-commitment scheme: + +``` +rawInputHash = keccak256(raw_user_input) +sanitizationPipelineHash = keccak256(sanitization_spec_cid || rawInputHash) +inputHash = keccak256(sanitized_input) +``` + +`IProofVerifier.verify()`'s `inputHash` maps directly to WYRIWE's `input_hash` — same field, same construction. `WyriweProofVerifier` is already deployed on mainnet, implements `IProofVerifier`, and can be passed directly to any `IAgentVerifier`. + +Consumers requiring input provenance SHOULD adopt WYRIWE alongside `verify()`. This ERC does not prescribe how `inputHash` is derived. + +### Result: Proof Anchoring + +`verify()` confirms cryptographic validity but not *when* the proof was generated. A consumer can confirm "this proof checks out" but not "the proof was committed before any action was taken." + +Proof anchoring fills this gap: agents anchor a `proofHash` on-chain at inference time via an anchor registry. Consumers requiring proof binding SHOULD verify the anchor alongside `verify()`. + +The anchor check is an application-layer concern — it belongs in `IAgentVerifier`, not `IProofVerifier`. Implementations can accept a Merkle inclusion proof inside `proof` to confirm `proofHash` exists in the anchor registry. + +| | Purpose | +|--|-------| +| `proofHash` | "This proof was committed at time T" | +| `verificationDigest` | "This verification event occurred with these parameters" | + +--- + +## Backwards Compatibility + +No backwards compatibility issues. This ERC introduces new interfaces and does not modify any existing standard. + +--- + +## Reference Implementation + +### Coding in Practice + +The following three examples form a complete composable stack. Each layer is independently deployable; Layer 2 wraps Layer 1 and Layer 3 wraps Layer 2. + +#### Layer 1 — IProofVerifier: Proof System Backend + +`SP1ProofVerifier` implements the ZK paradigm. `proofSystem()` is declared `public` so `proofProfile()` can call it internally. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IProofVerifier.sol"; + +/// @title SP1ProofVerifier +/// @notice IProofVerifier implementation for SP1 zkVM AI inference proofs. +/// +/// metadata: abi.encode(bytes32 programVKey) +/// programVKey — SP1 verification key identifying the ZK circuit for this model +/// proof: SP1 proof bytes (Plonk or Groth16 format) +contract SP1ProofVerifier is IProofVerifier { + + bytes32 private constant VERIFIER_HASH = + 0x1b34fe11a637737f0c75c88241669dcf9ca3c03713659265b8241f398a2d286d; + + function verify( + bytes32 inputHash, + bytes32 outputHash, + bytes calldata metadata, + bytes calldata proof + ) external returns (bool) { + bytes32 programVKey = abi.decode(metadata, (bytes32)); + // Verify the SP1 ZK proof: circuit identified by programVKey produced + // outputHash from inputHash. Delegates to SP1 Plonk verifier contract. + // Full implementation omitted; see SP1 documentation. + return true; + } + + function proofSystem() public pure returns (string memory) { + return "zk/sp1"; + } + + function proofProfile() external pure returns (bytes32) { + return keccak256(abi.encodePacked(proofSystem(), VERIFIER_HASH)); + } +} +``` + +#### Layer 2 — IAgentVerifier: Wraps IProofVerifier + +`AgentVerifier` is constructed with an `IProofVerifier` instance (e.g. `SP1ProofVerifier` from Layer 1), deployment `metadata`, and an authorized agent set. The inner verifier configuration is held in contract state — the settlement contract in Layer 3 never sees `metadata` or the verifier address. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IAgentVerifier.sol"; +import "./IProofVerifier.sol"; + +/// @title AgentVerifier +/// @notice IAgentVerifier implementation wrapping a single IProofVerifier backend. +/// +/// metadata is stored at deployment and passed to the inner verifier on every call. +contract AgentVerifier is IAgentVerifier { + + IProofVerifier private immutable _verifier; + bytes private _metadata; + + mapping(bytes32 => bool) private _authorizedAgents; + mapping(bytes32 => bool) private _usedTasks; + + constructor( + IProofVerifier verifier_, + bytes memory metadata_, + bytes32[] memory authorizedAgents_ + ) { + _verifier = verifier_; + _metadata = metadata_; + for (uint256 i = 0; i < authorizedAgents_.length; i++) { + _authorizedAgents[authorizedAgents_[i]] = true; + } + } + + function verify( + bytes32 taskId, + bytes32 agentId, + bytes32 inputHash, + bytes32 outputHash, + bytes calldata proof + ) external returns (bool valid, bytes32 verificationDigest) { + if (!_usedTasks[taskId] && _authorizedAgents[agentId]) { + valid = _verifier.verify(inputHash, outputHash, _metadata, proof); + if (valid) _usedTasks[taskId] = true; + } + + bytes32 agentProofProfile = _verifier.proofProfile(); + uint256 ts = block.timestamp; + verificationDigest = keccak256(abi.encode( + taskId, agentId, inputHash, outputHash, valid, agentProofProfile, ts + )); + + emit VerificationCompleted( + taskId, agentId, verificationDigest, valid, inputHash, outputHash, agentProofProfile, ts + ); + } +} +``` + +#### Layer 3 — Settlement Contract (ERC-8183): Wraps IAgentVerifier + +`ProofEvaluator` implements `IAgentVerifiable`, declares its `IAgentVerifier` via `getAgentVerifier()`, and calls `verify()` at settlement time. `verificationDigest` is forwarded as `job.reason` to the ERC-8183 ACP protocol. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IAgentVerifiable.sol"; +import "./IAgentVerifier.sol"; + +interface IACP { + function complete(uint256 jobId, bytes32 reason, bytes calldata data) external; + function reject(uint256 jobId, bytes32 reason, bytes calldata data) external; +} + +contract ProofEvaluator is IAgentVerifiable { + IAgentVerifier private _agentVerifier; + IACP private immutable _acp; + + constructor(IAgentVerifier agentVerifier_, IACP acp_) { + _agentVerifier = agentVerifier_; + _acp = acp_; + emit AgentVerifierUpdated(address(0), address(agentVerifier_)); + } + + function getAgentVerifier() external view returns (IAgentVerifier) { + return _agentVerifier; + } + + function settle( + uint256 jobId, + bytes32 agentId, + bytes32 inputHash, + bytes32 outputHash, + bytes calldata proof + ) external { + bytes32 taskId = bytes32(jobId); + (bool valid, bytes32 verificationDigest) = + _agentVerifier.verify(taskId, agentId, inputHash, outputHash, proof); + + if (valid) _acp.complete(jobId, verificationDigest, ""); + else _acp.reject(jobId, verificationDigest, ""); + } +} +``` + +### Composability in Practice + +#### ERC-8183 — Agentic Commerce + +ERC-8183 defines a job lifecycle where an evaluator calls `complete()` or `reject()` after assessing a submission. Four decisions apply directly: + +**`complete()` signature.** The evaluator implements `IAgentVerifiable`; the verifier is resolved at settlement via `getAgentVerifier()` — no pre-registration required. The caller supplies only agent-level claims: + +```solidity +complete(jobId, agentId, inputHash, outputHash, proof) +``` + +`metadata` and the verifier address are owned by `IAgentVerifier` internally. + +**`job.reason` = `verificationDigest`.** ERC-8183's `bytes32 reason` field is the natural anchor for `verificationDigest` — computed from all context, unique per event, and OCP-verifiable by any observer. + +**`verify()` returns `bool`.** No reconstruction needed: + +```solidity +(bool valid, bytes32 verificationDigest) = + getAgentVerifier().verify(bytes32(jobId), agentId, inputHash, outputHash, proof); + +if (valid) ACP.complete(jobId, verificationDigest, ""); +else ACP.reject(jobId, verificationDigest, ""); +``` + +**`inputHash` follows WYRIWE** — `keccak256(sanitized_input)`, binding verification to the full provenance chain. + +#### WYRIWE — Input Provenance + +WYRIWE makes `IProofVerifier`'s `inputHash` auditable. The base interface commits to a value — WYRIWE commits to *how that value was derived*, binding the signed inference receipt to the provenance chain that produced its input. + +**Triple-hash chain** (normative in WYRIWE): + +``` +rawInputHash = keccak256(raw_user_input) +sanitizationPipelineHash = keccak256(sanitization_spec_cid || rawInputHash) +inputHash = keccak256(sanitized_input) +``` + +`sanitizationPipelineHash` binds the public pipeline spec to *this specific raw input* (a pipeline commitment cannot be replayed against a different input), and `inputHash` commits to the exact bytes the model received. The verification invariant: given the three hashes and the public spec at `sanitization_spec_cid`, any verifier can recompute the pipeline over the raw input and confirm `inputHash` — no party is trusted to assert it. The sentinel case (no transformation) is a provable claim, not an assumption: `sanitizationPipelineHash = keccak256(IDENTITY_SENTINEL_CID || rawInputHash)`. + +**Two commitments, two auditor questions** (they are complementary, not contradictory): `sanitizationPipelineHash` proves *a specific pipeline was declared and bound to a specific raw input* — faithful application is the prover's commitment, not something the hash alone enforces. `inputHash` proves *what the model actually received*. A conformant implementation produces both; the verification invariant (recompute the pipeline over the raw input, confirm `inputHash`) is what ties declaration to delivery. + +**Inner layer.** `proofSystem()` returns `"attestation/wyriwe"`. `verify()` recomputes the commitment binding `{agentId, modelHash, inputHash, outputHash, timestamp}`, recovers the signer from the EIP-712 `WyriweAttestation` signature, and checks it against the ERC-8004 registry declared in `agentData` — pure recomputation, no external state reads beyond the registry check. **Layer separation holds:** an operator running only the inner layer gets receipt verification; adding the outer layer gets identity-bound accountability. + +**Judgment as provenance one layer up.** The same chain-of-custody shape maps slot-for-slot onto judgment (WYRIWE §L4): `rawProposalHash` ↔ `rawInputHash`, `verdictHash` ↔ `sanitizationPipelineHash` (the verdict is the public spec transforming *proposed* → *permitted*), `executedActionHash` ↔ `inputHash`. The binding rule carries over unchanged — `verdictHash = keccak256(verdict_artifact_ref || rawProposalHash)` — so a verdict cannot be shopped against a different proposal, exactly as a pipeline commitment cannot be replayed against a different input. This is the `JudgmentExecutionAttestation` used by the Judgment Validator subsection above; the two attestations compose without sharing fields. + +**Confidence and aggregation.** WYRIWE attestations do not carry a verdict score — they carry a tamper-evident receipt of what ran. Confidence derivation follows the Type A model established above: a pure function of settled commit-reveal records, no trusted updater, no mutable on-chain score. A bare signed receipt without a settled outcome does not satisfy the Type A invariant; Type B (usage-based) weight carries the same self-dealing risk noted above. Aggregation and weighting live in `IAgentVerifier`, above the inner layer. + +**Live proof.** Ledger entry 23 (`api.babyblueviper.com/ledger/23`) is the first production instance of this composition: + +| Step | Detail | +| --- | --- | +| Identity | PGA #14, tokenId 14 in the dinamic AgentIdentityRegistry (ERC-8004-style) — mainnet `ownerOf` checkable | +| Provenance | Non-sentinel: `rawProposalHash` → `verdictHash` → `executedActionHash` | +| Verdict | `approve_with_concerns`, confidence 0.85 — concerns: identity-binding, impersonation | +| Timing | Verdict timestamp strictly before execution timestamp — ordering invariant independently verifiable | +| Anchor | Entry 23 verdict event id → TruthAnchorV1 (proof anchoring) — anchor leg pending | + +#### Judgment Validator — `attestation/judgment` + +A **judgment claim** asserts that a validator examined a proposed action and issued a verdict on its soundness — in settings where the right answer is not computable at decision time (pre-trade review, contribution-reward evaluation, compliance sign-off). Verification establishes **authenticity, never soundness**: `verify()` answers *"is this authentically the named validator's verdict over exactly this input?"* It MUST NOT be read as *"the action is sound."* + +**Inner-layer wiring.** `proofSystem()` returns `"attestation/judgment"`. `verify()` checks the validator's signature over the canonical payload binding `{verdict object, inputHash, validator identity}`; the scheme is committed by `proofProfile()` (the production reference uses schnorr over a Nostr event). `outputHash` commits to the verdict object: + +```json +{ + "verdict": "approve | approve_with_concerns | reject", + "confidence": 0.85, + "issues": ["", "..."] +} +``` + +Judgment verdicts are trinary, not binary. `codeMeasurement` MUST be absent for judgment claims — absent, not zero-filled. + +**`committed_at` and `judgment_type` (REQUIRED for `attestation/*`).** Two fields make the commit-before-outcome guarantee checkable rather than assumed: + +- `committed_at` — the timestamp at which the *verdict* was committed, sourced from the **proof anchoring proofHash leg** (where `proofHash` is the verdict event id). This is a distinct commitment target from the **OCP anchor**, which commits the *input* (the prompt/task — *when it was issued*). The two anchor different things at different points in the chain — input-commitment (OCP) -> verdict-commitment (proof anchoring) -> settlement — and a verifier MUST treat them as separate, never substituting one for the other. `committed_at` MUST be provably prior to the outcome the verdict is graded against. +- `judgment_type` — discriminates *what* `committed_at` is checked against: + - `outcome_verifiable` (Type A): `committed_at` MUST predate the realized outcome's settlement. + - `consensus_weighted` (Type B): `committed_at` MUST predate the reveal. + +The field is universal across `attestation/*`; the `judgment_type` discriminator carries the per-type reference point — which is why the two are specified together. + +**How the deterministic bool emerges.** Judgment is non-deterministic; the interface output is not. Aggregation lives in `IAgentVerifier` (never the inner layer), by claim type: + +- **Type A — outcome-verifiable.** Validator confidence weights are *derived* as a pure function of settled commit-reveal records (verdicts committed on-chain before outcomes, revealed after) — recomputable by any party from chain state, with no updater-trust problem. **Outcome-oracle dependency, stated explicitly:** this recomputation is trustless only where outcomes settle on-chain; off-chain-settled outcomes (an exchange account, a counterparty system) require an outcome oracle, and the aggregation inherits that oracle's trust assumptions. Consumers MUST be able to distinguish on-chain-settled from oracle-attested records. +- **Type B — non-verifiable.** Usage-based weight MUST count distinct counterparties with their own at-stake identity, not call volume, and SHOULD be capped relative to outcome-derived weight (the self-dealing guard). + +The resulting bool gates settlement exactly like any other backend (see the ERC-8183 wiring above): authenticity from the inner layer, threshold over derived weights at the outer. + +**`recordPointer` accountability invariants** (normative for judgment claims; the record format is reference data): + +1. Losses MUST be present — a record showing only wins is marketing, not accountability. +2. Outcomes MUST settle somewhere the validator cannot edit. +3. The pre-outcome timestamp (`committed_at`, above) MUST be anchored at issue time to a system the validator does not control. For the verdict this is the proof anchoring proofHash leg — NOT the OCP input anchor (which commits the input, not the verdict; see `committed_at` above). It is the same kind of primitive as OCP's observation commitment, applied one layer up to the verdict. + +Commitment and outcome evidence MUST be separately addressable: `{recordPointer}/commitment` and `{recordPointer}/outcome`. + +**Execution binding (optional).** Where the judged action is subsequently executed, the reviewed→executed chain of custody binds with the WYRIWE L4 attestation — `JudgmentExecutionAttestation(... rawProposalHash, verdictHash, executedActionHash ...)` — committed at verdict time, revealed post-execution. + +**Production reference.** `https://api.babyblueviper.com/ledger` — a judgment validator running against real capital: 26 signed entries, 10 wins / 9 losses across 19 settled outcomes (losses published by design); every verdict relay-anchored at issue time with per-entry retention status; evidence-separability sub-paths live; first on-chain commit-reveal settlement of a judgment attestation on Sepolia (`GenericCommitRevealSettler`, entry 19 — `committedAt < revealedAt` checkable on-chain); and a live cross-stack trace (entry 23) binding a registry identity through a signed pre-action verdict to an executed action via the triple-hash. Offered as reference data, not a required format. + + +--- + +## Security Considerations + +**IProofVerifier trust boundary** + +This ERC defines the interface but does not vet `IProofVerifier` implementations. Agent developers MUST NOT assume a given `IProofVerifier` is secure simply because it satisfies the interface. Before configuring a backend, agent developers SHOULD independently assess its cryptographic assumptions, audit status, and known attack surface. A backend that always returns `true`, uses a broken circuit, or accepts replayed attestations will silently undermine the entire verification chain. + +**Trust chain** + +``` +settlement contract → IAgentVerifier → IProofVerifier[] +``` + +Each link SHOULD be audited independently. The same trust boundary applies at every layer: settlement contracts MUST NOT assume a given `IAgentVerifier` is secure simply because it satisfies the interface, just as agent developers MUST NOT assume the same of `IProofVerifier`. `IAgentVerifier` does not expose its internal verifier set; consumers requiring auditability SHOULD verify the full deployment configuration at setup time. + +**Replay protection** + +`IProofVerifier` does not define replay protection. A valid proof over `(inputHash, outputHash)` may replay across contexts if the underlying proof system does not bind to a chain ID, contract address, or nonce. Replay protection, if required, SHOULD be implemented in `IAgentVerifier`. + +**Verification commitment self-containment** + +`VerificationCompleted` is designed to be independently verifiable without querying any contract state. On-chain state is mutable — contracts may be upgraded, storage may change, and authorization rules may evolve. If reconstructing or confirming a `verificationDigest` required reading live contract state, historical records would become unverifiable as that state changed. The event carries all preimage fields alongside the digest precisely so that any observer can verify a past event against the immutable log alone, independent of current contract state. + +--- + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md).