ERC-7992 - Verifiable ML Model Inference (ZKML)

Created 2025-07-23
Status Draft
Category ERC
Type Standards Track
Authors
Requires

Abstract

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

proofSystemId MUST equal the first four bytes of:

bytes4(keccak256(abi.encodePacked(<canonical-proof-system-name-and-version>)))

Where <canonical-proof-system-name-and-version> is a lowercase, hyphen-separated string, e.g.: - "groth16-bn254-v1" - "plonk-bn254-v2" - "stark-airfoo-v1"

This method ensures deterministic, collision-resistant identifiers across implementations.

Interfaces

ZKML Registry

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

interface IERCZKMLRegistry /* is IERC165 */ {
    struct ModelCommitment {
        bytes32 modelHash;     // weights + architecture hash
        bytes32 circuitHash;   // arithmetic circuit / AIR hash
        bytes32 vkHash;        // verifying key hash
        bytes4  proofSystemId; // keccak-based identifier
        string  uri;           // optional off-chain metadata (IPFS/HTTP)
    }

    event ModelRegistered(
        uint256 indexed modelId,
        address indexed owner,
        ModelCommitment commitment
    );

    event ModelUpdated(
        uint256 indexed modelId,
        ModelCommitment oldCommitment,
        ModelCommitment newCommitment
    );

    event ModelDeprecated(uint256 indexed modelId);

    error ModelNotFound(uint256 modelId);
    error NotModelOwner(uint256 modelId, address caller);
    error ModelDeprecated(uint256 modelId);

    function registerModel(ModelCommitment calldata commitment)
        external
        returns (uint256 modelId);

    function updateModel(uint256 modelId, ModelCommitment calldata newCommitment)
        external;

    function deprecateModel(uint256 modelId) external;

    function getModel(uint256 modelId)
        external
        view
        returns (ModelCommitment memory commitment, bool deprecated, address owner);
}

ZKML Verifier

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

interface IERCZKMLVerifier /* is IERC165 */ {
    event InferenceVerified(
        uint256 indexed modelId,
        bytes32 indexed inputCommitment,
        bytes   output,
        address indexed caller
    );

    error InvalidProof();
    error ModelMismatch();            // proof verifies but not tied to given modelId
    error InputCommitmentMismatch();
    error OutputMismatch();           // if verifier checks output commitment/schema
    error UnsupportedProofSystem(bytes4 proofSystemId);
    error VerificationRefused_ModelDeprecated(uint256 modelId);

    /**
     * @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
     */
    function verifyInference(
        uint256 modelId,
        bytes32 inputCommitment,
        bytes calldata output,
        bytes calldata proof
    ) external;

    /// Optional helper views:
    function proofSystemOf(uint256 modelId) external view returns (bytes4);
    function registry() external view returns (address registryAddress);
}

The verifyInference:

Optional Storage Extension

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

interface IERCZKMLStorageExtension {
    event InferenceStored(bytes32 indexed inferenceId);

    /**
     * @notice Verify and store an inference record.
     * @dev MUST revert on invalid proof.
     * @return inferenceId keccak256(abi.encodePacked(modelId, inputCommitment, output))
     */
    function verifyAndStoreInference(
        uint256 modelId,
        bytes32 inputCommitment,
        bytes calldata output,
        bytes calldata proof
    ) external returns (bytes32 inferenceId);

    function getInference(bytes32 inferenceId)
        external
        view
        returns (uint256 modelId, bytes32 inputCommitment, bytes memory output);
}

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

Backwards Compatibility

Fully backwards compatible:

Reference Implementation

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

import "./IERCZKMLRegistry.sol";
import "./IERCZKMLVerifier.sol";

contract ZKMLVerifier is IERCZKMLVerifier {
    IERCZKMLRegistry public immutable override registry;

    constructor(IERCZKMLRegistry _registry) {
        registry = _registry;
    }

    function verifyInference(
        uint256 modelId,
        bytes32 inputCommitment,
        bytes calldata output,
        bytes calldata proof
    ) external override {
        (IERCZKMLRegistry.ModelCommitment memory cm, bool deprecated,) =
            registry.getModel(modelId);

        if (deprecated) revert ModelDeprecated(modelId);

        // Dispatch based on proofSystemId. Example only.
        // bool ok = VerifierLib.verify(proof, cm.vkHash, inputCommitment, output);
        bool ok = _dummyVerify(proof, cm.vkHash, inputCommitment, output);
        if (!ok) revert InvalidProof();

        emit InferenceVerified(modelId, inputCommitment, output, msg.sender);
    }

    function proofSystemOf(uint256 modelId) external view override returns (bytes4) {
        (IERCZKMLRegistry.ModelCommitment memory cm,,) = registry.getModel(modelId);
        return cm.proofSystemId;
    }

    function _dummyVerify(
        bytes calldata,
        bytes32,
        bytes32,
        bytes calldata
    ) private pure returns (bool) {
        return true;
    }
}

Security Considerations

Security

Gas

Copyright

Copyright and related rights waived via CC0.