Verifiable Credentials (VCs)
Verifiable Credentials are tamper-evident digital credentials that can be cryptographically verified without contacting the issuer. They’re the digital equivalent of physical credentials like driver’s licenses, diplomas, or identity cards.
What is a Verifiable Credential?
A Verifiable Credential is a digital statement made by an issuer about a subject (usually the credential holder) that includes:
Claims: Information about the subject (name, date of birth, degree, etc.)
Metadata: Issuer, issue date, expiration, credential type
Proof: Cryptographic signature that proves authenticity
Think of a VC like a digitally-signed diploma: it contains information about you, who issued it, and a cryptographic proof that it hasn’t been tampered with.
VC Structure
A standard Verifiable Credential follows the W3C data model:
{
"@context" : [
"https://www.w3.org/2018/credentials/v1" ,
"https://www.w3.org/2018/credentials/examples/v1"
],
"id" : "http://example.edu/credentials/3732" ,
"type" : [ "VerifiableCredential" , "UniversityDegreeCredential" ],
"issuer" : "did:alyra:UniversityPublicKey..." ,
"issuanceDate" : "2024-01-01T00:00:00Z" ,
"expirationDate" : "2029-01-01T00:00:00Z" ,
"credentialSubject" : {
"id" : "did:alyra:StudentPublicKey..." ,
"degree" : {
"type" : "BachelorDegree" ,
"name" : "Bachelor of Science in Computer Science"
},
"gpa" : "3.8"
},
"proof" : {
"type" : "Dilithium" ,
"created" : "2024-01-01T00:00:00Z" ,
"proofPurpose" : "assertionMethod" ,
"verificationMethod" : "did:alyra:UniversityPublicKey...#keys-1" ,
"signature" : "eyJhbGciOiJEaWxpdGhpdW0yIiwiYjY0IjpmYWxzZSwiY3JpdCI..."
}
}
VC Components
Defines the meaning of terms used in the credential. Points to JSON-LD context files. "@context" : [
"https://www.w3.org/2018/credentials/v1" ,
"https://example.com/contexts/education/v1"
]
Unique identifier for this specific credential instance. "id" : "https://university.edu/credentials/12345"
Specifies what kind of credential this is. Always includes VerifiableCredential. "type" : [ "VerifiableCredential" , "DriverLicense" ]
DID of the organization that issued the credential. "issuer" : "did:alyra:DMVPublicKey..."
When the credential was issued (ISO 8601 format). "issuanceDate" : "2024-01-15T10:30:00Z"
The actual claims about the subject. This is the main content of the credential. "credentialSubject" : {
"id" : "did:alyra:HolderDID..." ,
"givenName" : "Alice" ,
"familyName" : "Smith" ,
"birthDate" : "1990-01-01"
}
Cryptographic proof that makes the credential verifiable. "proof" : {
"type" : "Dilithium" ,
"created" : "2024-01-15T10:30:00Z" ,
"verificationMethod" : "did:alyra:IssuerDID...#keys-1" ,
"signature" : "base64_encoded_signature..."
}
VC Lifecycle
Issuance Process
In Sphyre, credential issuance follows these steps:
User Requests Credential
User fills out a credential request form in Sphyre ALV const request = {
schemaId: "national-id-v1" ,
templateId: "gov-national-id" ,
claims: {
fullName: "Alice Smith" ,
dateOfBirth: "1990-01-01" ,
nationality: "USA"
},
holderDid: "did:alyra:UserPublicKey..."
};
Issuer Reviews Request
Request appears in issuer dashboard (Sphyre Issuers)
Issuer Approves & Signs
Issuer approves and Fortro Engine creates the VC let credential = VerifiableCredential {
context : vec! [ VC_CONTEXT . to_string ()],
id : generate_credential_id (),
types : vec! [ "VerifiableCredential" . into (), schema . name . clone ()],
issuer : issuer_did . clone (),
issuance_date : Utc :: now (),
credential_subject : claims ,
proof : sign_credential ( & credential_data , & issuer_private_key )
};
Store on IPFS
Credential is uploaded to IPFS for decentralized storage let ipfs_hash = ipfs_client . add_json ( & credential ) . await ? ;
Anchor on Blockchain
IPFS hash is anchored on Ethereum for immutability let tx = contract . anchor_credential (
ipfs_hash ,
credential . id,
issuer_did
) . send () . await ? ;
Deliver to Holder
Credential is sent to user’s wallet
Verification Process
Verifiers can check credential authenticity without contacting the issuer:
Receive Presentation
Verifier receives a Verifiable Presentation containing the VC
Extract Credential
Parse the VC and extract the proof
Resolve Issuer DID
Get issuer’s public key from their DID const didDocument = await resolveDID ( credential . issuer );
const publicKey = didDocument . verificationMethod [ 0 ]. publicKeyBase64 ;
Verify Signature
Check cryptographic signature using issuer’s public key const isValid = verifySignature (
credential ,
credential . proof . signature ,
publicKey
);
Check Blockchain Anchor
Verify the credential hash exists on blockchain const onChain = await contract . verifyAnchor ( ipfsHash );
Check Revocation Status
Ensure credential hasn’t been revoked const isRevoked = await contract . isRevoked ( credential . id );
Validate Expiration
Check if credential is still valid const isExpired = new Date () > new Date ( credential . expirationDate );
Credential Types in Sphyre
Sphyre supports various credential schemas:
National ID Government-issued identity credentials with full name, DOB, nationality
Driver's License Driving credentials with license class, restrictions, expiration
Student ID Educational institution credentials with student number, program
Employee Badge Employment credentials with job title, department, access level
Professional License Professional certifications with license number, specialty, board
Health Insurance Insurance credentials with policy number, coverage type, provider
Selective Disclosure
VCs support selective disclosure - sharing only specific claims:
Example: Age Verification
Instead of showing full ID:
// Full credential contains:
{
"name" : "Alice Smith" ,
"dateOfBirth" : "1990-01-01" ,
"address" : "123 Main St" ,
"idNumber" : "ABC123456"
}
// Selective disclosure - only share age proof:
{
"isOver21" : true // Proven via ZKP without revealing exact birthdate
}
Verifiable Presentations
To share credentials, holders create Verifiable Presentations (VPs):
{
"@context" : [ "https://www.w3.org/2018/credentials/v1" ],
"type" : "VerifiablePresentation" ,
"verifiableCredential" : [{
// Full VC or selected claims
}],
"holder" : "did:alyra:HolderPublicKey..." ,
"proof" : {
"type" : "Dilithium" ,
"created" : "2024-01-15T12:00:00Z" ,
"challenge" : "random-challenge-from-verifier" ,
"domain" : "verifier.sphyre.tech" ,
"proofPurpose" : "authentication" ,
"verificationMethod" : "did:alyra:HolderPublicKey...#keys-1" ,
"signature" : "holder_signature..."
}
}
Key Points:
Holder signs the presentation (not just the credential)
Includes challenge from verifier to prevent replay attacks
Can contain multiple credentials
Proves holder controls the DID
Revocation
Issuers can revoke credentials if needed:
Revocation Methods
Status List
Blockchain Registry
Credential contains a reference to a revocation list "credentialStatus" : {
"id" : "https://issuer.com/status/1#94567" ,
"type" : "RevocationList2020Status" ,
"revocationListIndex" : "94567" ,
"revocationListCredential" : "https://issuer.com/status/1"
}
Sphyre’s approach: Mark as revoked on Ethereum smart contract function revokeCredential ( bytes32 credentialId ) external {
require ( msg.sender == credentialIssuers[credentialId]);
revokedCredentials[credentialId] = true ;
emit CredentialRevoked (credentialId);
}
Storage & Privacy
IPFS Storage
Content-Addressed Files identified by cryptographic hash, ensuring data integrity
Decentralized No single point of failure, distributed across IPFS network
Immutable Content cannot be modified; changes create new hash
Optional Encryption Credentials can be encrypted before upload for privacy
Privacy Considerations
VCs stored on IPFS are public by default. For sensitive data, use encryption or store only hashes.
Privacy Strategies:
Encryption: Encrypt credential before IPFS upload
Hashed Claims: Store only hashes of sensitive data
Zero-Knowledge Proofs: Prove claims without revealing data
Pairwise Credentials: Issue different credentials for different relationships
Schema Management
Schemas define the structure of credentials:
{
"id" : "national-id-v1" ,
"name" : "National ID" ,
"version" : "1.0" ,
"description" : "Government-issued national identification" ,
"fields" : [
{
"name" : "fullName" ,
"type" : "string" ,
"required" : true ,
"description" : "Legal full name"
},
{
"name" : "dateOfBirth" ,
"type" : "date" ,
"required" : true ,
"description" : "Date of birth (YYYY-MM-DD)"
},
{
"name" : "nationality" ,
"type" : "string" ,
"required" : true ,
"description" : "Country of citizenship"
},
{
"name" : "idNumber" ,
"type" : "string" ,
"required" : true ,
"description" : "Unique identification number"
}
],
"issuer" : "did:alyra:GovernmentDID..."
}
Best Practices
Always set appropriate expiration dates. Credentials shouldn’t be valid forever. "expirationDate" : "2029-12-31T23:59:59Z"
Issue specific credentials for specific purposes. Don’t create “super credentials” with all user data.
Include only necessary claims. Less data = better privacy.
Version your schemas and credentials for backward compatibility. "version" : "1.0" ,
"schemaVersion" : "2.1"
Use JSON-LD contexts for semantic interoperability across systems.
VC vs Traditional Credentials
Feature Traditional Verifiable Credential Format Paper/PDF JSON-LD Verification Contact issuer Cryptographic proof Tampering Easy to forge Cryptographically impossible Sharing Photocopy Selective disclosure Revocation Difficult Instant via blockchain Privacy Full disclosure Minimal disclosure Portability Physical/email Digital wallet Speed Days/weeks Instant
Advanced Features
Composite Credentials
Combine multiple credentials for complex proofs:
{
"type" : "VerifiablePresentation" ,
"verifiableCredential" : [
{ /* University degree */ },
{ /* Professional license */ },
{ /* Employment verification */ }
]
}
Derived Credentials
Create new credentials based on existing ones:
// Original: Full birthdate credential
// Derived: "Over 21" credential
const derivedCredential = deriveCredential (
originalCredential ,
[ "isOver21" ], // Only this claim
zkProof // Zero-knowledge proof
);
Delegated Credentials
Issue credentials on behalf of another issuer:
{
"issuer" : "did:alyra:RegionalOffice..." ,
"delegatedFrom" : "did:alyra:NationalAuthority..." ,
"proof" : {
"type" : "DelegatedProof" ,
"delegationCredential" : { /* Proof of delegation */ }
}
}
Code Examples
Creating a Credential (Backend)
use serde_json :: json;
use chrono :: Utc ;
async fn issue_credential (
issuer_did : & str ,
holder_did : & str ,
claims : HashMap < String , String >,
schema_id : & str
) -> Result < VerifiableCredential > {
// Create credential structure
let credential = json! ({
"@context" : [ "https://www.w3.org/2018/credentials/v1" ],
"type" : [ "VerifiableCredential" , schema_id ],
"issuer" : issuer_did ,
"issuanceDate" : Utc :: now () . to_rfc3339 (),
"credentialSubject" : {
"id" : holder_did ,
"claims" : claims
}
});
// Sign credential
let signature = sign_with_dilithium ( & credential , issuer_private_key ) ? ;
// Add proof
credential [ "proof" ] = json! ({
"type" : "Dilithium" ,
"created" : Utc :: now () . to_rfc3339 (),
"verificationMethod" : format! ( "{}#keys-1" , issuer_did ),
"signature" : base64 :: encode ( signature )
});
Ok ( credential )
}
Verifying a Credential (Frontend)
async function verifyCredential ( credential : VerifiableCredential ) {
// 1. Resolve issuer DID
const issuerDID = credential . issuer ;
const didDocument = await resolveDID ( issuerDID );
// 2. Get public key
const publicKey = didDocument . verificationMethod [ 0 ]. publicKeyBase64 ;
// 3. Verify signature
const credentialWithoutProof = { ... credential };
delete credentialWithoutProof . proof ;
const isValidSignature = await verifyDilithiumSignature (
JSON . stringify ( credentialWithoutProof ),
credential . proof . signature ,
publicKey
);
// 4. Check expiration
const isExpired = new Date () > new Date ( credential . expirationDate );
// 5. Check revocation
const isRevoked = await checkRevocationStatus ( credential . id );
return isValidSignature && ! isExpired && ! isRevoked ;
}
Resources
Next Steps
Issue Your First Credential