Skip to Content
ReferenceServicesCredential Issuance

Credential Issuance — Service Reference

Module: backends/credential-issuance · REST: 8081 · gRPC: 50051

Architecture

What it does

  • Single credential issuance — real Ed25519 signing: via Issuer Registry’s SignPayload RPC for CUSTODIAL issuers, or local verification of a caller-supplied BYOK signature against the issuer’s registered public key. Not a KMS placeholder.
  • Credential retrieval by internal UUID or public shareable_id (oneof lookup) — backs authenticated lookups and the public verification-link flow.
  • Credential claiming — links a placeholder-holder-DID credential to a real wallet account once the holder authenticates. Idempotent: re-claiming by the rightful owner returns ALREADY_CLAIMED, not an error.
  • Revocation with issuer-ownership verification — checks the caller’s issuer UUID (or resolved DID) against credential.issuer_id; supports a reason plus an LLM-assisted revocation_category classification.
  • 8 read-path RPCs backing the issuer portal, holder wallet, and network dashboards — so Core Engine reads wallet.credentials exclusively through gRPC instead of a direct JDBC read.

There is no batch RPC. POST /api/v1/credentials/batch exists only as a Core Engine REST endpoint that loops over this service’s single IssueCredential RPC once per record.

RPC surface (credential.proto)

service CredentialIssuanceService { rpc IssueCredential(IssueCredentialRequest) returns (IssueCredentialResponse); rpc RevokeCredential(RevokeCredentialRequest) returns (RevokeCredentialResponse); rpc GetCredential(GetCredentialRequest) returns (GetCredentialResponse); rpc ClaimCredential(ClaimCredentialRequest) returns (ClaimCredentialResponse); // Read-path — issuer portal / holder wallet / network dashboards 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); }

IssueCredentialRequest also carries optional BYOK fields (signature, key_id, credential_id, issuance_timestamp) and soft FKs (learner_id, catalog_entry_version_id, outcome_id). GetCredentialRequest is a oneof lookup (credential_id or shareable_id).

Error codes

RPCCodes
issueCredentialINVALID_ARGUMENT, ALREADY_EXISTS (duplicate BYOK credential_id), INTERNAL, or whatever status Issuer Registry’s SignPayload failed with (preserved, not masked)
revokeCredentialINVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED, INTERNAL
getCredentialINVALID_ARGUMENT, NOT_FOUND, INTERNAL
claimCredentialCLAIMED, ALREADY_CLAIMED, NOT_FOUND, DID_MISMATCH (statuses, not gRPC codes)

Server configuration

grpc: server: port: 50051 enable-keep-alive: true keep-alive-time: 30s permit-keep-alive-without-calls: true max-inbound-message-size: 10485760

HTTP/2 via gRPC netty-shaded, zero-copy for binary data, connection multiplexing, backpressure handling, graceful shutdown (10s timeout). GrpcLogInterceptor logs method name, latency, and response status/errors via SLF4J.

Test coverage

Unit tests cover 5 of the 12 RPCs — the 4 core issuance/lifecycle methods (minus claimCredential) plus revoke/get:

testIssueCredentialSuccess testIssueCredentialMissingIssuerDid testGetCredentialSuccess testGetCredentialNotFound testRevokeCredentialSuccess

claimCredential and all 8 read-path RPCs are untested.

Performance — targets, not measurements

No load-test harness exists for this service. Figures below are original-authoring estimates:

OperationLatencyNotes
Single issuance45–100 msDB + JSON construction + signing round-trip
Credential retrieval10–20 msDatabase lookup
Credential revocation30–50 msDatabase update

Security

  • Real Ed25519 signing — CUSTODIAL via Issuer Registry’s SignPayload; BYOK verified locally against the issuer’s registered public key.
  • Revocation checks that the calling issuer actually owns the credential being revoked.
  • No authentication/authorization at this service’s own gRPC layer — relies on network isolation; Core Engine/API Gateway handle caller auth upstream. mTLS between services is the recommended (and, per Internal gRPC mTLS, actually implemented) control.

Manual testing

grpcurl -plaintext \ -d '{"issuer_did":"did:key:issuer123","holder_did":"did:key:holder456","credential_type":"SecondaryEducationCredential"}' \ localhost:50051 \ com.attestpro.credential.CredentialIssuanceService/IssueCredential

What’s not implemented

  • Batch issuance as a gRPC RPC (see above — it’s Core Engine’s REST loop instead).
  • Issuer authorization validation via JWT claims at this service’s own gRPC layer.
  • gRPC reflection, request-validation interceptor, circuit breaker for DB failures, custom error details, gRPC compression, distributed tracing, load-test harness.
  • Swapping Issuer Registry’s application-layer AES-256-GCM custodial key encryption for a real HSM/KMS (see CLAUDE.md §Security & Key Management) — signing itself is already real.

See Integrate a Credential Issuance Client for the Core Engine-side integration walkthrough.