This page provides component-level implementation details for ZeroVerify, including database schemas, API specifications, and algorithm logic for the core cryptographic operations.
The credential metadata table tracks issued credentials and their revocation status. Each record uniquely identifies a credential with its expiration and revocation index.
| Attribute | Type | Role | Description |
|---|---|---|---|
| subject | String | Partition key | Pseudonymous user identifier (HMAC of issuer_id || sub_id) |
| credential_id | String (UUID) | Sort key | Unique credential identifier |
| issue_date | String (ISO 8601) | Attribute | Timestamp when credential was issued |
| expiry_date | String (ISO 8601) | Attribute | Timestamp when credential expires |
| revocation_index | Number | Attribute | Bit position in W3C Status List bitstring |
| status | String | Attribute | ACTIVE or REVOKED |
The credential itself is a W3C Verifiable Credential containing user attributes, signed with BBS+ signature. Stored locally in user's browser IndexedDB.
{
"student_id": "G89u28394",
"email": "anton@oakland.edu",
"first_name": "Anton",
"last_name": "Sakhanovych",
"birthdate": "2000-05-15",
"custom_claims": {
"student_status": "enrolled",
"enrollment_date": "2021-09-01",
"university": "Oakland University",
"major": "Computer Science"
}
}The proof structure generated by the client and sent to the verifier for validation.
{
"proof_type": "age_over_21",
"proof": "base64_zkSNARK_proof_bytes",
"public_inputs": {
"challenge": "n-0S6_WzA2Mj",
"credential_status_index": 94567
}
}POST /api/credentials/issue
Issues a BBS+ signed credential after OAuth authentication. Returns signed credential to client for local storage.
{
"authorization_code": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"redirect_uri": "https://app.zeroverify.com/callback"
}{
"credential": {
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiableCredential", "StudentCredential"],
"issuer": "did:web:api.zeroverify.com",
"issuanceDate": "2025-02-10T20:41:27Z",
"expirationDate": "2026-02-10T20:41:27Z",
"credentialSubject": {
"id": "did:key:z6MkF5rGMoatr...",
"student_status": "enrolled",
"university": "Oakland University",
"enrollment_date": "2021-09-01"
},
"proof": {
"type": "BbsBlsSignature2020",
"created": "2025-02-10T20:41:27Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:web:api.zeroverify.com#key-1",
"proofValue": "base64_bbs_signature_bytes..."
}
},
"credential_id": "a3f8b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"revocation_index": 94567
}400 Bad Request - Invalid authorization code
{
"error": "invalid_request",
"error_description": "Authorization code is invalid or expired"
}401 Unauthorized - OAuth verification failed
{
"error": "unauthorized",
"error_description": "OAuth token verification failed"
}409 Conflict - Active credential already exists
{
"error": "duplicate_credential",
"error_description": "An active credential already exists for this user"
}500 Internal Server Error - Credential signing failed
{
"error": "internal_error",
"error_description": "Failed to generate credential signature"
}POST /api/credentials/revoke
Revokes a credential by updating its status in DynamoDB and flipping the corresponding bit in the W3C Status List bitstring.
{
"credential_id": "a3f8b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"proof_of_ownership": "base64_zk_proof_of_credential_ownership"
}{
"status": "revoked",
"credential_id": "a3f8b2c1-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"revoked_at": "2025-03-15T14:22:00Z"
}404 Not Found - Credential does not exist
{
"error": "not_found",
"error_description": "Credential ID not found"
}403 Forbidden - Proof of ownership invalid
{
"error": "forbidden",
"error_description": "Invalid proof of credential ownership"
}1. User initiates issuance from React web app
2. App redirects to Keycloak
3. Keycloak federates with institution's IdP via SAML/OAuth
4. User authenticates with institution
5. Keycloak returns authorization code to React app
6. React app sends code to Issuance Lambda via API Gateway
Lambda execution:
7. Exchange authorization code with Keycloak for OIDC claims
8. Extract verified attributes from claims (student_status, email, etc.)
9. Compute pseudonymous subject ID: HMAC(issuer_id || sub_id)
10. Query DynamoDB for existing active credential
IF active credential exists:
RETURN 409 Conflict
11. Generate new credential_id (UUID)
12. Assign next available revocation_index
13. Create W3C Verifiable Credential structure
14. Load BBS+ private key from AWS Secrets Manager
15. Sign credential with BBS+ signature
16. Write credential metadata to DynamoDB:
- subject: pseudonymous_id
- credential_id: new_uuid
- status: ACTIVE
- issue_date, expiry_date, revocation_index
17. Return signed credential to React app
18. React app stores credential in browser IndexedDB
19. Credential never transmitted to any server again1. Verifier generates verification URL:
https://verify.zeroverify.com/?proof_type=student_status
&callback=https://merchant.com/verify
&nonce=random_challenge_string
2. User opens URL in browser
3. React app parses query parameters
4. Load credential from IndexedDB
5. Check credential validity:
- Expiration date > current time
- Status = ACTIVE (check local cache or fetch bitstring)
6. Display consent screen to user:
- Verifier: merchant.com
- Proof type: "Student Status"
- What will be disclosed: "Valid/Invalid (no personal data)"
7. IF user denies:
RETURN consent_denied to callback
8. IF user approves:
Client-side proof generation:
9. Load zk-SNARK circuit for proof_type from S3
10. Prepare circuit inputs:
- Private inputs: credential attributes (student_status, birthdate, etc.)
- Public inputs: challenge nonce, revocation_index
11. Execute Groth16 prover (compiled to WebAssembly):
- Circuit proves: "credential.student_status == 'enrolled'"
- Without revealing actual student_status value
12. Generate zk-SNARK proof (2-5 seconds)
13. Construct proof object:
{
proof_type: "student_status",
proof: base64_proof_bytes,
public_inputs: { challenge: nonce, credential_status_index: 94567 }
}
14. POST proof directly to verifier's callback endpoint
15. Display result to user: "Verification sent"Verifier receives proof at their callback endpoint:
1. Extract proof components:
- proof_type
- proof (zkSNARK proof bytes)
- public_inputs (challenge, credential_status_index)
2. Validation checks:
a) Challenge nonce verification:
IF public_inputs.challenge != expected_nonce:
RETURN invalid (replay attack or wrong session)
b) Challenge freshness:
IF challenge_timestamp > 5 minutes ago:
RETURN invalid (expired challenge)
c) Challenge single-use:
IF challenge already used:
RETURN invalid (replay attack)
ELSE:
Mark challenge as used
d) Cryptographic proof verification:
- Load verification key for proof_type from ZeroVerify S3
- Run Groth16 verifier:
verify(proof, public_inputs, verification_key)
IF verification fails:
RETURN invalid (tampered or incorrect proof)
e) Revocation check:
- Download W3C Status List bitstring from ZeroVerify S3
- Extract bit at position: public_inputs.credential_status_index
IF bit == 1:
RETURN invalid (credential revoked)
3. All checks passed:
RETURN valid
4. Verifier makes business decision:
- Grant student discount
- Allow access
- Log verification event (no PII, only: timestamp, proof_type, result)React App ↔ Keycloak ↔ Institution IdP
React App → API Gateway → Issuance Lambda → DynamoDB
Issuance Lambda ← AWS Secrets Manager (BBS+ key)
React App ← Signed Credential → IndexedDB (local storage)
Verifier → User (verification URL)
React App ← IndexedDB (load credential)
React App ← S3 (load circuit & verification key)
React App → Verifier Callback (POST proof)
Verifier ← S3 (verification key & bitstring)
Verifier → Business Logic (grant/deny access)
User → API Gateway → SQS Queue
Revocation Lambda ← SQS (batch processing)
Revocation Lambda ↔ DynamoDB (update status)
Revocation Lambda ↔ S3 (update bitstring)