ERC-8035: MultiTrust Credential (MTC) — Core

Hello everyone,

We are proposing a minimal on-chain anchor for VC-aligned reputation credentials (MTC Core) and an optional ZK presentation interface (MTC-ZK) with a fixed Groth16 ABI—enabling privacy-preserving, interoperable eligibility checks (e.g., score ≥ 80, violations ≤ 2). We are seeking community feedback on the initial draft.

Problem & Motivation

dApps need to evaluate user reputation or eligibility across apps without revealing raw data. Today’s approaches are bespoke or SBT-centric, fragmenting semantics and making revocation and consistency difficult.

MTC standardizes issuing/updating/revoking a non-transferable credential and verifying only the predicate (not the raw value) via a fixed ZK ABI, so wallets/SDKs/dApps can check the same thing the same way.

Use cases

A. Learning: “Reward NFT for score ≥ 80”

  • A platform issues an examScore metric to the student’s address via MTC (on-chain stores only a commitment; the actual score remains off-chain).
  • The student presents a ZK proof for score ≥ 80; no raw score is disclosed.
  • A rewards contract calls proveMetric(...)true ⇒ mints a “Pass” NFT.
  • If cheating is later found, the issuer revokes the metric; subsequent claims fail automatically.

Benefits: Show only “qualified,” reuse the same predicate across apps, immediate and auditable revocation.

B. Community/Mobility: “VIP event for violations ≤ 2”

  • The operator tracks violationPoints on MTC; policy (LTE) is fixed via CompareMask (GTE/LTE/EQ, inclusive) and may be frozen.
  • Users prove violations ≤ 2 with ZK for entry; violation details remain private.
  • Staff updates/penalizes via updateMetric/slash; rule changes are governed and logged.

Benefits: User privacy; consistent policy and instant revocation; transparent audit trail.

Specification (at a glance)

MTC Core (ERC category)

  • Schema: registerMetric(metricId, role, mask)
  • Write: mint / mintBatch / updateMetric / updateMetricBatch / revokeMetric / slash
  • Read: getMetric(tokenId, metricId), tokenIdOf(address)
  • Non-transferable & one token per subject: MUST
  • CompareMask: GTE=1, LTE=2, EQ=4 (inclusive); after freeze, setCompareMask MUST revert
  • Events: MetricRegistered / MetricUpdated / MetricRevoked / Slash / CompareMaskChanged / MaskFrozenSet
  • ERC-165: MUST implement and expose interfaceId

MTC-ZK (optional)

  • proveMetric(a,b,c,publicSignals) with fixed order [mode, root, nullifier, addr, threshold, leaf]
  • Binding: root == leafFull (current Core anchor), tokenId == tokenIdOf(address(uint160(addr)))
  • Policy: mode must be allowed by Core mask; mask mismatch and mode==0 MUST revert
  • Domain separation (in circuit):
    treeLeaf = Poseidon(leaf, addr, keccak256(abi.encode(chainid(), address(this))))
  • Optional events: VerifierSet, ProofVerified

Why MTC (design benefits)

  • Privacy × Interop: Predicate-only proofs; one stable ABI across wallets/dApps
  • Instant revocation: revokeMetric makes future claims fail by construction
  • Policy consistency: CompareMask (+ freeze) avoids ad-hoc rule changes
  • One per subject, non-transferable: Prevents lending/marketplaces
  • Replay-safe: Bound to Core’s current anchor and a domain-separated leaf

Related ERCs — Differences at a Glance

Aspect MTC (proposed) ERC-725/735 ERC-4973 ERC-5192
Goal / Scope VC-aligned reputation metrics on a non-transferable credential; issue / update / revoke; verify predicates only via ZK 725: identity registry; 735: claims model Account-bound token (ABT) Minimal SBT (lock state only)
Representation Non-transferable credential + metric (value kept as commitment) Identity / claims registry (may contain PII) Non-transferable token Non-transferable token
Transferability MUST NOT transfer; one token per subject (MUST) N/A Non-transferable Non-transferable
On-chain data Commitment + timestamps; PII stays off-chain Claims (content depends on app; may include PII) Token ownership Token ID + lock state
Verification Fixed ZK ABI checks predicate only (e.g., score ≥ threshold) Claim validity / signatures Ownership ≈ eligibility Locked ≈ eligibility
Revocation revokeMetricimmediate invalidation (later proofs fail) Claim revocation (app-specific) Up to issuer (out of scope) Minimal (lock/unlock)
Policy control CompareMask (GTE/LTE/EQ, inclusive) + freeze (no further changes) Out of scope Out of scope Out of scope
ZK Yes (optional): proveMetric(a,b,c,publicSignals) with fixed order Not specified Not specified Not specified
Replay resistance Current Core anchor (root == leafFull) + domain-separated leaf (chainId + contract) Impl-specific Impl-specific Impl-specific
ERC-165 Required (Core & ZK) Impl-specific Yes Yes
Typical use Threshold checks (scores/violations), reward NFTs, gated entry KYC/attribute claims storage & presentation Achievement badges, memberships Minimal “present/issued” signal
Composability Use MTC to decide, then mint 4973/5192 tokens as badges Can back MTC via off-chain VC layer Use MTC as pre-check; mint ABT on success Use MTC as pre-check; minimal SBT for display

TL;DR: MTC standardizes predicate verification (not raw values) with ZK.
725/735 = claim/identity layer; 4973/5192 = token representations. MTC sits upstream as a shared, privacy-preserving decision layer and composes well with them.

Draft EIPs (EIP-1 compliant)

Reference implementation (non-normative)

Contracts: MultiTrustCredential.sol


As this is our initial draft, I’d greatly appreciate broad feedback—on terminology, spec clarity, and interop. Suggestions and alternatives are welcome.

1 Like

We have built a simple test code to experiment with the proposed MultiTrust Credential (MTC) concept.
In this demo, you can record hidden information into MTC (e.g., country of affiliation, exam score) and then generate ZK proofs to show:

  • whether your country is included in a whitelist,
  • whether your score is above a given threshold,
    without revealing the raw values.

More details and usage instructions are in the README:

Feedback on the implementation, spec alignment, and use cases would be highly appreciated.

I’ve summarized the worldview achievable with MTC in a short story format.
I’d love to hear your thoughts.

1 Like

Right now the CompareMask supports EQ / GTE / LTE.
For credit modules, two more conditions might be useful to be added:

  • bounded range (e.g., score ∈ [x,y])
  • delta constraints (e.g., “score hasn’t decreased more than Δ since last epoch”)
1 Like

Thanks a lot for the suggestion. Totally agree these are very relevant for credit modules.

For bounded range (x ≤ score ≤ y), today it can be expressed by combining GTE and LTE checks, but having a first-class “RANGE” style predicate could simplify UX/SDK and on-chain integration. Happy to explore that.

For delta constraints (e.g., “no more than Δ change since last epoch”), I also agree it’s useful, though it likely needs an explicit epoch/freshness model (and possibly referencing a prior epoch state). My initial thought is to keep ERC-8035 minimal and maybe we can discuss RANGE/DELTA as part of the ZK/presentation extension (ERC-8036) or a credit-focused extension.

That makes a lot of sense — I agree with keeping ERC-8035 minimal and pushing higher-order constraints into a presentation or domain-specific extension.

I like the idea of RANGE as a first-class predicate mainly for UX and integration clarity, even if it compiles down to GTE+LTE internally. That feels like a clean win for SDKs and downstream credit modules.

For DELTA, I agree it naturally ties into an explicit epoch/freshness model. Treating it as a presentation-layer concern sounds right and keeps MTC-Core simple and future-proof.

If you’re planning an ERC-8036 or credit-focused extension, I’d be very interested in contributing ideas or concrete interfaces — especially around epoch binding and replay safety.

1 Like

Thanks! Really glad we’re aligned.

I’ll put together a draft update for ERC-8036 to cover RANGE and DELTA, and let’s continue the discussion in the ERC-8036 thread:

I’d love your input on the concrete interfaces there, especially around epoch binding and replay safety.

Here’s another suggestion.

For multi-market credit systems, I’d recommend binding proofs to:

  • contract address

  • chainid

  • anchor root

  • epoch/version

The epoch field prevents long-lived proofs from being reused across credit cycles.

Thanks Alan, really helpful suggestion.

We already bind proofs to chainId + contract address (domain separation) and to the anchor root in the current draft/implementation.

The remaining question is whether we should generalize epoch/version binding across all proofs (baseline included), or keep it mandatory only for certain predicate types (e.g., DELTA) where replay across credit cycles is a bigger concern.

I’ll think through the trade-offs (UX/complexity vs. replay resistance) and update the draft accordingly. If you have a strong opinion on “MUST vs SHOULD” for epoch/version, I’d love to hear it.