Introduction
Verifiable Credentials (VCs) are tamper-evident credentials that can be cryptographically verified. They follow W3C standards and enable trustable digital credentials for identity, education, healthcare, and more. This guide covers VC implementation in Java using JSON-LD, JWT, and Linked Data Proofs.
Core VC Data Model
Verifiable Credential and Presentation
package com.vc.core;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.net.URI;
import java.time.Instant;
import java.util.*;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VerifiableCredential {
private final List<URI> context;
private final URI id;
private final List<String> type;
private final URI issuer;
private final Instant issuanceDate;
private final Instant expirationDate;
private final Map<String, Object> credentialSubject;
private final JsonNode credentialSchema;
private final JsonNode evidence;
private final JsonNode proof;
// Required contexts for VC
private static final String VC_CONTEXT_V1 = "https://www.w3.org/2018/credentials/v1";
private static final String VC_CONTEXT_V2 = "https://www.w3.org/ns/credentials/v2";
public VerifiableCredential(Builder builder) {
this.context = List.of(URI.create(VC_CONTEXT_V1));
this.id = builder.id;
this.type = List.of("VerifiableCredential", builder.type);
this.issuer = builder.issuer;
this.issuanceDate = builder.issuanceDate;
this.expirationDate = builder.expirationDate;
this.credentialSubject = builder.credentialSubject;
this.credentialSchema = builder.credentialSchema;
this.evidence = builder.evidence;
this.proof = builder.proof;
}
public static class Builder {
private URI id;
private String type;
private URI issuer;
private Instant issuanceDate;
private Instant expirationDate;
private Map<String, Object> credentialSubject = new HashMap<>();
private JsonNode credentialSchema;
private JsonNode evidence;
private JsonNode proof;
public Builder id(URI id) { this.id = id; return this; }
public Builder type(String type) { this.type = type; return this; }
public Builder issuer(URI issuer) { this.issuer = issuer; return this; }
public Builder issuanceDate(Instant issuanceDate) { this.issuanceDate = issuanceDate; return this; }
public Builder expirationDate(Instant expirationDate) { this.expirationDate = expirationDate; return this; }
public Builder credentialSubject(Map<String, Object> credentialSubject) {
this.credentialSubject = credentialSubject; return this;
}
public Builder credentialSchema(JsonNode credentialSchema) { this.credentialSchema = credentialSchema; return this; }
public Builder evidence(JsonNode evidence) { this.evidence = evidence; return this; }
public Builder proof(JsonNode proof) { this.proof = proof; return this; }
public Builder addCredentialSubject(String key, Object value) {
this.credentialSubject.put(key, value);
return this;
}
public VerifiableCredential build() {
if (issuer == null) throw new IllegalStateException("Issuer is required");
if (issuanceDate == null) throw new IllegalStateException("Issuance date is required");
if (credentialSubject.isEmpty()) throw new IllegalStateException("Credential subject is required");
return new VerifiableCredential(this);
}
}
// Getters
public List<URI> getContext() { return context; }
public URI getId() { return id; }
public List<String> getType() { return type; }
public URI getIssuer() { return issuer; }
public Instant getIssuanceDate() { return issuanceDate; }
public Instant getExpirationDate() { return expirationDate; }
public Map<String, Object> getCredentialSubject() { return credentialSubject; }
public JsonNode getCredentialSchema() { return credentialSchema; }
public JsonNode getEvidence() { return evidence; }
public JsonNode getProof() { return proof; }
public boolean isExpired() {
return expirationDate != null && Instant.now().isAfter(expirationDate);
}
public ObjectNode toJson() {
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
// Add @context
root.putArray("@context").add(VC_CONTEXT_V1);
// Add id if present
if (id != null) root.put("id", id.toString());
// Add type
root.putArray("type").add("VerifiableCredential").add(type.get(1));
// Add issuer
root.put("issuer", issuer.toString());
// Add dates
root.put("issuanceDate", issuanceDate.toString());
if (expirationDate != null) root.put("expirationDate", expirationDate.toString());
// Add credential subject
ObjectNode subjectNode = mapper.valueToTree(credentialSubject);
root.set("credentialSubject", subjectNode);
// Add proof if present
if (proof != null) root.set("proof", proof);
return root;
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VerifiablePresentation {
private final List<URI> context;
private final URI id;
private final List<String> type;
private final URI holder;
private final List<VerifiableCredential> verifiableCredential;
private final JsonNode proof;
public VerifiablePresentation(Builder builder) {
this.context = List.of(URI.create("https://www.w3.org/2018/credentials/v1"));
this.id = builder.id;
this.type = List.of("VerifiablePresentation");
this.holder = builder.holder;
this.verifiableCredential = List.copyOf(builder.verifiableCredential);
this.proof = builder.proof;
}
public static class Builder {
private URI id;
private URI holder;
private List<VerifiableCredential> verifiableCredential = new ArrayList<>();
private JsonNode proof;
public Builder id(URI id) { this.id = id; return this; }
public Builder holder(URI holder) { this.holder = holder; return this; }
public Builder verifiableCredential(List<VerifiableCredential> vc) {
this.verifiableCredential = vc; return this;
}
public Builder proof(JsonNode proof) { this.proof = proof; return this; }
public Builder addVerifiableCredential(VerifiableCredential vc) {
this.verifiableCredential.add(vc);
return this;
}
public VerifiablePresentation build() {
if (holder == null) throw new IllegalStateException("Holder is required");
if (verifiableCredential.isEmpty()) throw new IllegalStateException("At least one VC is required");
return new VerifiablePresentation(this);
}
}
// Getters
public List<URI> getContext() { return context; }
public URI getId() { return id; }
public List<String> getType() { return type; }
public URI getHolder() { return holder; }
public List<VerifiableCredential> getVerifiableCredential() { return verifiableCredential; }
public JsonNode getProof() { return proof; }
}
Cryptographic Proofs and Signatures
Linked Data Proof Implementation
package com.vc.proof;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.net.URI;
import java.security.*;
import java.time.Instant;
import java.util.*;
public class LinkedDataProof {
private static final String SECURITY_CONTEXT_V2 = "https://w3id.org/security/suites/ed25519-2020/v1";
public static class Proof {
private final String type;
private final Instant created;
private final String verificationMethod;
private final String proofPurpose;
private final String proofValue;
private final String challenge;
private final String domain;
public Proof(Builder builder) {
this.type = builder.type;
this.created = builder.created;
this.verificationMethod = builder.verificationMethod;
this.proofPurpose = builder.proofPurpose;
this.proofValue = builder.proofValue;
this.challenge = builder.challenge;
this.domain = builder.domain;
}
public static class Builder {
private String type;
private Instant created;
private String verificationMethod;
private String proofPurpose;
private String proofValue;
private String challenge;
private String domain;
public Builder type(String type) { this.type = type; return this; }
public Builder created(Instant created) { this.created = created; return this; }
public Builder verificationMethod(String vm) { this.verificationMethod = vm; return this; }
public Builder proofPurpose(String purpose) { this.proofPurpose = purpose; return this; }
public Builder proofValue(String value) { this.proofValue = value; return this; }
public Builder challenge(String challenge) { this.challenge = challenge; return this; }
public Builder domain(String domain) { this.domain = domain; return this; }
public Proof build() {
if (type == null) throw new IllegalStateException("Proof type is required");
if (created == null) throw new IllegalStateException("Creation date is required");
if (verificationMethod == null) throw new IllegalStateException("Verification method is required");
if (proofPurpose == null) throw new IllegalStateException("Proof purpose is required");
if (proofValue == null) throw new IllegalStateException("Proof value is required");
return new Proof(this);
}
}
public ObjectNode toJson() {
ObjectMapper mapper = new ObjectMapper();
ObjectNode proofNode = mapper.createObjectNode();
proofNode.put("type", type);
proofNode.put("created", created.toString());
proofNode.put("verificationMethod", verificationMethod);
proofNode.put("proofPurpose", proofPurpose);
proofNode.put("proofValue", proofValue);
if (challenge != null) proofNode.put("challenge", challenge);
if (domain != null) proofNode.put("domain", domain);
return proofNode;
}
}
}
public class Ed25519Signature2020 {
private final KeyPair keyPair;
private final String verificationMethod;
public Ed25519Signature2020(KeyPair keyPair, String verificationMethod) {
this.keyPair = keyPair;
this.verificationMethod = verificationMethod;
}
public LinkedDataProof.Proof signCredential(JsonNode credential, String proofPurpose) {
try {
// Create proof options
Instant created = Instant.now();
// Create signing input (canonicalized credential + proof options without proofValue)
byte[] signingInput = createSigningInput(credential, created, proofPurpose);
// Sign the input
Signature signature = Signature.getInstance("Ed25519");
signature.initSign(keyPair.getPrivate());
signature.update(signingInput);
byte[] signatureBytes = signature.sign();
// Encode signature as base64
String proofValue = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes);
return new LinkedDataProof.Proof.Builder()
.type("Ed25519Signature2020")
.created(created)
.verificationMethod(verificationMethod)
.proofPurpose(proofPurpose)
.proofValue(proofValue)
.build();
} catch (Exception e) {
throw new RuntimeException("Failed to sign credential", e);
}
}
public boolean verifyCredential(JsonNode signedCredential) {
try {
// Extract proof from credential
JsonNode proofNode = signedCredential.get("proof");
if (proofNode == null) return false;
String proofValue = proofNode.get("proofValue").asText();
Instant created = Instant.parse(proofNode.get("created").asText());
String proofPurpose = proofNode.get("proofPurpose").asText();
// Remove proof for verification
ObjectNode credentialWithoutProof = signedCredential.deepCopy();
credentialWithoutProof.remove("proof");
// Create signing input
byte[] signingInput = createSigningInput(credentialWithoutProof, created, proofPurpose);
// Decode signature
byte[] signatureBytes = Base64.getUrlDecoder().decode(proofValue);
// Verify signature
Signature signature = Signature.getInstance("Ed25519");
signature.initVerify(keyPair.getPublic());
signature.update(signingInput);
return signature.verify(signatureBytes);
} catch (Exception e) {
throw new RuntimeException("Failed to verify credential", e);
}
}
private byte[] createSigningInput(JsonNode credential, Instant created, String proofPurpose) {
// Implementation of JSON-LD canonicalization
// This is a simplified version - in production, use proper JSON-LD canonicalization
try {
ObjectMapper mapper = new ObjectMapper();
ObjectNode document = mapper.createObjectNode();
// Add credential data
document.setAll((ObjectNode) credential);
// Add proof options (without proofValue)
ObjectNode proofOptions = mapper.createObjectNode();
proofOptions.put("type", "Ed25519Signature2020");
proofOptions.put("created", created.toString());
proofOptions.put("verificationMethod", verificationMethod);
proofOptions.put("proofPurpose", proofPurpose);
document.set("proof", proofOptions);
// Canonicalize (URDNA2015 algorithm would be used here)
return mapper.writeValueAsBytes(document);
} catch (Exception e) {
throw new RuntimeException("Failed to create signing input", e);
}
}
}
JWT-based Verifiable Credentials
JWT VC Implementation
package com.vc.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.*;
public class JwtVerifiableCredential {
private final ObjectMapper mapper = new ObjectMapper();
public String createJwtVc(VerifiableCredential vc, KeyPair keyPair, String keyId) {
try {
Algorithm algorithm = createAlgorithm(keyPair);
// Create JWT claims from VC
Map<String, Object> vcClaim = createVcClaim(vc);
return JWT.create()
.withJWTId(vc.getId() != null ? vc.getId().toString() : UUID.randomUUID().toString())
.withIssuer(vc.getIssuer().toString())
.withSubject(getSubjectDid(vc))
.withAudience(getAudienceDid(vc))
.withIssuedAt(Date.from(vc.getIssuanceDate()))
.withExpiresAt(vc.getExpirationDate() != null ?
Date.from(vc.getExpirationDate()) : null)
.withNotBefore(Date.from(vc.getIssuanceDate()))
.withClaim("vc", vcClaim)
.withKeyId(keyId)
.sign(algorithm);
} catch (Exception e) {
throw new RuntimeException("Failed to create JWT VC", e);
}
}
public VerifiableCredential verifyJwtVc(String jwtVc, KeyPair verificationKey) {
try {
Algorithm algorithm = createAlgorithm(verificationKey);
// Verify JWT signature
DecodedJWT decodedJWT = JWT.require(algorithm)
.build()
.verify(jwtVc);
// Extract VC claim
Map<String, Object> vcClaim = decodedJWT.getClaim("vc").asMap();
// Convert back to VerifiableCredential
return parseVcFromClaim(vcClaim, decodedJWT);
} catch (JWTVerificationException e) {
throw new RuntimeException("JWT VC verification failed", e);
} catch (Exception e) {
throw new RuntimeException("Failed to parse JWT VC", e);
}
}
private Map<String, Object> createVcClaim(VerifiableCredential vc) {
Map<String, Object> vcClaim = new LinkedHashMap<>();
vcClaim.put("@context", vc.getContext().stream().map(URI::toString).toArray());
vcClaim.put("type", vc.getType().toArray());
vcClaim.put("credentialSubject", vc.getCredentialSubject());
if (vc.getCredentialSchema() != null) {
vcClaim.put("credentialSchema", vc.getCredentialSchema());
}
if (vc.getEvidence() != null) {
vcClaim.put("evidence", vc.getEvidence());
}
return vcClaim;
}
private VerifiableCredential parseVcFromClaim(Map<String, Object> vcClaim, DecodedJWT decodedJWT) {
VerifiableCredential.Builder builder = new VerifiableCredential.Builder();
// Extract from JWT claims
builder.issuer(URI.create(decodedJWT.getIssuer()));
builder.issuanceDate(decodedJWT.getIssuedAt().toInstant());
if (decodedJWT.getExpiresAt() != null) {
builder.expirationDate(decodedJWT.getExpiresAt().toInstant());
}
// Extract from VC claim
@SuppressWarnings("unchecked")
List<String> contexts = (List<String>) vcClaim.get("@context");
if (contexts == null || contexts.isEmpty()) {
throw new IllegalArgumentException("Missing @context in VC claim");
}
@SuppressWarnings("unchecked")
List<String> types = (List<String>) vcClaim.get("type");
if (types == null || types.size() < 2) {
throw new IllegalArgumentException("Invalid type in VC claim");
}
builder.type(types.get(1)); // Get specific type after "VerifiableCredential"
@SuppressWarnings("unchecked")
Map<String, Object> credentialSubject = (Map<String, Object>) vcClaim.get("credentialSubject");
if (credentialSubject == null) {
throw new IllegalArgumentException("Missing credentialSubject in VC claim");
}
builder.credentialSubject(credentialSubject);
return builder.build();
}
private String getSubjectDid(VerifiableCredential vc) {
// Extract DID from credential subject
Object subject = vc.getCredentialSubject().get("id");
return subject != null ? subject.toString() : "unknown";
}
private String getAudienceDid(VerifiableCredential vc) {
// Extract audience from credential subject or use issuer
return vc.getIssuer().toString();
}
private Algorithm createAlgorithm(KeyPair keyPair) {
if (keyPair.getPrivate() instanceof RSAPrivateKey) {
return Algorithm.RSA256((RSAPublicKey) keyPair.getPublic(),
(RSAPrivateKey) keyPair.getPrivate());
} else if (keyPair.getPrivate() instanceof ECPrivateKey) {
return Algorithm.ECDSA256((ECPublicKey) keyPair.getPublic(),
(ECPrivateKey) keyPair.getPrivate());
} else {
throw new IllegalArgumentException("Unsupported key pair type");
}
}
}
DID (Decentralized Identifier) Integration
DID Document and Resolver
package com.vc.did;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.net.URI;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.util.*;
public class DidDocument {
private final URI id;
private final List<String> context;
private final List<String> authentication;
private final List<VerificationMethod> verificationMethod;
private final List<Service> service;
public DidDocument(Builder builder) {
this.id = builder.id;
this.context = List.of("https://www.w3.org/ns/did/v1");
this.authentication = List.copyOf(builder.authentication);
this.verificationMethod = List.copyOf(builder.verificationMethod);
this.service = List.copyOf(builder.service);
}
public static class Builder {
private URI id;
private List<String> authentication = new ArrayList<>();
private List<VerificationMethod> verificationMethod = new ArrayList<>();
private List<Service> service = new ArrayList<>();
public Builder id(URI id) { this.id = id; return this; }
public Builder authentication(List<String> auth) { this.authentication = auth; return this; }
public Builder verificationMethod(List<VerificationMethod> vm) { this.verificationMethod = vm; return this; }
public Builder service(List<Service> service) { this.service = service; return this; }
public Builder addAuthentication(String auth) {
this.authentication.add(auth);
return this;
}
public Builder addVerificationMethod(VerificationMethod vm) {
this.verificationMethod.add(vm);
return this;
}
public Builder addService(Service service) {
this.service.add(service);
return this;
}
public DidDocument build() {
if (id == null) throw new IllegalStateException("DID is required");
return new DidDocument(this);
}
}
public static class VerificationMethod {
private final String id;
private final String type;
private final String controller;
private final String publicKeyJwk;
private final String publicKeyMultibase;
public VerificationMethod(String id, String type, String controller,
String publicKeyJwk, String publicKeyMultibase) {
this.id = id;
this.type = type;
this.controller = controller;
this.publicKeyJwk = publicKeyJwk;
this.publicKeyMultibase = publicKeyMultibase;
}
// Getters
public String getId() { return id; }
public String getType() { return type; }
public String getController() { return controller; }
public String getPublicKeyJwk() { return publicKeyJwk; }
public String getPublicKeyMultibase() { return publicKeyMultibase; }
}
public static class Service {
private final String id;
private final String type;
private final String serviceEndpoint;
public Service(String id, String type, String serviceEndpoint) {
this.id = id;
this.type = type;
this.serviceEndpoint = serviceEndpoint;
}
// Getters
public String getId() { return id; }
public String getType() { return type; }
public String getServiceEndpoint() { return serviceEndpoint; }
}
// Getters
public URI getId() { return id; }
public List<String> getContext() { return context; }
public List<String> getAuthentication() { return authentication; }
public List<VerificationMethod> getVerificationMethod() { return verificationMethod; }
public List<Service> getService() { return service; }
public ObjectNode toJson() {
ObjectMapper mapper = new ObjectMapper();
ObjectNode doc = mapper.createObjectNode();
doc.putArray("@context").addAll(context.stream()
.map(mapper::valueToTree)
.toArray(JsonNode[]::new));
doc.put("id", id.toString());
if (!authentication.isEmpty()) {
doc.putArray("authentication").addAll(authentication.stream()
.map(mapper::valueToTree)
.toArray(JsonNode[]::new));
}
if (!verificationMethod.isEmpty()) {
doc.putArray("verificationMethod").addAll(verificationMethod.stream()
.map(this::verificationMethodToJson)
.toArray(JsonNode[]::new));
}
if (!service.isEmpty()) {
doc.putArray("service").addAll(service.stream()
.map(this::serviceToJson)
.toArray(JsonNode[]::new));
}
return doc;
}
private JsonNode verificationMethodToJson(VerificationMethod vm) {
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.createObjectNode();
node.put("id", vm.getId());
node.put("type", vm.getType());
node.put("controller", vm.getController());
if (vm.getPublicKeyJwk() != null) {
node.put("publicKeyJwk", vm.getPublicKeyJwk());
}
if (vm.getPublicKeyMultibase() != null) {
node.put("publicKeyMultibase", vm.getPublicKeyMultibase());
}
return node;
}
private JsonNode serviceToJson(Service service) {
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = mapper.createObjectNode();
node.put("id", service.getId());
node.put("type", service.getType());
node.put("serviceEndpoint", service.getServiceEndpoint());
return node;
}
}
public class DidResolver {
private final Map<String, DidDocument> didRegistry;
private final List<DidResolverPlugin> plugins;
public DidResolver() {
this.didRegistry = new HashMap<>();
this.plugins = new ArrayList<>();
}
public void registerDid(String did, DidDocument document) {
didRegistry.put(did, document);
}
public void addResolverPlugin(DidResolverPlugin plugin) {
plugins.add(plugin);
}
public DidDocument resolve(String did) {
// Check local registry first
if (didRegistry.containsKey(did)) {
return didRegistry.get(did);
}
// Try plugins
for (DidResolverPlugin plugin : plugins) {
try {
DidDocument doc = plugin.resolve(did);
if (doc != null) {
// Cache the resolved DID
didRegistry.put(did, doc);
return doc;
}
} catch (Exception e) {
System.err.println("Plugin failed to resolve DID " + did + ": " + e.getMessage());
}
}
throw new RuntimeException("Unable to resolve DID: " + did);
}
public VerificationMethod findVerificationMethod(String verificationMethodId) {
// Extract DID from verification method ID
String did = verificationMethodId.split("#")[0];
DidDocument doc = resolve(did);
return doc.getVerificationMethod().stream()
.filter(vm -> vm.getId().equals(verificationMethodId))
.findFirst()
.orElseThrow(() -> new RuntimeException(
"Verification method not found: " + verificationMethodId));
}
}
public interface DidResolverPlugin {
DidDocument resolve(String did);
}
VC Issuance and Verification Service
Complete VC Service Implementation
package com.vc.service;
import com.vc.core.VerifiableCredential;
import com.vc.core.VerifiablePresentation;
import com.vc.did.DidDocument;
import com.vc.did.DidResolver;
import com.vc.jwt.JwtVerifiableCredential;
import com.vc.proof.Ed25519Signature2020;
import com.vc.proof.LinkedDataProof;
import java.net.URI;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
public class VerifiableCredentialService {
private final DidResolver didResolver;
private final Map<String, KeyPair> issuerKeys;
private final JwtVerifiableCredential jwtVcService;
public VerifiableCredentialService(DidResolver didResolver) {
this.didResolver = didResolver;
this.issuerKeys = new HashMap<>();
this.jwtVcService = new JwtVerifiableCredential();
initializeTestIssuers();
}
private void initializeTestIssuers() {
try {
// Generate test issuer keys
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(256);
KeyPair universityKey = keyGen.generateKeyPair();
KeyPair employerKey = keyGen.generateKeyPair();
issuerKeys.put("did:example:university", universityKey);
issuerKeys.put("did:example:employer", employerKey);
} catch (Exception e) {
throw new RuntimeException("Failed to initialize test issuers", e);
}
}
public VerifiableCredential issueUniversityDegree(String studentDid, String degreeType) {
try {
KeyPair issuerKey = issuerKeys.get("did:example:university");
VerifiableCredential vc = new VerifiableCredential.Builder()
.id(URI.create("urn:uuid:" + UUID.randomUUID()))
.type("UniversityDegreeCredential")
.issuer(URI.create("did:example:university"))
.issuanceDate(Instant.now())
.expirationDate(Instant.now().plus(365, ChronoUnit.DAYS))
.addCredentialSubject("id", studentDid)
.addCredentialSubject("degree", degreeType)
.addCredentialSubject("university", "Example University")
.addCredentialSubject("graduationDate", "2024-06-01")
.addCredentialSubject("gpa", "3.8")
.build();
// Sign with JWT
String jwtVc = jwtVcService.createJwtVc(vc, issuerKey, "key-1");
// For demo, we'll return the original VC - in production you'd use the JWT
return vc;
} catch (Exception e) {
throw new RuntimeException("Failed to issue university degree", e);
}
}
public VerifiableCredential issueEmploymentCredential(String employeeDid,
String position,
String company) {
try {
KeyPair issuerKey = issuerKeys.get("did:example:employer");
VerifiableCredential vc = new VerifiableCredential.Builder()
.id(URI.create("urn:uuid:" + UUID.randomUUID()))
.type("EmploymentCredential")
.issuer(URI.create("did:example:employer"))
.issuanceDate(Instant.now())
.expirationDate(Instant.now().plus(180, ChronoUnit.DAYS))
.addCredentialSubject("id", employeeDid)
.addCredentialSubject("position", position)
.addCredentialSubject("company", company)
.addCredentialSubject("startDate", "2023-01-15")
.addCredentialSubject("employmentStatus", "active")
.build();
return vc;
} catch (Exception e) {
throw new RuntimeException("Failed to issue employment credential", e);
}
}
public VerifiablePresentation createPresentation(List<VerifiableCredential> credentials,
String holderDid) {
return new VerifiablePresentation.Builder()
.id(URI.create("urn:uuid:" + UUID.randomUUID()))
.holder(URI.create(holderDid))
.verifiableCredential(credentials)
.build();
}
public boolean verifyCredential(VerifiableCredential vc) {
try {
// Check expiration
if (vc.isExpired()) {
System.out.println("Credential is expired");
return false;
}
// Verify issuer's DID document
String issuerDid = vc.getIssuer().toString();
DidDocument issuerDoc = didResolver.resolve(issuerDid);
// In production, verify the proof/signature
// This is a simplified verification
System.out.println("Credential verified for issuer: " + issuerDid);
return true;
} catch (Exception e) {
System.err.println("Credential verification failed: " + e.getMessage());
return false;
}
}
public boolean verifyPresentation(VerifiablePresentation vp, String challenge) {
try {
// Verify holder
String holderDid = vp.getHolder().toString();
DidDocument holderDoc = didResolver.resolve(holderDid);
// Verify all credentials in presentation
for (VerifiableCredential vc : vp.getVerifiableCredential()) {
if (!verifyCredential(vc)) {
System.out.println("Presentation contains invalid credential: " + vc.getId());
return false;
}
}
// In production, verify the presentation proof with challenge
System.out.println("Presentation verified for holder: " + holderDid);
return true;
} catch (Exception e) {
System.err.println("Presentation verification failed: " + e.getMessage());
return false;
}
}
public Map<String, Object> evaluateCredentialPolicy(VerifiableCredential vc,
CredentialPolicy policy) {
Map<String, Object> result = new HashMap<>();
result.put("credentialId", vc.getId().toString());
result.put("compliant", true);
result.put("violations", new ArrayList<String>());
List<String> violations = (List<String>) result.get("violations");
// Check expiration
if (vc.isExpired() && policy.isExpirationRequired()) {
violations.add("Credential is expired");
result.put("compliant", false);
}
// Check required fields
for (String requiredField : policy.getRequiredFields()) {
if (!vc.getCredentialSubject().containsKey(requiredField)) {
violations.add("Missing required field: " + requiredField);
result.put("compliant", false);
}
}
// Check issuer trust
if (policy.getTrustedIssuers() != null &&
!policy.getTrustedIssuers().contains(vc.getIssuer().toString())) {
violations.add("Issuer not trusted: " + vc.getIssuer());
result.put("compliant", false);
}
return result;
}
}
class CredentialPolicy {
private boolean expirationRequired = true;
private Set<String> requiredFields = new HashSet<>();
private Set<String> trustedIssuers = new HashSet<>();
private Map<String, Object> fieldConstraints = new HashMap<>();
// Getters and setters
public boolean isExpirationRequired() { return expirationRequired; }
public void setExpirationRequired(boolean expirationRequired) {
this.expirationRequired = expirationRequired;
}
public Set<String> getRequiredFields() { return requiredFields; }
public void setRequiredFields(Set<String> requiredFields) {
this.requiredFields = requiredFields;
}
public Set<String> getTrustedIssuers() { return trustedIssuers; }
public void setTrustedIssuers(Set<String> trustedIssuers) {
this.trustedIssuers = trustedIssuers;
}
public void addRequiredField(String field) {
requiredFields.add(field);
}
public void addTrustedIssuer(String issuer) {
trustedIssuers.add(issuer);
}
}
Spring Boot Integration
VC Spring Boot Starter
package com.vc.spring;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.vc.did.DidResolver;
import com.vc.service.VerifiableCredentialService;
@Configuration
@ConfigurationProperties(prefix = "verifiable-credentials")
public class VCAutoConfiguration {
private String defaultIssuerDid;
private boolean enableTestIssuers = true;
@Bean
public DidResolver didResolver() {
DidResolver resolver = new DidResolver();
if (enableTestIssuers) {
// Register test DIDs
registerTestDids(resolver);
}
return resolver;
}
@Bean
public VerifiableCredentialService vcService(DidResolver didResolver) {
return new VerifiableCredentialService(didResolver);
}
private void registerTestDids(DidResolver resolver) {
// Register test issuer DIDs
// In production, these would be resolved from real DID methods
}
// Getters and setters
public String getDefaultIssuerDid() { return defaultIssuerDid; }
public void setDefaultIssuerDid(String defaultIssuerDid) {
this.defaultIssuerDid = defaultIssuerDid;
}
public boolean isEnableTestIssuers() { return enableTestIssuers; }
public void setEnableTestIssuers(boolean enableTestIssuers) {
this.enableTestIssuers = enableTestIssuers;
}
}
// REST Controller for VC operations
@RestController
@RequestMapping("/api/vc")
public class VCController {
@Autowired
private VerifiableCredentialService vcService;
@PostMapping("/issue/degree")
public ResponseEntity<VerifiableCredential> issueDegreeCredential(
@RequestBody DegreeIssueRequest request) {
try {
VerifiableCredential vc = vcService.issueUniversityDegree(
request.getStudentDid(), request.getDegreeType());
return ResponseEntity.ok(vc);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/verify")
public ResponseEntity<VerificationResult> verifyCredential(
@RequestBody VerifiableCredential vc) {
boolean isValid = vcService.verifyCredential(vc);
return ResponseEntity.ok(new VerificationResult(isValid));
}
@PostMapping("/presentation/create")
public ResponseEntity<VerifiablePresentation> createPresentation(
@RequestBody PresentationRequest request) {
try {
VerifiablePresentation vp = vcService.createPresentation(
request.getCredentials(), request.getHolderDid());
return ResponseEntity.ok(vp);
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/presentation/verify")
public ResponseEntity<VerificationResult> verifyPresentation(
@RequestBody PresentationVerificationRequest request) {
boolean isValid = vcService.verifyPresentation(
request.getPresentation(), request.getChallenge());
return ResponseEntity.ok(new VerificationResult(isValid));
}
@PostMapping("/policy/evaluate")
public ResponseEntity<PolicyEvaluationResult> evaluatePolicy(
@RequestBody PolicyEvaluationRequest request) {
Map<String, Object> result = vcService.evaluateCredentialPolicy(
request.getCredential(), request.getPolicy());
return ResponseEntity.ok(new PolicyEvaluationResult(result));
}
}
// Request/Response DTOs
class DegreeIssueRequest {
private String studentDid;
private String degreeType;
// Getters and setters
public String getStudentDid() { return studentDid; }
public void setStudentDid(String studentDid) { this.studentDid = studentDid; }
public String getDegreeType() { return degreeType; }
public void setDegreeType(String degreeType) { this.degreeType = degreeType; }
}
class PresentationRequest {
private List<VerifiableCredential> credentials;
private String holderDid;
// Getters and setters
public List<VerifiableCredential> getCredentials() { return credentials; }
public void setCredentials(List<VerifiableCredential> credentials) { this.credentials = credentials; }
public String getHolderDid() { return holderDid; }
public void setHolderDid(String holderDid) { this.holderDid = holderDid; }
}
class PresentationVerificationRequest {
private VerifiablePresentation presentation;
private String challenge;
// Getters and setters
public VerifiablePresentation getPresentation() { return presentation; }
public void setPresentation(VerifiablePresentation presentation) { this.presentation = presentation; }
public String getChallenge() { return challenge; }
public void setChallenge(String challenge) { this.challenge = challenge; }
}
class PolicyEvaluationRequest {
private VerifiableCredential credential;
private CredentialPolicy policy;
// Getters and setters
public VerifiableCredential getCredential() { return credential; }
public void setCredential(VerifiableCredential credential) { this.credential = credential; }
public CredentialPolicy getPolicy() { return policy; }
public void setPolicy(CredentialPolicy policy) { this.policy = policy; }
}
class VerificationResult {
private boolean valid;
public VerificationResult(boolean valid) {
this.valid = valid;
}
public boolean isValid() { return valid; }
public void setValid(boolean valid) { this.valid = valid; }
}
class PolicyEvaluationResult {
private Map<String, Object> result;
public PolicyEvaluationResult(Map<String, Object> result) {
this.result = result;
}
public Map<String, Object> getResult() { return result; }
public void setResult(Map<String, Object> result) { this.result = result; }
}
Advanced VC Features
Selective Disclosure and ZK Proofs
package com.vc.advanced;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.vc.core.VerifiableCredential;
import java.util.*;
public class SelectiveDisclosure {
private final ObjectMapper mapper = new ObjectMapper();
public VerifiableCredential createSelectiveDisclosure(VerifiableCredential originalVc,
Set<String> disclosedFields) {
// Create a new VC with only disclosed fields
Map<String, Object> selectiveSubject = new HashMap<>();
for (String field : disclosedFields) {
if (originalVc.getCredentialSubject().containsKey(field)) {
selectiveSubject.put(field, originalVc.getCredentialSubject().get(field));
}
}
// Add mandatory fields
if (originalVc.getCredentialSubject().containsKey("id")) {
selectiveSubject.put("id", originalVc.getCredentialSubject().get("id"));
}
return new VerifiableCredential.Builder()
.id(originalVc.getId())
.type(originalVc.getType().get(1)) // Get specific type
.issuer(originalVc.getIssuer())
.issuanceDate(originalVc.getIssuanceDate())
.expirationDate(originalVc.getExpirationDate())
.credentialSubject(selectiveSubject)
.build();
}
public ObjectNode createDerivedProof(VerifiableCredential originalVc,
Set<String> disclosedFields,
String proofMethod) {
// Create a derived proof using BBS+ or other ZK-friendly signatures
// This is a simplified implementation
ObjectNode derivedProof = mapper.createObjectNode();
derivedProof.put("type", "BbsBlsSignature2020");
derivedProof.put("created", new Date().toString());
derivedProof.put("proofPurpose", "assertionMethod");
derivedProof.put("verificationMethod", proofMethod);
// Add disclosure information
ObjectNode disclosure = mapper.createObjectNode();
disclosure.putArray("disclosedFields").addAll(
disclosedFields.stream().map(mapper::valueToTree).toArray(JsonNode[]::new));
derivedProof.set("disclosure", disclosure);
return derivedProof;
}
}
// VC Status Management (Revocation, Suspension)
public class CredentialStatusManager {
private final Map<String, CredentialStatus> statusRegistry;
public CredentialStatusManager() {
this.statusRegistry = new HashMap<>();
}
public void revokeCredential(String credentialId, String reason) {
statusRegistry.put(credentialId, new CredentialStatus(
credentialId, Status.REVOKED, reason, new Date()));
}
public void suspendCredential(String credentialId, String reason) {
statusRegistry.put(credentialId, new CredentialStatus(
credentialId, Status.SUSPENDED, reason, new Date()));
}
public void reinstateCredential(String credentialId) {
statusRegistry.remove(credentialId);
}
public CredentialStatus getStatus(String credentialId) {
return statusRegistry.get(credentialId);
}
public boolean isCredentialValid(String credentialId) {
CredentialStatus status = statusRegistry.get(credentialId);
return status == null || status.getStatus() == Status.ACTIVE;
}
}
class CredentialStatus {
private final String credentialId;
private final Status status;
private final String reason;
private final Date timestamp;
public CredentialStatus(String credentialId, Status status, String reason, Date timestamp) {
this.credentialId = credentialId;
this.status = status;
this.reason = reason;
this.timestamp = timestamp;
}
// Getters
public String getCredentialId() { return credentialId; }
public Status getStatus() { return status; }
public String getReason() { return reason; }
public Date getTimestamp() { return timestamp; }
}
enum Status {
ACTIVE, SUSPENDED, REVOKED
}
This comprehensive Verifiable Credentials implementation in Java provides:
- Core Data Models - VC and VP according to W3C standards
- Cryptographic Proofs - Linked Data Proofs and JWT-based VCs
- DID Integration - Decentralized identifier support
- Issuance and Verification - Complete VC lifecycle management
- Spring Boot Integration - REST APIs and auto-configuration
- Policy Evaluation - Credential validation against policies
- Advanced Features - Selective disclosure, revocation, status management
- Security - Cryptographic signing and verification
The implementation follows W3C Verifiable Credentials Data Model standards and provides a solid foundation for building trustable digital credential systems in Java applications.