メインコンテンツまでスキップ

Verifiable Credentials

The Credential Model

Components:

┌─────────────────────────────────────────────────────┐
│ Issuer │
│ (University, Govt, etc) │
│ │ │
│ ↓ Issues │
│ ┌────────────────────┐ │
│ │ Verifiable │ │
│ │ Credential │ │
│ │ │ │
│ │ Claim: Degree │ │
│ │ Holder: Alice DID │ │
│ │ Issuer Signature │ │
│ └────────────────────┘ │
│ ↓ Held by │
│ Holder │
│ (Alice) │
│ │ │
│ ↓ Presents │
│ Verifier │
│ (Employer, Service) │
└─────────────────────────────────────────────────────┘

Verifiable Credential structure:

{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"type": ["VerifiableCredential", "UniversityDegreeCredential"],
"issuer": "did:ethr:0xUniversityAddress",
"issuanceDate": "2024-01-15T00:00:00Z",
"credentialSubject": {
"id": "did:ethr:0xAliceAddress",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science in Computer Science",
"university": "Example University"
}
},
"proof": {
"type": "EcdsaSecp256k1Signature2019",
"created": "2024-01-15T00:00:00Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "did:ethr:0xUniversity...#keys-1",
"jws": "eyJhbGciOiJFUzI1NksifQ.eyJ..."
}
}

Verification process:

1. Verifier receives credential from holder
2. Extract issuer DID: did:ethr:0xUniversity...
3. Resolve issuer DID → Get public key
4. Verify signature on credential
5. Check credential hasn't been revoked
6. Check credential is still valid (not expired)
7. Verify claims match requirements

If all checks pass → Credential is valid
Verifier now knows: Alice has this degree, issued by this university

Zero-Knowledge Proofs for Selective Disclosure

Problem with plain credentials:

Scenario: Alice wants to prove she's over 18

Traditional credential:
{
"name": "Alice Johnson",
"dateOfBirth": "1995-03-15",
"address": "123 Main St, City, State",
"nationalID": "123-45-6789"
}

Alice must reveal:
✓ She's over 18 (needed)
✗ Exact birth date (unnecessary)
✗ Full name (unnecessary)
✗ Address (unnecessary)
✗ National ID (unnecessary)

Privacy loss: 80% of data revealed unnecessarily

Zero-knowledge solution:

Alice generates ZK proof:
"I prove that I am over 18"

Without revealing:
- Exact age
- Birth date
- Name
- Any other information

Verifier learns:
✓ Alice is over 18
✗ Nothing else

Implementation: zk-SNARKs

contract AgeVerifier {
// Verifier contract (generated by Circom/ZoKrates)
function verifyProof(
uint[2] memory a,
uint[2][2] memory b,
uint[2] memory c,
uint[1] memory input
) public view returns (bool) {
// Verify ZK proof that age > 18
// Without knowing exact age
return zkVerify(a, b, c, input);
}
}

// Alice's client:
const proof = generateProof({
birthDate: "1995-03-15", // Private input
currentDate: "2024-11-03", // Public input
minimumAge: 18 // Public input
});
// Circuit proves: currentDate - birthDate >= 18 years

// Alice submits proof (no birth date revealed)
await ageVerifier.verifyProof(proof.a, proof.b, proof.c, proof.input);
// Returns: true (Alice is over 18)
// Verifier learns nothing else

Other selective disclosure examples:

1. Prove income range without exact amount:
"I earn between $50k-$100k"
Without revealing: $67,543.21

2. Prove country of residence without full address:
"I reside in the United States"
Without revealing: 123 Main St, Springfield, IL

3. Prove credit score above threshold:
"My credit score is >700"
Without revealing: 742

4. Prove education level:
"I have a bachelor's degree or higher"
Without revealing: PhD in Computer Science, MIT, 2018

5. Prove vaccination status:
"I am fully vaccinated against COVID-19"
Without revealing: Which vaccine, date, location

Credential Revocation

Problem: How to revoke credentials on a permanent blockchain?

Method 1: Revocation list

contract RevocationRegistry {
mapping(bytes32 => bool) public revoked;
mapping(address => bool) public isIssuer;

modifier onlyIssuer() {
require(isIssuer[msg.sender], "Not an issuer");
_;
}

function revokeCredential(bytes32 credentialId) external onlyIssuer {
revoked[credentialId] = true;
emit CredentialRevoked(credentialId, msg.sender);
}

function isRevoked(bytes32 credentialId) external view returns (bool) {
return revoked[credentialId];
}
}

// Verification now includes:
async function verifyCredential(credential) {
// 1. Verify signature
const signatureValid = verifySignature(credential);
if (!signatureValid) return false;

// 2. Check revocation
const credentialId = hashCredential(credential);
const isRevoked = await revocationRegistry.isRevoked(credentialId);
if (isRevoked) return false;

// 3. Credential is valid
return true;
}

Method 2: Accumulator-based (privacy-preserving)

Cryptographic accumulator:
- All valid credentials hashed into single accumulator value
- Revoked credentials removed from accumulator
- Holder proves credential is in accumulator (ZK proof)
- No need to reveal credential ID

Benefits:
+ Privacy: Verifier doesn't learn credential ID
+ Efficiency: Single accumulator check
+ Compact: O(1) size regardless of credential count

Drawbacks:
- Complex cryptography
- Requires updates when credentials revoked

Method 3: Expiration

{
"credentialSubject": {...},
"expirationDate": "2025-01-15T00:00:00Z",
"proof": {...}
}

// Simple approach:
// Credentials expire automatically
// No revocation needed (just don't renew)
// Trade-off: Need to reissue periodically