|
| 1 | +# ADR-0014: W3C Verifiable Credential Support in Circuit Lib |
| 2 | + |
| 3 | +## Status |
| 4 | +Proposed |
| 5 | + |
| 6 | +## Date |
| 7 | +2026-04-02 |
| 8 | + |
| 9 | +## Context |
| 10 | + |
| 11 | +The identity-kyc demo (`zeroj-usecases/identity-kyc`) demonstrates privacy-preserving KYC on Cardano — users prove eligibility (age, country) without revealing personal data. However, it uses **Poseidon-signed credentials** (a shared-secret scheme), which has two limitations: |
| 12 | + |
| 13 | +1. **No standard interoperability** — Poseidon-signed credentials are ZeroJ-specific. They can't be issued or verified by W3C VC tooling, DID wallets, or Atala PRISM. |
| 14 | +2. **Weak security model** — the issuer and holder share the same secret (`credentialSecret`). The issuer can impersonate the holder, and the secret must be transmitted securely. |
| 15 | + |
| 16 | +To support W3C Verifiable Credentials and DID-based identity, the ZK circuit library needs three new capabilities that enable **asymmetric credential signatures** verifiable inside a ZK proof. |
| 17 | + |
| 18 | +### Current state of zeroj-circuit-lib |
| 19 | + |
| 20 | +| Primitive | Status | Notes | |
| 21 | +|-----------|--------|-------| |
| 22 | +| Poseidon hash | Available | Used for credential hashing today | |
| 23 | +| Merkle proof verification | Available | Used for country whitelist | |
| 24 | +| Range comparisons | Available | Used for age >= minAge | |
| 25 | +| **BabyJubJub curve** | **Removed** | Was in circuit-lib, removed during security audit (missing subgroup checks) | |
| 26 | +| **EdDSA signature verification** | **Removed** | Depended on BabyJubJub, removed with it (signature malleability issues) | |
| 27 | +| **BBS+ signature verification** | **Not implemented** | Requires pairing math inside circuit | |
| 28 | + |
| 29 | +The BabyJubJub and EdDSA implementations were removed after a security audit identified: |
| 30 | +- BabyJubJub: missing cofactor-clearing / subgroup checks — points from the larger curve group could bypass verification |
| 31 | +- EdDSA: signature malleability — multiple valid signatures for the same message (S + L is also valid where L is the subgroup order) |
| 32 | +- Both: zero test coverage for edge cases |
| 33 | + |
| 34 | +### Why these matter for Cardano |
| 35 | + |
| 36 | +Cardano's identity ecosystem is moving toward DID and W3C VCs: |
| 37 | +- **Atala PRISM** uses W3C Verifiable Credentials with DID:prism identifiers |
| 38 | +- **CIP-0030 / CIP-0045** wallet standards support credential presentation |
| 39 | +- **EU eIDAS 2.0** mandates interoperable digital identity — W3C VC is the baseline format |
| 40 | + |
| 41 | +Without in-circuit signature verification, ZeroJ can only prove statements about credentials signed with symmetric (Poseidon) schemes, limiting adoption to single-issuer, closed systems. |
| 42 | + |
| 43 | +## Decision |
| 44 | + |
| 45 | +### Add three capabilities to zeroj-circuit-lib, in priority order: |
| 46 | + |
| 47 | +### 1. BabyJubJub curve (re-add with hardening) |
| 48 | + |
| 49 | +Re-implement the BabyJubJub twisted Edwards curve with proper security: |
| 50 | + |
| 51 | +```java |
| 52 | +public class SignalBabyJubJub { |
| 53 | + // Point addition on BabyJubJub (twisted Edwards: ax^2 + y^2 = 1 + dx^2y^2) |
| 54 | + // a = 168700, d = 168696 (matching circomlib) |
| 55 | + static Signal[] add(SignalBuilder c, Signal[] p1, Signal[] p2); |
| 56 | + |
| 57 | + // Scalar multiplication via double-and-add (constant-time ladder) |
| 58 | + static Signal[] scalarMul(SignalBuilder c, Signal scalar, Signal[] basePoint, int nBits); |
| 59 | + |
| 60 | + // Subgroup check: verify point is in the prime-order subgroup |
| 61 | + // Multiplies by cofactor (8) and checks result is not identity |
| 62 | + static void assertInSubgroup(SignalBuilder c, Signal[] point); |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +**Security fixes:** |
| 67 | +- Cofactor clearing: `assertInSubgroup()` ensures points are in the prime-order subgroup (order `l`), not the larger curve group (order `8l`) |
| 68 | +- All public inputs that are curve points must pass subgroup check before use in arithmetic |
| 69 | +- Test vectors from circomlib/iden3 for edge cases (identity, generator, cofactor multiples) |
| 70 | + |
| 71 | +**Constraints:** ~500 per scalar multiplication (256 doublings + ~128 additions for 256-bit scalar) |
| 72 | + |
| 73 | +### 2. EdDSA signature verification in-circuit |
| 74 | + |
| 75 | +Verify EdDSA (EdDSA-BabyJubJub, compatible with circomlib/iden3) signatures inside a ZK proof: |
| 76 | + |
| 77 | +```java |
| 78 | +public class SignalEdDSA { |
| 79 | + /** |
| 80 | + * Verify an EdDSA signature (R, S) on message M under public key A. |
| 81 | + * |
| 82 | + * Checks: [S]B == R + [H(R, A, M)]A |
| 83 | + * where B is the generator and H is Poseidon (matching circomlib). |
| 84 | + */ |
| 85 | + static Signal verify(SignalBuilder c, |
| 86 | + Signal[] pubKeyA, // [Ax, Ay] |
| 87 | + Signal[] sigR, // [Rx, Ry] |
| 88 | + Signal sigS, // scalar S |
| 89 | + Signal message); // message hash |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +**Security fixes:** |
| 94 | +- Strict S range check: `S < l` (subgroup order) — prevents signature malleability where `S + l` is also valid |
| 95 | +- R point subgroup check via `assertInSubgroup()` |
| 96 | +- Public key subgroup check via `assertInSubgroup()` |
| 97 | +- Use Poseidon for H(R, A, M) — matching circomlib's `EdDSAPoseidonVerifier` |
| 98 | + |
| 99 | +**Constraints:** ~3,000 (2 scalar multiplications + 1 point addition + Poseidon hash + range check) |
| 100 | + |
| 101 | +### 3. BBS+ selective disclosure (future) |
| 102 | + |
| 103 | +BBS+ signatures allow a holder to selectively disclose individual claims from a credential without revealing others. This is the gold standard for W3C VC privacy. |
| 104 | + |
| 105 | +```java |
| 106 | +public class SignalBBS { |
| 107 | + /** |
| 108 | + * Verify a BBS+ signature on a set of messages, with selective disclosure. |
| 109 | + * |
| 110 | + * Only disclosed messages are public inputs. Hidden messages are private. |
| 111 | + * The verifier learns: "a trusted issuer signed these claims, and the |
| 112 | + * disclosed claims have these values" — without learning the hidden claims. |
| 113 | + */ |
| 114 | + static Signal verify(SignalBuilder c, |
| 115 | + Signal[] disclosedMessages, // public |
| 116 | + Signal[] hiddenMessages, // private |
| 117 | + Signal[] signature, // private |
| 118 | + Signal[] issuerPublicKey); // public |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +**Complexity:** Requires BLS12-381 pairing operations inside the circuit (~10,000+ constraints). This is the most complex primitive and should be implemented after EdDSA is stable. |
| 123 | + |
| 124 | +**Note:** BBS+ is still evolving as a standard (IETF draft). Implementation should track the latest spec. |
| 125 | + |
| 126 | +## Consequences |
| 127 | + |
| 128 | +### Positive |
| 129 | + |
| 130 | +1. **W3C VC compatibility** — credentials issued by any W3C VC compliant issuer (Atala PRISM, Spruce, etc.) can be verified inside ZeroJ ZK proofs |
| 131 | +2. **Multi-issuer ecosystems** — asymmetric signatures mean the issuer can't impersonate the holder |
| 132 | +3. **Atala PRISM integration** — enables ZK proofs over PRISM-issued credentials on Cardano |
| 133 | +4. **EU eIDAS readiness** — Cardano DApps can accept European Digital Identity Wallet credentials |
| 134 | +5. **Selective disclosure (BBS+)** — holders reveal only the claims needed, not the entire credential |
| 135 | +6. **Ecosystem alignment** — matches what other ZK projects (circomlib, Polygon ID, Iden3) provide |
| 136 | + |
| 137 | +### Negative |
| 138 | + |
| 139 | +1. **Circuit size increase** — EdDSA verification adds ~3,000 constraints vs ~660 for Poseidon-signed, increasing proof generation time by ~3x |
| 140 | +2. **BabyJubJub is not Cardano-native** — Cardano's Plutus V3 has BLS12-381 builtins, not BabyJubJub. EdDSA signature verification happens inside the ZK circuit (off-chain), not via on-chain builtins |
| 141 | +3. **Implementation complexity** — elliptic curve arithmetic in R1CS is error-prone; incomplete addition formulas, special-case handling for identity/doubling |
| 142 | +4. **BBS+ is speculative** — the IETF standard is not finalized; implementation may need to change |
| 143 | + |
| 144 | +## Risks |
| 145 | + |
| 146 | +| Risk | Severity | Mitigation | |
| 147 | +|------|----------|------------| |
| 148 | +| BabyJubJub subgroup check bypass | **Critical** | Mandatory cofactor clearing; test with all 8 coset representatives; differential testing against circomlib | |
| 149 | +| EdDSA signature malleability | **Critical** | Strict S < l range check (bit decomposition); test with S, S+l, S+2l; compare against circomlib EdDSAPoseidonVerifier | |
| 150 | +| Incomplete addition formula edge cases | High | Use complete twisted Edwards addition formula (no exceptions for doubling/identity); exhaustive test with identity, generator, -generator, cofactor points | |
| 151 | +| BBS+ spec changes | Medium | Implement behind feature flag; track IETF draft; plan for breaking changes | |
| 152 | +| Performance regression for simple use cases | Low | Poseidon-signed credentials remain available as a lightweight option; circuit choice is per-application | |
| 153 | + |
| 154 | +## Implementation Order |
| 155 | + |
| 156 | +1. **Phase 1: BabyJubJub** — re-add with subgroup checks, 100% test coverage, differential testing against circomlib |
| 157 | +2. **Phase 2: EdDSA** — signature verification circuit, malleability fix, test against circomlib EdDSAPoseidonVerifier |
| 158 | +3. **Phase 3: Identity KYC demo upgrade** — switch from Poseidon-signed to EdDSA-signed credentials |
| 159 | +4. **Phase 4: BBS+** — selective disclosure (when IETF spec stabilizes) |
| 160 | + |
| 161 | +## References |
| 162 | + |
| 163 | +- [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) |
| 164 | +- [W3C Decentralized Identifiers (DIDs)](https://www.w3.org/TR/did-core/) |
| 165 | +- [BabyJubJub — iden3 specification](https://iden3-docs.readthedocs.io/en/latest/iden3_repos/research/publications/zkproof-standards-workshop-2/baby-jubjub/baby-jubjub.html) |
| 166 | +- [circomlib EdDSAPoseidonVerifier](https://github.com/iden3/circomlib/blob/master/circuits/eddsa.circom) |
| 167 | +- [BBS+ Signatures — IETF Draft](https://www.ietf.org/archive/id/draft-irtf-cfrg-bbs-signatures-07.html) |
| 168 | +- [Atala PRISM — Cardano Identity](https://atalaprism.io/) |
| 169 | +- [EU eIDAS 2.0 — European Digital Identity](https://digital-strategy.ec.europa.eu/en/policies/eidas-regulation) |
| 170 | +- ZeroJ security audit findings (BabyJubJub/EdDSA removal rationale) |
| 171 | +- `zeroj-usecases/identity-kyc` — current Poseidon-signed credential demo |
| 172 | +- `docs/usecases/identity-and-credentials.md` — full design doc with 3 credential approaches |
0 commit comments