This ERC standardizes how smart contracts reference machine-learning (ML) models and accept zero-knowledge attestations of their inferences. It defines a registry that issues a modelId for a ModelCommitment, hashes of the model’s weights/architecture, proving circuit/AIR, and verifying key, along with a proofSystemId for the proving system, and exposes discoverability via ERC-165. A verifier interface provides verifyInference(modelId, inputCommitment, output, proof): it retrieves the model commitment, dispatches verification to the declared proof system, and reverts on any mismatch or invalid proof; success implies validity and emits InferenceVerified. Inputs are bound by domain-separated commitments (nonceable for replay protection), outputs are ABI-encoded bytes whose schema can be application-defined or additionally committed on-chain, and proof systems (e.g., Groth16/Plonk/STARK) are pluggable without ABI changes. An optional extension persists verified inference records to enable auditability and deterministic settlement.
Motivation
Smart contracts can’t run large ML models, and they can’t trust an oracle’s claim about a model’s output.
Today, projects either (1) trust a centralized server, (2) cripple models to fit on-chain, or (3) rely on social committees. None provide cryptographic assurance.
Zero-knowledge ML (ZKML) fixes the trust gap by letting a prover show—succinctly and privately—that a specific model, with specific inputs, produced a specific output.
But without a shared interface, every dApp/verifier pair is bespoke: different ABIs, different registry schemas, poor composability.
This ERC standardizes that on-chain boundary:
Registry: publish immutable commitments to model weights/architecture/circuits so callers know exactly which model they’re referencing.
Verifier: a uniform function to validate inference proofs, independent of proof system (Groth16, Plonk, STARKs, …).
Benefits include:
Trustless, composable “AI oracles” for DeFi risk, prediction markets, insurance, etc.
Protection of proprietary models and private inputs while still guaranteeing correctness.
Deterministic, dispute-free settlement for complex computations.
Lower integration and audit overhead via consistent events, structs, and revert semantics.
Future-proofing as proving systems evolve—only implementations change, not integrators.
Clear security expectations (e.g., nonce usage to prevent replays) baked into the spec.
In short, this ERC turns verifiable ML inference into a reusable primitive—doing for AI outputs what ERC-20/ERC-721 did for assets.
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.
Terminology & IDs
Model Commitment: A bundle of hashes that ties together the model’s weights, architecture, proving circuit/Algebraic intermediate representation (AIR), and verifying key.
modelId (uint256): A unique identifier returned by the registry upon model registration.
inputCommitment (bytes32): A hash commitment to all private inputs (and any declared public inputs) for an inference. Implementations MUST domain-separate and SHOULD include a nonce/salt when single-use or non-deterministic behavior is possible.
output (bytes): ABI-encoded public outputs of the inference. Consumers MUST agree on its schema and MAY validate it via an outputCommitment (not included in the minimal interface).
proof (bytes): The ZK proof blob.
proofSystemId (bytes4): Identifier of the proving system + curve + version used by the circuit.
updateModel and deprecateModel MUST only be callable by the model owner.
getModel MUST return the current commitment, deprecation status, and owner address.
Implementations MAY allow versioning under one modelId or require a new modelId per change. The chosen policy MUST be documented.
ZKML Verifier
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.20;interfaceIERCZKMLVerifier/* is IERC165 */{eventInferenceVerified(uint256indexedmodelId,bytes32indexedinputCommitment,bytesoutput,addressindexedcaller);errorInvalidProof();errorModelMismatch();// proof verifies but not tied to given modelId
errorInputCommitmentMismatch();errorOutputMismatch();// if verifier checks output commitment/schema
errorUnsupportedProofSystem(bytes4proofSystemId);errorVerificationRefused_ModelDeprecated(uint256modelId);/**
* @notice Verifies a ZK proof for an inference.
* @dev MUST revert on any failure path. Successful execution implies validity.
* @param modelId Registry model identifier
* @param inputCommitment Commitment to private inputs
* @param output ABI-encoded public outputs
* @param proof ZK proof bytes
*/functionverifyInference(uint256modelId,bytes32inputCommitment,bytescalldataoutput,bytescalldataproof)external;/// Optional helper views:
functionproofSystemOf(uint256modelId)externalviewreturns(bytes4);functionregistry()externalviewreturns(addressregistryAddress);}
The verifyInference:
MUST fetch the model commitment from the registry and validate the proof against it.
MUST revert on any failure (invalid proof, mismatched commitments, unsupported proof system, deprecated model, etc.).
SHOULD emit InferenceVerified on success.
SHOULD remain stateless except for emitting events. Statefulness MAY be introduced by extensions.
Optional Storage Extension
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.20;interfaceIERCZKMLStorageExtension{eventInferenceStored(bytes32indexedinferenceId);/**
* @notice Verify and store an inference record.
* @dev MUST revert on invalid proof.
* @return inferenceId keccak256(abi.encodePacked(modelId, inputCommitment, output))
*/functionverifyAndStoreInference(uint256modelId,bytes32inputCommitment,bytescalldataoutput,bytescalldataproof)externalreturns(bytes32inferenceId);functiongetInference(bytes32inferenceId)externalviewreturns(uint256modelId,bytes32inputCommitment,bytesmemoryoutput);}
Replay Protection Note: Implementations that rely on
inferenceId = keccak256(modelId, inputCommitment, output)
MUST ensure that inputCommitment embeds a nonce/salt or other uniqueness source
if replays are a concern (e.g., non-deterministic models or single-use inferences).
The registry interface exposes an owner per modelId.
Implementations MUST include some ownership/access-control mechanism
(e.g., simple owner storage, ERC-173, ERC-721 representation, or role-based control).
The returned owner address SHOULD be treated as the canonical authority to mutate or deprecate that model.
Rationale
Void Return on verifyInference: Reverting on failure and returning nothing on success removes redundant gas-expensive booleans and matches modern Solidity patterns (e.g., OpenZeppelin’s SafeERC20).
Separated Registry & Verifier: Encourages modularity—teams can upgrade verifiers or registries independently.
Opaque bytes for Proof/Output: Avoids lock-in to a specific proof system or output schema.
Deterministic proofSystemId: Prevents collisions and ambiguity; enables predictable dispatch in mixed-system verifiers.
Nonce in inputCommitment: Explicitly mitigates replay attacks when inference uniqueness matters.
No dependency on token ownership standards; minimal collision with existing protocols.
Reference Implementation
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.20;import"./IERCZKMLRegistry.sol";import"./IERCZKMLVerifier.sol";contractZKMLVerifierisIERCZKMLVerifier{IERCZKMLRegistrypublicimmutableoverrideregistry;constructor(IERCZKMLRegistry_registry){registry=_registry;}functionverifyInference(uint256modelId,bytes32inputCommitment,bytescalldataoutput,bytescalldataproof)externaloverride{(IERCZKMLRegistry.ModelCommitmentmemorycm,booldeprecated,)=registry.getModel(modelId);if(deprecated)revertModelDeprecated(modelId);// Dispatch based on proofSystemId. Example only.
// bool ok = VerifierLib.verify(proof, cm.vkHash, inputCommitment, output);
boolok=_dummyVerify(proof,cm.vkHash,inputCommitment,output);if(!ok)revertInvalidProof();emitInferenceVerified(modelId,inputCommitment,output,msg.sender);}functionproofSystemOf(uint256modelId)externalviewoverridereturns(bytes4){(IERCZKMLRegistry.ModelCommitmentmemorycm,,)=registry.getModel(modelId);returncm.proofSystemId;}function_dummyVerify(bytescalldata,bytes32,bytes32,bytescalldata)privatepurereturns(bool){returntrue;}}
Security Considerations
Security
Replay Attacks: Inputs must embed a nonce/salt where uniqueness matters. Contracts may also track consumed inferenceIds.
Model Commitment Drift: Updating commitments can invalidate proofs; consumers should pin specific modelId + commitment hashes or check deprecated.
Hash Domain Separation: Use distinct prefixes (e.g., “ZKML_MODEL_V1”) to avoid collisions across contexts.
Output Ambiguity: Contracts must validate or commit to output schemas to avoid maliciously crafted bytes.
DoS via Heavy Verification: Consider off-chain verification with on-chain succinct attestations, or batching/aggregation.
Gas
Proof verification can dominate gas costs; splitting verification-only calls from storage writes lets integrators choose.
Events are cheaper than persistent storage for audit trails.