--- eip: 8036 title: MultiTrustCredential (MTC) ZK Presentation description: Verify ZK proofs on MTC commitments with a fixed Groth16 ABI, tied to the current anchor and active policy. author: Yuta Hoshino (@YutaHoshino) discussions-to: https://ethereum-magicians.org/t/erc-8036-zero-knowledge-presentation-interface-for-multitrust-credential-8035/25648 status: Draft type: Standards Track category: ERC created: 2025-09-19 requires: 165, 8035 --- ## Abstract This proposal standardizes an implementation-agnostic interface for on-chain verification of Groth16 proofs over committed metrics, enabling predicate checks with minimal disclosure. It standardizes how a verifier contract (or an MTC deployment that integrates a verifier) accepts a proof and public signals showing that a subject satisfies a predicate over a committed metric (e.g., value ≥ threshold, membership, range/delta) without revealing the underlying value. Proofs are bound to a domain (chainId + contract address) and to the current anchor root used by the credential system to prevent cross-contract/cross-chain replay and substitution. In typical deployments, the anchor root is the current Merkle root committed on-chain for the subject’s metric, which this interface treats as the verification reference. This interface is designed to be used alongside an address-bound credential system (MultiTrustCredential), but can be integrated by any contract that maintains committed metrics and an anchor root. ## Motivation Applications and circuits need a shared, minimal way to verify that a subject’s metric satisfies a policy (e.g., “score ≥ threshold”) without exposing raw values. A common ABI enables wallets, circuits, verifiers, and indexers to interoperate across ecosystems while keeping personally identifiable information (PII) off-chain. More expressive predicates (e.g., bounded ranges or epoch-based deltas) are common in credit and risk modules, motivating a standardized extension path at the presentation layer. ## Specification The key words “MUST”, “MUST NOT”, “SHOULD”, and “MAY” are to be interpreted as described in RFC 2119 and RFC 8174. ### [ERC-165](./eip-165.md) Compliance (normative) Implementations of this standard MUST implement [ERC-165](./eip-165.md) and report the interface id for `IMultiTrustCredentialZK`. Implementations that support `IMultiTrustCredentialZKEx` MUST also report the interface id for `IMultiTrustCredentialZKEx`. The interface id is the XOR of all function selectors defined in this interface. Implementations MUST NOT change function signatures in a way that alters the interface id. ### External Verifier (normative) ```solidity pragma solidity ^0.8.22; /// @notice External circuit verifier (e.g., Groth16 via snarkJS). /// @dev This EIP's reference profile uses 6 public signals; other profiles SHOULD use IMultiTrustCredentialZKEx. interface IVerifier { /// @dev Circuit-specific verification with fixed-length proof/public inputs. function verifyProof( uint256[2] calldata a, uint256[2][2] calldata b, uint256[2] calldata c, uint256[6] calldata publicSignals ) external view returns (bool); } ``` ### ZK Presentation Interface (normative) ```solidity pragma solidity ^0.8.22; /// @title IMultiTrustCredentialZK /// @notice Verify a ZK proof against the current MTC Core anchor and the active comparison policy. /// @dev Additive extension to MTC Core; does not modify Core semantics. interface IMultiTrustCredentialZK { /// @return ok True if proof is valid AND bound to the current anchor/policy for (tokenId, metricId). function proveMetric( uint256 tokenId, bytes32 metricId, uint256[2] calldata a, uint256[2][2] calldata b, uint256[2] calldata c, uint256[6] calldata publicSignals ) external view returns (bool ok); } ``` ### Extended ZK Presentation Interface (normative) This EIP defines an additive extension to the fixed Groth16 [1] interface to support richer predicates such as bounded ranges and epoch-based delta constraints, without breaking the baseline interface. ```solidity pragma solidity ^0.8.22; /// @title IMultiTrustCredentialZKEx /// @notice Verify extended predicates (e.g., RANGE, DELTA) over MTC metrics. /// @dev This interface is additive and does not replace IMultiTrustCredentialZK. interface IMultiTrustCredentialZKEx { /// @param tokenId Credential token id /// @param metricId Metric schema id /// @param predicateType Identifier of the predicate (e.g., RANGE, DELTA) /// @param proof Opaque proof bytes (verifier-specific) /// @param publicSignals Variable-length public inputs (predicate-specific) function provePredicate( uint256 tokenId, bytes32 metricId, bytes32 predicateType, bytes calldata proof, uint256[] calldata publicSignals ) external view returns (bool ok); } ``` The binding requirements defined in this EIP (anchor binding, address binding, comparison mask enforcement, and domain separation) MUST also apply to provePredicate. ### Public Signals Mapping (normative) This EIP fixes the order of `publicSignals` to match the reference circuit and verifier: ``` publicSignals[0] = mode // operator bitmask: GT=1, LT=2, EQ=4 (combinable); 0 allowed only for KYC-only proofs (see below) publicSignals[1] = root // Merkle root (must equal the Core anchor for the metric) publicSignals[2] = nullifier // circuit-defined nullifier publicSignals[3] = addr // holder address as field element publicSignals[4] = threshold // comparator parameter (e.g., threshold or expected value) publicSignals[5] = leaf // commitment to the underlying value (e.g., Poseidon(score, rand)); the raw value remains private ``` ### Binding Requirements (normative) Implementations of `proveMetric` MUST guarantee that a successful proof is **bound to the current MTC anchor and policy** for `(tokenId, metricId)`: - Implementations MUST obtain the **current** anchor root for (`tokenId`, `metricId`) from the credential system. For the reference profile, implementations MUST call IMultiTrustCredentialCore.getMetric(tokenId, metricId) (as defined in [ERC-8035](./eip-8035.md)) and interpret the returned leafFull as anchorRoot, then MUST verify `publicSignals[1] == anchorRoot`. If the metric is revoked, per Core specification, getMetric MUST revert; ZK verification MUST NOT succeed for revoked metrics. - Implementations MUST enforce the **active comparison mask** from Core for the metric schema; CompareMask domain: bitwise subset of `GT=1, LT=2, EQ=4` (combinations allowed), with aliases such as `GTE=GT|EQ` and `LTE=LT|EQ`. The operator encoded by `mode` MUST be permitted by the mask; otherwise `proveMetric` MUST revert. `mode == 0` MAY be used only for "KYC-only" proofs, where the Core comparison mask for `metricId` is `0` (i.e., no comparison operators are permitted). In that case `proveMetric` MUST verify membership/inclusion and binding constraints, and MUST revert if the mask is non-zero. - Implementations MUST verify `tokenId == tokenIdOf(address(uint160(publicSignals[3])))`, interpreting `publicSignals[3]` as a **big-endian field element whose lower 160 bits map to the EVM address**. If no token exists for that address, the call MUST revert. ### Comparison Semantics (normative) Let `value` denote the committed metric value proven inside the circuit. Implementations MUST interpret `mode` as a bitmask selecting any of {GT, LT, EQ}. The proof MUST satisfy at least one selected relation. For example: - `mode = GT|EQ` means `value ≥ threshold`. - `mode = LT|EQ` means `value ≤ threshold`. - `mode = GT|LT` means `value != threshold`. `value` and `threshold` are treated as **unsigned 256-bit integers**; units/scaling are defined by the metric schema. When `mode == 0` (KYC-only), no comparison is performed; only membership/binding is proven. ### Extended Predicate Semantics (normative) The extended presentation interface defines the following predicate types. Implementations MAY support each predicate type unless otherwise specified: #### RANGE A bounded range predicate proves that a metric value lies within an inclusive range: lower ≤ value ≤ upper The proof MUST enforce both bounds within a single circuit execution. `lower` and `upper` MUST be provided as part of the `publicSignals` passed to `provePredicate`. The exact ordering and encoding of these values is profile-specific. #### DELTA (epoch-based) A delta predicate proves that the absolute change of a metric value between epochs does not exceed a specified threshold: |value(epoch_n) − value(epoch_{n−1})| ≤ Δ An implementation supporting DELTA predicates MUST bind the proof to an explicit `epoch` or `version` identifier and MUST prevent reuse of proofs across epochs (e.g., via epoch-bound nullifiers or statement hashes). The epoch/version identifier MUST be encoded in the `publicSignals` passed to `provePredicate` (profile-specific). #### Freshness and Replay Safety Extended predicates involving epochs or versions MUST ensure freshness by binding the proof to the current anchor and the declared epoch/version. Reuse of proofs from prior epochs MUST be rejected. ### Leaf Construction with Domain Separation (normative) To prevent cross-contract / cross-chain replay without changing the public signals arity, this profile MUST include a domain separator in the tree leaf construction: ``` domain := keccak256(abi.encode(chainid(), verifierContract)) (reduced mod field) treeLeaf := Poseidon(leaf, addr, domain) ``` - The circuit MUST compute `treeLeaf` as above and prove membership in the Merkle tree with `root == publicSignals[1]`. - On-chain contracts MUST rely on the proof (and current root equality) and MUST NOT attempt to reconstruct `leaf` beyond verifying the proof and anchor binding. ### Extensions (non-normative) Some implementations may support proving multiple metrics, group predicates, or domain-specific predicates in a single proof. Such features are provided as non-normative extensions or via the extended presentation interface defined above. ### Proof Verification and Binding (normative) An implementation that verifies a ZK presentation MUST ensure the proof is bound to the intended context: - the subject and metric identifiers (e.g., `tokenId`, `metricId`), - the current anchor root for that metric, - and the active comparison/predicate policy for that metric. Implementations MUST reject proofs whose bound context does not match the current on-chain state. To prevent cross-contract replay, implementations MUST use domain separation in the leaf/statement construction so that the same underlying attribute cannot be presented against unrelated deployments. ### Revocation Checks (normative) Verification MUST fail if the target metric is not currently valid (e.g., revoked, or expired if the implementation supports expiration). ### Public Signals Layout (normative) Implementations MUST validate the expected public input length and indices before evaluating policy checks. ### Optional Events ```solidity /// @notice Emitted when the verifier address is configured (e.g., during initialize()). event VerifierSet(address verifier); /// @notice MAY be emitted when a proof is successfully verified. event ProofVerified(uint256 tokenId, bytes32 metricId, uint8 mode, uint256 threshold); ``` ### References 1. ```csl-json { "id": "10.1007/978-3-662-49896-5_11", "citation-key": "10.1007/978-3-662-49896-5_11", "type": "paper-conference", "title": "On the Size of Pairing-based Non-interactive Arguments", "author": [ { "family": "Groth", "given": "Jens" } ], "container-title": "Advances in Cryptology – EUROCRYPT 2016", "collection-title": "Lecture Notes in Computer Science", "volume": "9666", "page": "305-326", "publisher": "Springer Berlin Heidelberg", "publisher-place": "Berlin, Heidelberg", "issued": { "date-parts": [[2016]] }, "DOI": "10.1007/978-3-662-49896-5_11", "URL": "https://link.springer.com/chapter/10.1007/978-3-662-49896-5_11" } ``` ## Rationale - **Fixed ABI**: A Groth16 tuple is widely supported and keeps calldata minimal; other proving systems MAY adapt behind `IVerifier`. - **Separation of Concerns**: The Core and the presentation layer are separated so deployments can integrate verification without changing Core semantics. - **Binding over leakage**: By binding proofs to Core’s current anchor and the policy mask, implementations avoid value disclosure while ensuring policy is enforced. Domain separation in the leaf construction prevents cross-contract replay while preserving the 6-signal layout. ## Backwards Compatibility This interface is compatible with any MTC Core implementation that stores a commitment (leafFull) and a comparison mask policy. For this reference profile, leafFull is interpreted as anchorRoot. Gas costs for pairing precompiles vary by platform; implementations SHOULD document expected ranges. ## Reference Implementation A non-normative reference will be published with example circuits and an adapter verifier contract demonstrating: - Reading Core’s `getMetric` to retrieve the current `leafFull` (root) - Enforcing the Core comparison mask against `mode` - Mapping `publicSignals` per the order above - Domain-separated leaf construction: `treeLeaf = Poseidon(leaf, addr, keccak256(abi.encode(chainid(), verifierContract)))` (where verifierContract is the contract implementing proveMetric / provePredicate, i.e., address(this) in the reference adapter) - Emitting `ProofVerified` on success (optional) ## Security Considerations - **Substitution & Replay**: If proofs are not context-bound, an attacker may reuse a valid proof for a different subject, metric, contract, or after a policy update. Mitigate by binding proofs to the subject/metric context (e.g., tokenId/metricId), the current anchor root, and the active policy, and by using domain separation so the same leaf cannot be reused across unrelated deployments. - **Revocation**: Revocation creates a time-of-check/time-of-use gap if clients or relayers cache old proofs. Implementations typically mitigate this by checking the current on-chain state at verification time and rejecting proofs for revoked metrics. - **Upgrades / Governance**: Changing verifiers or policy parameters can invalidate security assumptions. Operational mitigations include role-gated updates, timelocks, and emitting auditable events so indexers and clients can detect changes. - **Malleability / Input Validation**: Proof verification should fail closed on malformed proofs or unexpected public signal layouts. Defensive parsing and strict length/index checks reduce the risk of bypasses due to decoding or ABI mismatches. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).