System Design Document: ZKP-Powered Portable Credit Union Identity¶
Author: Viswa Kondoju Date: February 22, 2026 Version: 1.1 Status: Implemented — All core features built and tested
Table of Contents¶
- System Overview
- Architecture Overview
- Deep Dive: ZKP Credential Engine
- Deep Dive: Member Identity Layer
- Deep Dive: QR Code Encoding & Transport
- Deep Dive: Lender Verification Portal
- Deep Dive: Credit Union Core Banking Integration
- Deep Dive: Credential Lifecycle & Revocation
- Security Model
- Regulatory Alignment
- Technology Stack Recommendations
- Deployment Architecture
- Risk Register
1. System Overview¶
1.1 Problem Statement¶
Credit union members applying for financial products — auto loans, mortgages, credit cards, personal loans — are forced to repeatedly submit the same personal and financial information to every lender they approach. Each lender independently verifies this information through data aggregators that transmit raw personal data, creating privacy exposure, verification delays, and redundant effort.
No solution exists today that allows a credit union member to carry a verified, privacy-preserving financial identity across lending institutions without exposing raw personal data.
1.2 Solution¶
A platform that enables credit union members to generate zero-knowledge proof credentials from their existing credit union financial data, encode those credentials into time-limited QR codes, and share them with any participating lender. Lenders can cryptographically verify financial claims (income ranges, credit score tiers, membership standing) and auto-fill loan applications — all without ever receiving the member's raw data.
1.3 Core Actors¶
| Actor | Role |
|---|---|
| Member | Credit union member who generates and controls their portable credential |
| Credit Union (Issuer) | Source of truth for member financial data; attests to credential accuracy |
| Lender (Verifier) | External institution that scans QR codes, verifies claims, and auto-fills applications |
| Platform | Orchestration layer that manages credential generation, encoding, verification APIs, and trust registry |
1.4 Key Design Principles¶
- Member-controlled sharing: No data leaves the system without an explicit member action.
- Data minimization: Lenders receive only the boolean truth of a claim (e.g., "income > $50K"), never the underlying value.
- Offline-capable verification: QR-based sharing works in physical environments without real-time connectivity requirements for the member.
- Standards-aligned: Built on W3C Verifiable Credentials and Decentralized Identifiers.
- Regulatory-forward: Designed to align with CFPB Section 1033 open banking mandates.
2. Architecture Overview¶
2.1 High-Level System Architecture¶
┌─────────────────────────────────────────────────────────────────────────┐
│ MEMBER MOBILE APP │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Credential │ │ QR Code │ │ Wallet & │ │
│ │ Request UI │ │ Generator │ │ History UI │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │
│ │ │ │
│ ┌──────┴─────────────────┴──────────────────────────┐ │
│ │ Local Credential Store │ │
│ │ (Encrypted, Device-Bound Keys) │ │
│ └─────────────────────┬───────────────────────────────┘ │
└────────────────────────┼────────────────────────────────────────────────┘
│ HTTPS/mTLS
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ PLATFORM SERVICES │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ ZKP │ │ Credential │ │ Trust │ │
│ │ Credential │ │ Status & │ │ Registry │ │
│ │ Engine │ │ Revocation │ │ Service │ │
│ └──────┬───────┘ └──────────────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────┴───────┐ ┌──────────────┐ ┌──────┴───────┐ │
│ │ DID │ │ API │ │ Lender │ │
│ │ Resolver │ │ Gateway │ │ Onboarding │ │
│ └──────────────┘ └──────┬───────┘ └──────────────┘ │
└────────────────────────────┼────────────────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌─────────────┐ ┌─────────────────┐
│ CREDIT UNION │ │ LENDER │ │ LENDER │
│ CORE BANKING │ │ PORTAL │ │ API CLIENT │
│ (Symitar, DNA, │ │ (Web App) │ │ (Programmatic) │
│ KeyStone) │ │ │ │ │
└──────────────────┘ └─────────────┘ └─────────────────┘
2.2 Data Flow Summary¶
CREDENTIAL GENERATION:
CU Core Banking → Platform (ZKP Engine) → Member App (Credential Store)
CREDENTIAL SHARING:
Member App (QR Generator) → Physical/Digital QR → Lender Scanner
CREDENTIAL VERIFICATION:
Lender Scanner → Platform (Verification API) → Trust Registry + Status List → Result
3. Deep Dive: ZKP Credential Engine¶
3.1 ZKP Scheme Selection¶
The system uses Groth16 zk-SNARKs as the primary proof system. This decision is driven by three constraints: QR code data capacity, verification speed in lending environments, and implementation maturity.
| Property | Groth16 | PLONK | zk-STARK | Bulletproofs |
|---|---|---|---|---|
| Proof Size | 260 bytes | 868 bytes | ~45 KB | 576–674 bytes |
| Verification Time | ~5 ms | ~8 ms | ~50 ms | ~30 ms |
| Trusted Setup | Per-circuit | Universal | None | None |
| QR Feasibility | Excellent | Marginal | Infeasible | Good |
| Post-Quantum | No | No | Yes | No |
| Maturity | Very High | High | Medium | Medium |
Why Groth16: At 260 bytes, a Groth16 proof fits comfortably within a QR code alongside metadata (issuer DID, credential schema, expiration timestamp). The proof + metadata total stays under 600 bytes after Base64 encoding, well within the 2,953-byte maximum of a Version 40 QR code. Verification takes approximately 5 milliseconds — fast enough for a real-time point-of-sale lending experience.
Trusted setup tradeoff: Groth16 requires a per-circuit trusted setup ceremony. For this system, "per-circuit" means one setup per claim type (income range, credit score tier, membership tenure, etc.). With a fixed and slowly-evolving set of claim types, the setup burden is manageable. The ceremony can be conducted as a multi-party computation (MPC) involving multiple credit unions to distribute trust.
Fallback consideration: If trusted setup concerns become a barrier to credit union adoption, Bulletproofs (576–674 bytes, no setup) are the fallback. They still fit within QR constraints, though with slightly slower verification.
3.2 Credential Predicate Design¶
The engine does not prove raw values. It proves predicates — boolean statements about a member's financial standing. This is the core privacy mechanism.
| Predicate Category | Example Predicates | ZKP Circuit |
|---|---|---|
| Income | annual_income > $50,000 |
Range proof against CU-attested income |
annual_income ∈ [$75K, $100K] |
Range membership proof | |
| Credit Score | credit_tier ∈ {Excellent, Good} |
Set membership proof |
credit_score >= 720 |
Threshold comparison proof | |
| Account Standing | membership_tenure > 24 months |
Duration comparison proof |
account_status = Active |
Equality proof | |
| Debt-to-Income | DTI_ratio < 0.43 |
Ratio computation + comparison |
| Account Balance | avg_balance_6mo > $10,000 |
Aggregation + comparison proof |
3.3 Circuit Architecture¶
Each predicate type maps to a ZKP arithmetic circuit. The circuit takes private inputs (the member's actual financial data) and public inputs (the claim threshold and the credit union's attestation), and outputs a proof that the claim is true.
CIRCUIT: IncomeRangeProof
Private Inputs (witness — known only to member/CU):
- actual_income: uint64
- cu_attestation_signature: bytes
Public Inputs (known to verifier):
- threshold: uint64 (e.g., 50000)
- comparison_operator: enum (GT, GTE, LT, LTE, EQ, RANGE)
- cu_public_key: bytes
- credential_expiry: timestamp
- member_did: bytes
Constraints:
1. Verify cu_attestation_signature over actual_income using cu_public_key
2. Assert actual_income >= threshold (for GTE operator)
3. Assert credential_expiry > current_time
Output:
- Groth16 proof (260 bytes)
- Public input hash
3.4 Proof Generation Flow¶
Step 1: Member requests credential via mobile app
↓
Step 2: Platform authenticates member (biometric + device key)
↓
Step 3: Platform fetches current financial data from CU core banking API
↓
Step 4: CU signs the data attestation (proving the data is real and current)
↓
Step 5: ZKP Engine selects appropriate circuit for requested predicate
↓
Step 6: Engine generates Groth16 proof with:
- Private witness: actual financial values + CU signature
- Public inputs: threshold, CU public key, expiry, member DID
↓
Step 7: Engine packages proof into W3C Verifiable Credential format
↓
Step 8: Credential returned to member's app and stored locally
3.5 Proof Generation Performance¶
Groth16 proof generation is computationally intensive and runs server-side on the platform. Expected benchmarks for the circuit types in this system:
| Circuit | Constraints | Generation Time | Proof Size |
|---|---|---|---|
| Income threshold | ~10K | ~1.5 seconds | 260 bytes |
| Credit score tier | ~8K | ~1.2 seconds | 260 bytes |
| Account tenure | ~5K | ~0.8 seconds | 260 bytes |
| DTI ratio | ~15K | ~2.0 seconds | 260 bytes |
| Composite (3 claims) | ~30K | ~3.5 seconds | 260 bytes per claim |
Generation runs on the platform's infrastructure, not the member's device. The member receives the completed proof within a few seconds of request.
4. Deep Dive: Member Identity Layer¶
4.1 Decentralized Identifier (DID) Architecture¶
Each member receives a Decentralized Identifier (DID) that serves as their portable cryptographic identity across the system. The DID is not tied to any single credit union — it belongs to the member.
DID Method Selection: did:key
The current implementation uses did:key as the DID method. This approach requires no external infrastructure — DIDs are self-resolving from the Ed25519 public key, making them ideal for MVP and offline-capable scenarios.
| DID Method | Infrastructure Required | Resolution | Suitability |
|---|---|---|---|
did:key |
None | Self-resolving | Current implementation — zero infrastructure, Ed25519 native |
did:web |
HTTPS endpoint | DNS + HTTPS | Future option — leverages existing web infrastructure, supports key rotation |
did:ion |
Bitcoin blockchain | Sidetree protocol | Overkill for initial deployment |
did:indy |
Hyperledger ledger | Indy network | Good fit if CUs already use Hyperledger |
Example Member DID (current implementation):
Implementation note: The MVP uses
did:keywith Ed25519 multicodec encoding. Migration todid:webis planned for production to support key rotation and institutional DID documents.
DID Document (resolved from https://identity.cuportable.com/members/m-7f3a9b2c/did.json):
{
"@context": ["https://www.w3.org/ns/did/v1"],
"id": "did:web:identity.cuportable.com:members:m-7f3a9b2c",
"verificationMethod": [{
"id": "#key-1",
"type": "JsonWebKey2020",
"controller": "did:web:identity.cuportable.com:members:m-7f3a9b2c",
"publicKeyJwk": { "kty": "EC", "crv": "BLS12-381-G2", ... }
}],
"authentication": ["#key-1"],
"assertionMethod": ["#key-1"]
}
4.2 Key Management¶
Member keys are generated and stored on the member's device using hardware-backed secure enclaves (iOS Secure Enclave, Android StrongBox).
KEY HIERARCHY:
Device Root Key (hardware-backed, never exportable)
└── Member DID Key Pair (BLS12-381)
├── Used for: credential binding, presentation signing
└── Rotation: annual, or on device change
Backup & Recovery:
- Encrypted key backup to member's credit union account
- Recovery via CU identity verification + new device enrollment
- Social recovery option: 2-of-3 trusted contacts (future)
4.3 Member Registration Flow¶
Step 1: Member downloads app and selects their credit union
↓
Step 2: Member authenticates with their CU online banking credentials
(OAuth2 flow — platform never sees the password)
↓
Step 3: CU confirms member identity and returns member profile reference
↓
Step 4: App generates DID key pair in device secure enclave
↓
Step 5: Platform creates DID document and publishes to DID registry
↓
Step 6: CU issues a "membership attestation" VC bound to the member's DID
↓
Step 7: Member is now enrolled and can request financial credentials
5. Deep Dive: QR Code Encoding & Transport¶
5.1 QR Code Data Budget¶
A QR code's data capacity is the binding constraint on the credential transport format. The system targets QR Version 25–27 for optimal balance between data capacity and scanability.
| QR Version | Modules | Byte Capacity (Level M) | Scanability |
|---|---|---|---|
| 25 | 117×117 | 1,853 bytes | Good at arm's length |
| 27 | 125×125 | 2,107 bytes | Good at arm's length |
| 33 | 149×149 | 2,701 bytes | Requires closer scan |
| 40 | 177×177 | 2,953 bytes | Difficult to scan quickly |
Error correction level M (15% recovery) is the target — it provides reasonable damage tolerance for printed or screen-displayed QR codes without sacrificing too much capacity.
5.2 Credential Payload Structure¶
The QR code encodes a Verifiable Presentation (VP) containing one or more Verifiable Credentials:
{
"header": {
"version": 1,
"encoding": "base64url",
"compression": "none",
"created": 1740268800,
"expires": 1740355200
},
"presentation": {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiablePresentation"],
"holder": "did:web:identity.cuportable.com:members:m-7f3a9b2c",
"verifiableCredential": [{
"issuer": "did:web:cu.firstfederal.org",
"issuanceDate": "2026-02-22T00:00:00Z",
"expirationDate": "2026-02-23T00:00:00Z",
"credentialSubject": {
"id": "did:web:identity.cuportable.com:members:m-7f3a9b2c",
"claim": "annual_income_gte",
"threshold": 75000,
"currency": "USD"
},
"credentialStatus": {
"type": "BitstringStatusListEntry",
"statusListIndex": "4521",
"statusListCredential": "https://status.cuportable.com/lists/firstfederal/1"
},
"proof": {
"type": "Groth16Proof2026",
"proofValue": "<base64url-encoded 260-byte Groth16 proof>",
"verificationMethod": "did:web:cu.firstfederal.org#zkp-key-1"
}
}]
},
"presentationProof": "<base64url-encoded signature by holder DID>"
}
5.3 Size Budget Breakdown¶
| Component | Raw Size | Base64url Size |
|---|---|---|
| Groth16 proof | 260 bytes | 347 bytes |
| Header + metadata | ~120 bytes | ~160 bytes |
| Credential subject (1 claim) | ~150 bytes | ~200 bytes |
| Issuer DID + status | ~180 bytes | ~240 bytes |
| Holder DID + presentation proof | ~200 bytes | ~267 bytes |
| Total (1 claim) | ~910 bytes | ~1,214 bytes |
| Total (3 claims) | ~1,500 bytes | ~2,000 bytes |
A single-claim credential fits in QR Version 22 (Level M). A three-claim composite credential fits in QR Version 27 (Level M). Both are comfortably scannable.
5.4 QR Lifecycle¶
GENERATION (member app):
1. Member selects which credentials to share
2. App packages selected VCs into a Verifiable Presentation
3. Member signs the presentation with their DID key
4. App generates time-limited QR code (default: 24 hours)
5. QR displayed on screen or saved for printing
SHARING:
- In-person: Lender scans QR from member's phone screen
- Digital: Member shares QR image via secure channel
- Embedded: QR payload transmitted as deep link or API payload
EXPIRATION:
- QR encodes an expiration timestamp
- After expiration, verification always fails
- Member generates a new QR for each sharing event (recommended)
6. Deep Dive: Lender Verification Portal¶
6.1 Verification Flow¶
When a lender scans a member's QR code, the verification process executes the following steps:
Step 1: DECODE
- Parse QR payload
- Validate header (version, encoding, expiration)
- Extract Verifiable Presentation
Step 2: FRESHNESS CHECK
- Verify credential has not expired (expirationDate > now)
- Verify QR code has not expired (header.expires > now)
- Reject if either timestamp has passed
Step 3: ISSUER AUTHENTICATION
- Resolve issuer DID (e.g., did:web:cu.firstfederal.org)
- Fetch issuer's DID Document
- Verify issuer is in the Trust Registry (see 6.2)
- Extract issuer's ZKP verification key
Step 4: ZKP PROOF VERIFICATION
- Load the Groth16 verification key for the claim's circuit type
- Extract public inputs (threshold, issuer key, expiry, member DID)
- Run Groth16 verification algorithm
- Result: VALID or INVALID (deterministic, no judgment)
Step 5: REVOCATION CHECK
- Fetch the Bitstring Status List from the credential's statusListCredential URL
- Check the bit at statusListIndex
- If bit = 0: credential is active
- If bit = 1: credential has been revoked
Step 6: HOLDER BINDING
- Verify the presentation proof (holder's DID signature)
- Confirms the person presenting the credential controls the DID
Step 7: RESULT
- Return structured verification result to lender system
- If all checks pass: release verified claim values for auto-fill
6.2 Trust Registry¶
The Trust Registry is the mechanism by which lenders know that an issuer (credit union) is legitimate. Without it, anyone could generate a fake credential from a fabricated "credit union."
TRUST REGISTRY ARCHITECTURE:
┌──────────────────────────────────────┐
│ TRUST REGISTRY │
│ │
│ Maintained by: Platform operator │
│ Updated: As CUs onboard/offboard │
│ │
│ ┌────────────────────────────────┐ │
│ │ Registered Issuers: │ │
│ │ │ │
│ │ did:web:cu.firstfederal.org │ │
│ │ → Name: First Federal CU │ │
│ │ → NCUA Charter: #12345 │ │
│ │ → ZKP Verify Keys: [...] │ │
│ │ → Status: Active │ │
│ │ │ │
│ │ did:web:cu.navyfcu.org │ │
│ │ → Name: Navy Federal CU │ │
│ │ → NCUA Charter: #67890 │ │
│ │ → ZKP Verify Keys: [...] │ │
│ │ → Status: Active │ │
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘
The registry maps each issuer DID to a verified credit union identity (NCUA charter number) and their current ZKP verification keys. Lenders query this registry during Step 3 of verification.
6.3 Lender Integration Modes¶
| Mode | Integration Effort | Use Case |
|---|---|---|
| Web Portal | None — lender uses hosted portal | Small lenders, occasional use |
| JavaScript SDK | Low — embed widget in existing LOS | Mid-size lenders with web-based loan origination |
| REST API | Medium — integrate into backend systems | Large lenders with custom workflows |
| Webhook | Medium — receive verification events | Lenders wanting async/batch processing |
6.4 Auto-Fill Mapping¶
After successful verification, the lender's loan origination system receives a structured result that maps directly to application fields:
{
"verificationId": "vrf-8a3b2c1d",
"status": "VERIFIED",
"timestamp": "2026-02-22T14:30:00Z",
"holder": "did:web:identity.cuportable.com:members:m-7f3a9b2c",
"issuer": {
"did": "did:web:cu.firstfederal.org",
"name": "First Federal Credit Union",
"ncuaCharter": "12345"
},
"verifiedClaims": [
{
"claimType": "annual_income_gte",
"threshold": 75000,
"currency": "USD",
"result": true,
"autoFillMapping": {
"field": "applicant.annual_income_range",
"value": "$75,000+"
}
},
{
"claimType": "credit_score_tier",
"tier": "Excellent",
"result": true,
"autoFillMapping": {
"field": "applicant.credit_tier",
"value": "Excellent (750+)"
}
}
],
"credentialExpiry": "2026-02-23T00:00:00Z"
}
7. Deep Dive: Credit Union Core Banking Integration¶
7.1 Core Banking Landscape¶
The system must integrate with the major core banking platforms used by U.S. credit unions:
| Platform | Vendor | Market Share | API Type | Integration Path |
|---|---|---|---|---|
| Symitar | Jack Henry | ~15.7% (699 CUs) | SOAP (SymXchange) | Vendor Integration Program (VIP) |
| DNA | Fiserv | ~25.9% (1,155 CUs) | REST / Proprietary | Fiserv Developer Portal |
| KeyStone | Corelation | ~2.9% (145 CUs) | REST (KeyBridge) | Corelation Certification |
| Spectrum | Jack Henry | ~12.0% (535 CUs) | SOAP | Jack Henry VIP |
7.2 Data Elements Required¶
The ZKP Credential Engine needs access to specific member data elements from the core banking system to generate proofs. Critically, the platform processes these values only ephemerally — they are used as private inputs to the ZKP circuit and are never stored by the platform.
| Data Element | Source Field (Symitar) | Used For |
|---|---|---|
| Annual Income | Member record / payroll data | Income range predicates |
| Credit Score | Credit report pull / cached score | Credit tier predicates |
| Account Open Date | Account creation date | Membership tenure predicates |
| Account Status | Share/Loan status codes | Account standing predicates |
| Monthly Debt Payments | Loan payment summaries | DTI ratio predicates |
| Average Balance (6mo) | Transaction history aggregation | Balance threshold predicates |
7.3 Integration Architecture¶
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ CREDIT UNION │ │ PLATFORM │ │ MEMBER APP │
│ CORE BANKING │ │ ADAPTER LAYER │ │ │
│ │ │ │ │ │
│ ┌────────────┐ │ SOAP/ │ ┌────────────┐ │ HTTPS │ │
│ │ Symitar │──┼──REST──▶│ │ Symitar │ │◀───────▶│ Credential │
│ │ SymXchange│ │ │ │ Adapter │ │ │ Request UI │
│ └────────────┘ │ │ └─────┬──────┘ │ │ │
│ │ │ │ │ │ │
│ ┌────────────┐ │ REST │ ┌─────┴──────┐ │ │ │
│ │ KeyStone │──┼────────▶│ │ Normalized │ │ │ │
│ │ KeyBridge │ │ │ │ Data Model │──┼────────▶│ ZKP Engine │
│ └────────────┘ │ │ └─────┬──────┘ │ │ │
│ │ │ │ │ │ │
│ ┌────────────┐ │ REST/ │ ┌─────┴──────┐ │ │ │
│ │ DNA │──┼──Prop──▶│ │ DNA │ │ │ │
│ │ (Fiserv) │ │ │ │ Adapter │ │ │ │
│ └────────────┘ │ │ └────────────┘ │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
The Adapter Layer normalizes data from different core banking APIs into a unified data model that the ZKP Engine consumes. Each adapter handles the protocol differences (SOAP vs REST), authentication mechanisms, and data field mappings specific to that core platform.
7.4 Data Flow Security¶
Member financial data is handled with strict ephemerality:
- Fetch: Adapter retrieves member data from core banking API (encrypted in transit via mTLS).
- Normalize: Data mapped to internal schema in memory.
- Attest: Credit union's HSM-hosted key signs the data attestation.
- Prove: ZKP Engine uses the data as private witness input to the circuit.
- Purge: Raw financial data is wiped from platform memory immediately after proof generation. The data is never written to disk, logged, or cached.
The platform can verifiably demonstrate this property through architecture audits and can adopt trusted execution environments (Intel SGX, AWS Nitro Enclaves) to provide hardware-enforced guarantees.
8. Deep Dive: Credential Lifecycle & Revocation¶
8.1 Credential States¶
┌──────────┐ generate ┌──────────┐ expire ┌──────────┐
│ │ ──────────────▶ │ │ ─────────────▶ │ │
│ NONE │ │ ACTIVE │ │ EXPIRED │
│ │ │ │ │ │
└──────────┘ └────┬─────┘ └──────────┘
│
│ revoke
▼
┌──────────┐
│ │
│ REVOKED │
│ │
└──────────┘
8.2 Revocation Mechanism: W3C Bitstring Status List¶
The system uses Bitstring Status List v1.0 (W3C standard) for credential revocation.
How it works:
Each credit union (issuer) maintains a bitstring — a long binary array where each bit position corresponds to one issued credential. Bit value 0 means the credential is active; bit value 1 means it has been revoked.
BITSTRING STATUS LIST:
Credit Union: First Federal (did:web:cu.firstfederal.org)
Status List ID: https://status.cuportable.com/lists/firstfederal/1
Bit positions:
[0] = 0 (active) ← Credential for Member A (income claim)
[1] = 0 (active) ← Credential for Member B (credit tier claim)
[2] = 1 (REVOKED) ← Credential for Member C (account closed)
[3] = 0 (active) ← Credential for Member D (income claim)
...
[131071] = 0 ← Minimum list size: 131,072 bits (16 KB)
Privacy property: The minimum list size of 131,072 entries ensures that a verifier checking one credential cannot determine how many total credentials the issuer has issued or which specific member is at a given index — the member is hidden within a large anonymity set.
8.3 Revocation Triggers¶
| Trigger | Source | Action |
|---|---|---|
| Account closure | CU core banking webhook | Revoke all active credentials for member |
| Fraud flag | CU fraud system | Immediate revocation of all credentials |
| Data staleness | Platform TTL policy | Credential expires (not revoked, just expired) |
| Member request | Member app | Member can self-revoke any credential |
| Material change | CU-defined rules | Revoke if income drops below attested threshold |
8.4 Revocation Propagation¶
REVOCATION FLOW:
Step 1: CU detects revocation event (e.g., account closure)
↓
Step 2: CU sends revocation request to Platform via API
↓
Step 3: Platform updates the bitstring at the credential's index
↓
Step 4: Updated status list published to HTTPS endpoint
↓
Step 5: Next lender verification fetches fresh status list
↓
Step 6: Lender sees bit = 1 → verification fails
Propagation time: < 60 seconds (near real-time)
Caching policy: Lenders cache status lists for max 5 minutes
9. Security Model¶
9.1 Threat Model¶
| Threat | Attack Vector | Mitigation |
|---|---|---|
| Credential forgery | Attacker generates fake proof | Groth16 soundness — computationally infeasible without valid witness |
| Replay attack | Attacker reuses a captured QR code | Time-limited expiration + holder binding (DID signature) |
| Issuer impersonation | Attacker fakes a credit union | Trust Registry verification; only registered CU DIDs are accepted |
| Data exfiltration from platform | Attacker compromises platform servers | Ephemeral data handling; raw data never stored. TEE enforcement optional. |
| Collusion (lender + platform) | Lender and platform conspire to extract raw data | ZKP mathematical guarantee: proof reveals nothing beyond the boolean claim |
| Quantum computing | Future quantum computers break elliptic curve cryptography | Groth16 is not post-quantum. Migration path: transition to zk-STARKs when QR capacity or alternative transport allows |
| Revocation delay | Revoked credential used before status list update | 5-minute max cache TTL. High-value transactions require real-time status check. |
| Device compromise | Attacker gains access to member's phone | Device-bound keys in secure enclave; biometric authentication for credential access |
9.2 Cryptographic Assumptions¶
The system's security relies on the following assumptions:
- Groth16 soundness: Based on the Knowledge of Exponent assumption in bilinear groups (BN254 or BLS12-381 curves).
- BLS12-381 discrete log hardness: The curve used for DID keys and BBS+ signatures.
- SHA-256 collision resistance: Used in credential hashing and status list integrity.
- TLS 1.3 channel security: All platform-to-CU and platform-to-lender communications.
9.3 Compliance & Audit¶
- SOC 2 Type II: Platform infrastructure will target SOC 2 certification.
- PCI DSS: Not required — platform never handles payment card data.
- GLBA Safeguards Rule: Applicable as a service provider to financial institutions.
- NCUA Vendor Due Diligence: CUs will require standard vendor risk assessment; system design supports this.
10. Regulatory Alignment¶
10.1 CFPB Section 1033 (Open Banking)¶
Section 1033 of the Dodd-Frank Act mandates that financial institutions make consumer data available to consumers and their authorized third parties. The rule was finalized in October 2024, with implementation dates staggered by institution size:
- Largest institutions ($850M+ assets): April 1, 2026
- Mid-tier: April 1, 2028
- Smaller institutions: April 1, 2030
As of 2025, the CFPB has reopened rulemaking for reconsideration of certain provisions (definition of "representative," fee assessment, data security requirements). The rule's core mandate — consumer-controlled data portability — remains intact.
Alignment: This system is a privacy-enhanced implementation of Section 1033's vision. Rather than sharing raw financial data (the Plaid/Finicity model), it shares cryptographically verified claims. This satisfies the consumer's right to port their financial standing while exceeding the rule's data security expectations.
10.2 ECOA / Reg B (Fair Lending)¶
The Equal Credit Opportunity Act prohibits discrimination in lending. ZKP credentials naturally support fair lending by design — a lender verifying "income > $75K" never sees the applicant's name, age, race, or any other protected characteristic unless the applicant explicitly includes it.
10.3 FCRA (Fair Credit Reporting)¶
The system must be carefully positioned relative to the Fair Credit Reporting Act. If the platform is deemed a "consumer reporting agency," it would face significant compliance obligations. The mitigation: the platform does not collect, store, or report consumer credit information. It generates mathematical proofs from data held by the credit union (the regulated entity) and passes only the proof to the lender. The credit union remains the data custodian.
10.4 State Privacy Laws¶
Emerging state privacy laws (California CCPA/CPRA, Virginia VCDPA, Colorado CPA) generally strengthen the case for this system. ZKP-based verification is inherently aligned with data minimization principles required by these laws.
11. Technology Stack Recommendations¶
11.1 ZKP Layer¶
| Component | Technology | Rationale |
|---|---|---|
| Circuit language | Circom 2.0 | Mature, large community, Groth16-native |
| Proving system | snarkjs / rapidsnark | JavaScript + native Rust prover for performance |
| Curve | BN254 or BLS12-381 | Well-supported in snarkjs; BLS12-381 for BBS+ compatibility |
| Trusted setup | snarkjs MPC | Multi-party ceremony tooling built-in |
11.2 Identity Layer¶
| Component | Technology | Rationale |
|---|---|---|
| DID resolution | Universal Resolver (DIF) | Standard resolution for multiple DID methods |
| VC format | W3C VC Data Model 2.0 | Industry standard |
| Selective disclosure | BBS+ (Data Integrity BBS Cryptosuites) | W3C standardization path, unlinkable proofs |
| Key storage (mobile) | iOS Secure Enclave / Android StrongBox | Hardware-backed key protection |
11.3 Platform Services¶
| Component | Technology | Rationale |
|---|---|---|
| API Gateway | Kong or AWS API Gateway | Rate limiting, auth, lender key management |
| Backend services | Rust (proving engine) + Node.js (API/orchestration) | Rust for performance-critical ZKP generation; Node.js for rapid API development |
| Database | PostgreSQL (metadata) + Redis (session/cache) | Relational for audit trails; Redis for status list caching |
| Message queue | Apache Kafka | Async credential generation, revocation event propagation |
| Secrets management | HashiCorp Vault or AWS KMS | HSM-backed key storage for CU attestation keys |
11.4 Mobile App¶
| Component | Technology | Rationale |
|---|---|---|
| Framework | React Native or Flutter | Cross-platform; single codebase for iOS + Android |
| QR generation | react-native-qrcode-svg or equivalent |
Client-side QR rendering |
| Local storage | Encrypted SQLite (SQLCipher) | Credential storage on device |
| Biometric auth | Native biometric APIs | Gate access to credential operations |
12. Deployment Architecture¶
12.1 Cloud Infrastructure¶
┌─────────────────────────────────────────────────────────────────┐
│ AWS / GCP / Azure │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ API │ │ ZKP │ │ Status │ │
│ │ Gateway │ │ Proving │ │ List │ │
│ │ (Kong) │ │ Workers │ │ CDN │ │
│ │ │ │ (Rust) │ │ (CloudFront)│ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────┴─────────────────┴──────────────────┴──────┐ │
│ │ Kubernetes Cluster │ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ Credential │ │ DID │ │ Trust │ │ │
│ │ │ Service │ │ Resolver │ │ Registry │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ CU Adapter│ │ Kafka │ │ Postgres │ │ │
│ │ │ Services │ │ Cluster │ │ + Redis │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ HSM / KMS │ │
│ │ (CU attestation keys, │ │
│ │ platform signing keys) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
12.2 Scaling Considerations¶
| Component | Scaling Strategy | Target |
|---|---|---|
| ZKP Proving Workers | Horizontal auto-scaling (CPU-bound) | < 5 sec proof generation at P99 |
| API Gateway | Load-balanced replicas | 10,000 verifications/minute |
| Status List CDN | Edge-cached, 5-min TTL | Global < 50ms latency |
| CU Adapters | Per-CU dedicated worker pools | Isolation between CU integrations |
13. Risk Register¶
| # | Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| 1 | Credit unions slow to adopt due to integration complexity | High | High | Start with CUs already on modern platforms (KeyStone). Offer managed adapter deployment. |
| 2 | Lenders don't trust ZKP verification as equivalent to raw data | Medium | High | Pilot with progressive lenders. Publish audit results. Seek regulatory endorsement. |
| 3 | Patent rejection on obviousness grounds | Medium | Medium | File provisional early. Collect pilot data showing unexpected technical results. |
| 4 | Groth16 trusted setup ceremony deters CU participation | Low | Medium | Conduct multi-party ceremony with 5+ CUs. Publish ceremony artifacts. Offer Bulletproofs fallback. |
| 5 | Quantum computing threatens Groth16 security within 10 years | Low | High | Monitor NIST post-quantum standards. Design migration path to zk-STARKs or lattice-based proofs. |
| 6 | Section 1033 rulemaking changes reduce regulatory tailwind | Medium | Low | System value proposition stands independent of regulatory mandate. Privacy is a feature, not just compliance. |
| 7 | Competitor (Plaid, Mastercard) builds similar system | Medium | High | Network effects from CU partnerships. Patent protection. First-mover advantage in credit union vertical. |
| 8 | Member adoption friction (app download, enrollment) | Medium | Medium | Integrate with existing CU mobile banking apps via SDK. Reduce enrollment to 2 taps. |
14. Implementation Status¶
14.1 What's Been Built (v1.1 — February 2026)¶
The following features from this design document have been fully implemented and tested:
| Component | Design Section | Status | Notes |
|---|---|---|---|
| Groth16 ZK-SNARKs | 3.1 | Implemented | 5 Circom circuits (membership, employment, income, credit, account age) |
| W3C Verifiable Credentials | 3.4 | Implemented | Real Ed25519 issuer signatures, JSON-LD format |
| DID:key Identity | 4.1 | Implemented | Ed25519 multicodec, self-resolving DIDs |
| Holder Binding | 6.1 Step 6 | Implemented | Member signs QR presentation with private key |
| 7-Step Verification | 6.1 | Implemented | All 7 steps: decode, freshness, issuer auth, ZKP, revocation, binding, result |
| Trust Registry | 6.2 | Implemented | Public API, NCUA charter verification, issuer DID lookup |
| Auto-Fill Mapping | 6.4 | Implemented | Maps verified claims to loan application fields |
| Bitstring Status List | 8.2 | Implemented | W3C standard, 16KB (131,072 slots), instant revocation |
| Credential Lifecycle | 8.1 | Implemented | Three-state: ACTIVE / EXPIRED / REVOKED |
| Lender API Key Auth | 6.3 | Implemented | JWT Bearer + X-API-Key header (REST API mode) |
| AES-256-GCM Encryption | 7.4 | Implemented | PII + private keys encrypted at rest |
| Poseidon Hash | 3.3 | Implemented | Identity commitments via circomlibjs |
14.2 Current Technology Stack (Implemented)¶
| Component | Design Recommendation (11.x) | Actual Implementation |
|---|---|---|
| Circuit language | Circom 2.0 | Circom 2 |
| Proving system | snarkjs / rapidsnark | snarkjs (Node.js subprocess) |
| Curve | BN254 or BLS12-381 | BN128 (alt_bn128) |
| DID method | did:web | did:key (Ed25519) — did:web planned for production |
| VC format | W3C VC Data Model 2.0 | W3C VC with Ed25519 proof + BitstringStatusListEntry |
| Backend | Rust + Node.js | Python FastAPI + Node.js (snarkjs subprocess) |
| Database | PostgreSQL + Redis | PostgreSQL 16 + Redis 7 (Docker Compose) |
| Frontend | React Native / Flutter | Next.js 15 (TypeScript, Tailwind CSS) |
| Key storage | iOS Secure Enclave / Android StrongBox | AES-256-GCM encrypted in database (web MVP) |
14.3 Not Yet Implemented (Future Work)¶
| Component | Design Section | Priority | Notes |
|---|---|---|---|
| did:web migration | 4.1 | High | Needed for key rotation and institutional DIDs |
| Mobile app (React Native) | 11.4 | High | Current web app to be wrapped or rebuilt native |
| Core banking adapters (Symitar, DNA, KeyStone) | 7.3 | High | Currently using direct database seeding |
| DTI ratio circuit | 3.2 | Medium | Requires additional circuit design |
| Account balance circuit | 3.2 | Medium | Requires transaction aggregation |
| BBS+ selective disclosure | 11.2 | Low | Future enhancement for unlinkable proofs |
| Kubernetes deployment | 12.1 | Medium | Currently Docker Compose for dev |
| Hardware-backed key storage | 4.2 | Medium | Planned for mobile app release |
| Multi-party trusted setup ceremony | 3.1 | Medium | Currently single-party dev setup |
| Webhook integration for lenders | 6.3 | Low | REST API and web portal available |
14.4 Database Schema (Implemented)¶
Seven tables in PostgreSQL:
- members — UUID, DID, encrypted PII, Ed25519 keys, encrypted private key, identity commitment
- credentials — Member FK, claim type, W3C VC JSON, Groth16 proof, credential_status (ACTIVE/EXPIRED/REVOKED), status_list_index
- qr_tokens — Member FK, cryptographic token, credential IDs, single_use, TTL, holder_signature, binding_payload
- lenders — UUID, name, email, password hash, api_key
- verification_logs — Lender FK, member DID, claims_verified, all_verified, issuer_info JSONB, failed_step
- trust_registry — DID, name, NCUA charter, public key hex, status
- status_lists — Issuer DID, list_index, 16KB bitstring, next_available_index, version
This document is a living technical specification. It should be updated as architectural decisions are finalized, pilot data is collected, and the regulatory landscape evolves.