Data Models & Cryptography
W3C Verifiable Credential format
All credentials conform to the W3C VC Data Model 1.1:
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://attestpro.global/context/v1"
],
"type": ["VerifiableCredential", "SecondaryEducationCredential"],
"id": "urn:uuid:12345678-1234-1234-1234-123456789012",
"issuer": {
"id": "did:key:z6MkhaXgBZDvotDkL5257faWxcqyqQ4yoni1nUbt5d12345",
"name": "National Board of Education"
},
"issuanceDate": "2026-03-15T00:00:00Z",
"credentialSubject": {
"id": "did:key:z6MkpqXgBZDvotDkL5257faWxcqyqQ4yoni1nUbt5d67890",
"name": "Jane Doe",
"marks": { "english": 95, "mathematics": 92 }
},
"proof": {
"type": "DataIntegrityProof",
"cryptosuite": "eddsa-jcs-2022",
"verificationMethod": "did:key:z6MkhaXgBZDvotDkL5257faWxcqyqQ4yoni1nUbt5d12345#z6MkhaXgBZDvotDkL5257faWxcqyqQ4yoni1nUbt5d12345",
"proofValue": "3MCNuFxcqyqQ4yoni1nUbt5d12345..."
}
}PostgreSQL schema (orientation only)
See Database Schema Ownership for the authoritative, table-by-table reference. Quick schema-to-service map:
| Schema | Owning service |
|---|---|
authority | Issuer Registry |
wallet | Credential Issuance |
core | Core Engine (largest — 50+ tables spanning auth sessions, claim-integrity, custodian intake/compliance/workforce/search, audit logs, share links) |
catalog | Program Catalog |
learner | Learner Records |
custodian | Custodian Registry |
console | Admin Console |
Two core tables worth calling out directly:
CREATE TABLE core.share_links (
share_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
credential_id UUID NOT NULL, -- soft FK -> wallet.credentials.credential_id
holder_did VARCHAR(512) NOT NULL,
share_token VARCHAR(64) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ,
access_limit INTEGER,
access_count INTEGER NOT NULL DEFAULT 0,
active BOOLEAN NOT NULL DEFAULT true,
revoked_at TIMESTAMPTZ
);
CREATE TABLE core.holder_requests (
request_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issuer_id UUID NOT NULL, -- soft FK -> authority.issuers.issuer_id
issuer_name VARCHAR(255),
credential_id UUID NOT NULL, -- soft FK -> wallet.credentials.credential_id
holder_did VARCHAR(512),
holder_name VARCHAR(255) NOT NULL,
holder_email VARCHAR(255) NOT NULL,
credential_type VARCHAR(100),
request_type VARCHAR(50) NOT NULL, -- REISSUE | REVOKE | UPDATE | DOWNLOAD
reason TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING | APPROVED | REJECTED
response TEXT,
submitted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
resolved_at TIMESTAMPTZ,
resolved_by VARCHAR(255)
);Holder-visible access history has no standalone access_logs table — GET /api/v1/holder/access-logs is served from core.audit_logs, the general audit trail.
Key gRPC contracts
backends/shared/src/main/proto/ holds 8 proto files. Full per-service RPC lists live in
API & gRPC Reference; the two most commonly referenced:
CredentialIssuanceService (credential.proto, port 50051):
service CredentialIssuanceService {
rpc IssueCredential(IssueCredentialRequest) returns (IssueCredentialResponse);
rpc RevokeCredential(RevokeCredentialRequest) returns (RevokeCredentialResponse);
rpc GetCredential(GetCredentialRequest) returns (GetCredentialResponse);
rpc ClaimCredential(ClaimCredentialRequest) returns (ClaimCredentialResponse);
rpc ListCredentialsByIssuer(ListCredentialsByIssuerRequest) returns (ListCredentialsResponse);
rpc GetCredentialForIssuer(GetCredentialForIssuerRequest) returns (CredentialRecord);
rpc GetIssuerCredentialStats(GetIssuerCredentialStatsRequest) returns (IssuerCredentialStatsResponse);
rpc GetCredentialForHolder(GetCredentialForHolderRequest) returns (CredentialRecord);
rpc SearchCredentialsByHolder(SearchCredentialsByHolderRequest) returns (ListCredentialsResponse);
rpc GetHolderCredentialStats(GetHolderCredentialStatsRequest) returns (HolderCredentialStatsResponse);
rpc ListNewlyExpiredCredentials(ListNewlyExpiredCredentialsRequest) returns (ListCredentialsResponse);
rpc GetNetworkCredentialStats(GetNetworkCredentialStatsRequest) returns (NetworkCredentialStatsResponse);
rpc ListCredentialsByCustodian(ListCredentialsByCustodianRequest) returns (ListCredentialsResponse);
rpc GetCredentialForCustodian(GetCredentialForCustodianRequest) returns (CredentialRecord);
rpc GetCustodianCredentialStats(GetCustodianCredentialStatsRequest) returns (CustodianCredentialStatsResponse);
rpc ListCustodianCredentialsExpiringBetween(ListCustodianCredentialsExpiringBetweenRequest) returns (ListCredentialsResponse);
rpc LinkSupersededCredential(LinkSupersededCredentialRequest) returns (LinkSupersededCredentialResponse);
}There is no BatchIssue RPC — batch issuance is a REST-layer concern (POST /api/v1/credentials/batch), not a streaming gRPC method.
VerificationEngineService (verification.proto, port 50052):
service VerificationEngineService {
rpc VerifyCredential(VerifyCredentialRequest) returns (VerifyCredentialResponse);
rpc ValidateSignature(ValidateSignatureRequest) returns (ValidateSignatureResponse);
rpc CheckCredentialStatus(CheckStatusRequest) returns (CheckStatusResponse);
rpc VerifyRawSignature(VerifyRawSignatureRequest) returns (VerifyRawSignatureResponse);
}registry.proto (IssuerRegistryService, 50053) additionally carries RegisterIssuer,
GetIssuer, ListIssuers, GetDIDDocument, RotateKey, UpdateIssuerStatus, SignPayload, API
key CRUD, and federated did:web trust-registry RPCs. catalog.proto (CatalogRegistryService,
50054) is the largest contract in the codebase — jurisdictions, frameworks, catalog
entries/versions, code sets, regulatory schemes, scheme registrations, proposals, skills, and
semantic search/embeddings.
Cryptography
Ed25519 signatures (RFC 8037)
public boolean verifySignature(byte[] publicKey, byte[] message, byte[] signature) {
Ed25519PublicKeyParameters pubKey = new Ed25519PublicKeyParameters(publicKey, 0);
Ed25519Signer signer = new Ed25519Signer();
signer.init(false, pubKey);
signer.update(message, 0, message.length);
return signer.verifySignature(signature);
}Key management — current state, not just target design
| Path | Custody |
|---|---|
| BYOK issuer keys | Never touch Attest ID — true today, no caveat |
| Custodial issuer keys | AES-256-GCM-encrypted at the application layer (SigningKeyEncryptionService, authority.signing_keys) — a documented pilot-only stopgap, not AWS KMS/CloudHSM |
| Custodian Registry keys | Same shared encryption service, same caveat (custodian.signing_keys) |
See CLAUDE.md’s “Security & Key Management” section for the real current state and the migration bar: a real HSM/KMS is required before onboarding any external issuer or custodian whose key custody isn’t otherwise trusted to this app layer.
Performance — targets, not measurements
No load-test harness or benchmark results exist in this codebase. The numbers below are
targets carried over from early planning, not measured figures — load testing is an explicit
launch-blocking item in CLAUDE.md’s Roadmap Context. There is also no batch/streaming issuance
RPC, so “Issue Batch” below refers to the REST /api/v1/credentials/batch path issuing records
sequentially, not a streamed RPC.
| Operation | Latency (p99) | Notes |
|---|---|---|
| Verify link | < 80 ms | Includes DID resolution & access logging |
| Issue batch | 150–200/sec | Sequential REST calls, not a streamed RPC |
| Cache retrieval | < 5 ms | Redis-backed |