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 andmode==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 | revokeMetric ⇒ immediate 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)
- Core: eip-mtc-core.md
- MTC-ZK: eip-mtc-zk.md
I haven’t made the PR yet. I’d like to proceed based on everyone’s feedback.
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.