@synet/credential
Version:
VC Credentials - Simple, Robust, Unit-based Verifiable Credentials service
696 lines (547 loc) • 18.9 kB
Markdown
# /credential
```
_____ _
/ ____| | |
| (___ _ _ _ __ ___| |_
\___ \| | | | '_ \ / _ \ __|
____) | |_| | | | | __/ |_
|_____/ \__, |_| |_|\___|\__|
__/ |
____|___/ _ _ _ _
/ ____| | | | | (_) | |
| | _ __ ___ __| | ___ _ __ | |_ _ __ _| |
| | | '__/ _ \/ _` |/ _ \ '_ \| __| |/ _` | |
| |____| | | __/ (_| | __/ | | | |_| | (_| | |
\_____|_| \___|\__,_|\___|_| |_|\__|_|\__,_|_|
version: 1.0.3
```
**Production-ready W3C Verifiable Credentials** built on battle-tested /keys. Issue, verify, and manage credentials with zero dependencies and full TypeScript support.
## Why /credential?
✅ **W3C Standard Compatible** - Real verifiable credentials that work everywhere
✅ **Built on /keys** - Inherits 211 tests, 87% coverage, zero dependencies
✅ **JWT Proof Format** - Industry-standard, widely supported
✅ **Type Safe** - Full TypeScript support with proper error handling
✅ **Simple & Powerful** - Clean functions + intelligent units that learn capabilities
✅ **Production Ready** - Used in real identity systems and credential workflows
**Supported**: Ed25519, RSA, secp256k1 keys • JWT proofs • W3C data model • Custom credential types
## Quick Start
### Option 1: Direct Functions (Simple & Fast)
Perfect for straightforward credential workflows:
```typescript
import { generateKeyPair, Signer } from '/keys';
import { createDIDKey } from '/did';
import { issueVC, verifyVC } from '/credential';
// 1. Generate issuer keys
const keyPair = generateKeyPair('ed25519');
// 2. Create signer from keys
const signer = Signer.create(
keyPair.privateKey,
keyPair.publicKey,
'ed25519',
{ purpose: 'credential-issuing' }
);
// 3. Create proper DID
const issuerDid = createDIDKey(keyPair.publicKey, 'ed25519');
// 4. Issue a university degree credential
const credential = await issueVC(
signer,
{
holder: {
id: 'did:key:alice123',
name: 'Alice Smith'
},
degree: 'Computer Science',
university: 'Tech University',
graduationYear: 2024
},
'UniversityDegreeCredential',
issuerDid
);
if (credential.success) {
console.log('✅ Credential issued:', credential.data.id);
// 5. Verify the credential
const verification = await verifyVC(signer, credential.data);
console.log('✅ Verified:', verification.success);
} else {
console.error('❌ Failed:', credential.error);
}
```
### Option 2: Credential (Advanced Learning)
For complex systems where units need to learn capabilities:
```typescript
import { generateKeyPair, Signer } from '/keys';
import { Credential } from '/credential';
// 1. Create issuer signer
const keyPair = generateKeyPair('ed25519');
const issuerSigner = Signer.create(keyPair.privateKey, keyPair.publicKey, 'ed25519');
// 2. Create credential unit
const credential = Credential.create();
// 3. Unit learns from signer (no private key transfer!)
const key = issuerSigner.createKey();
const capabilities = key.teach();
await credential.learn([capabilities]);
// 4. Now the unit can issue credentials autonomously
const credentialResult = await credential.issueCredential(
{
holder: { id: 'did:key:alice', name: 'Alice' },
certification: 'Blockchain Developer',
level: 'Advanced'
},
'CertificationCredential',
issuerDid
);
console.log('🎓 Credential:', credential?.id);
```
**When to use each:**
- **Option 1**: Simple apps, document signing, quick prototypes
- **Option 2**: Complex identity systems, distributed workflows, enterprise apps
## Real-World Examples
### University Degree Credential
```typescript
import { generateKeyPair, Signer } from '/keys';
import { createDIDKey } from '/did';
import { issueVC, verifyVC } from '/credential';
// University issuer setup
const universityKeys = generateKeyPair('ed25519');
const universitySigner = Signer.create({
privateKeyPEM: universityKeys.privateKey,
publicKeyPEM: universityKeys.publicKey,
keyType: 'ed25519',
metadata: { institution: 'Tech University' }
});
const universityDid = createDIDKey(universityKeys.publicKey, 'ed25519');
// Issue degree credential
const degreeCredential = await issueVC(
universitySigner,
{
holder: {
id: 'did:key:student123',
name: 'Alice Johnson'
},
degree: 'Master of Computer Science',
university: 'Tech University',
graduationDate: '2024-06-15',
gpa: 3.85,
honors: 'Magna Cum Laude'
},
'UniversityDegreeCredential',
universityDid
);
console.log('🎓 Degree issued:', degreeCredential.data?.id);
```
### Employment Verification
```typescript
// Employer issuer setup
const employerKeys = generateKeyPair('ed25519');
const employerSigner = Signer.create({
privateKeyPEM: employerKeys.privateKey,
publicKeyPEM: employerKeys.publicKey,
keyType: 'ed25519',
metadata: { company: 'Tech Corp Inc.' }
});
const employerDid = createDIDKey(employerKeys.publicKey, 'ed25519');
// Issue employment credential
const employmentCredential = await issueVC(
employerSigner,
{
employee: {
id: 'did:key:employee456',
name: 'Bob Smith'
},
employer: {
name: 'Tech Corp Inc.',
id: employerDid
},
position: 'Senior Software Engineer',
startDate: '2022-03-01',
endDate: '2024-07-31',
salary: '$95,000',
status: 'Good Standing'
},
'EmploymentCredential',
employerDid
);
console.log('💼 Employment verified:', employmentCredential.data?.id);
```
### Multi-Party Verification Workflow
```typescript
// University verifies the degree credential
const degreeVerification = await verifyVC(universitySigner, degreeCredential.data);
// Employer verifies the employment credential
const employmentVerification = await verifyVC(employerSigner, employmentCredential.data);
// Third party (bank) verifies both credentials
const bankKeys = generateKeyPair('ed25519');
const bankSigner = Signer.create({
privateKeyPEM: bankKeys.privateKey,
publicKeyPEM: bankKeys.publicKey,
keyType: 'ed25519',
metadata: { institution: 'Verification Bank' }
});
// Bank can verify without being the issuer
const bankVerifyDegree = await verifyVC(bankSigner, degreeCredential.data);
const bankVerifyEmployment = await verifyVC(bankSigner, employmentCredential.data);
console.log('🏦 Bank verification results:');
console.log(' Degree valid:', bankVerifyDegree.success);
console.log(' Employment valid:', bankVerifyEmployment.success);
```
## Core Functions
Battle-tested functions for credential operations:
### Credential Issuance
```typescript
import { issueVC, createCredentialSubject } from '/credential';
// Issue a credential
const result = await issueVC(
signer, // Signer from @synet/keys
credentialSubject, // Your credential data
'CredentialType', // W3C credential type
issuerDid // DID of the issuer
);
// Create properly structured credential subject
const subject = createCredentialSubject({
holder: { id: 'did:key:alice', name: 'Alice' },
university: 'Tech University',
degree: 'Computer Science'
});
```
### Credential Verification
```typescript
import { verifyVC, validateCredentialStructure } from '/credential';
// Verify credential signature and integrity
const verification = await verifyVC(signer, credential);
if (verification.success) {
console.log('✅ Valid credential');
console.log('Issuer:', verification.data.issuer);
console.log('Subject:', verification.data.subject);
} else {
console.error('❌ Invalid:', verification.error);
}
// Validate W3C structure only (no signature verification)
const structureValid = validateCredentialStructure(credential);
```
### Credential Utilities
```typescript
import {
extractCredentialData,
getCredentialType,
isExpired,
createCredentialId
} from '/credential';
// Extract data from credential
const data = extractCredentialData(credential);
// Get credential type
const type = getCredentialType(credential); // 'UniversityDegreeCredential'
// Check if expired
const expired = isExpired(credential);
// Generate unique credential ID
const credentialId = createCredentialId('IdentityCredential');
// 2. Create proper DID from the key
const issuerDid = createDIDKey(keyPair.publicKey, 'Ed25519');
// 3. Create Key unit for signing
const issuerKey = Key.fromKeyPair('ed25519', keyPair.publicKey, keyPair.privateKey, {
name: 'issuer-key'
});
// 4. Issue credential with proper DID
const result = await issueVC(
## Credential (Advanced)
```
For complex systems, use the intelligent Credential that learns capabilities:
### Basic Credential Usage
```typescript
import { Credential } from '/credential';
import { generateKeyPair, Signer } from '/keys';
// 1. Create credential unit
const credential = Credential.create();
// 2. Create signer
const keyPair = generateKeyPair('ed25519');
const signer = Signer.create(keyPair.privateKey, keyPair.publicKey, 'ed25519');
// 3. Unit learns from signer (no private key transfer!)
const key = signer.createKey();
const capabilities = key.teach();
await credential.learn([capabilities]);
// 4. Now the unit can operate autonomously
const credentialResult = await credential.issueCredential(
credentialSubject,
'CertificationCredential',
issuerDid
);
```
### Credential API
```typescript
interface CredentialConfig {
metadata?: Record<string, unknown>;
}
class Credential {
// Creation
static create(config?: CredentialConfig): Credential
// Learning capabilities
learn(capabilities: TeachingCapabilities[]): Promise<boolean>
// Operations (after learning)
async issueCredential(subject: any, type: string, issuerDid: string): Promise<SynetVerifiableCredential | null>
async verifyCredential(credential: any): Promise<VerificationResult | null>
validateStructure(credential: any): boolean
// Unit interface
async execute(command: string, params?: any): Promise<Result<any>>
capabilities(): string[]
whoami(): string
}
```
## API Reference
### Core Functions
```typescript
// Issue verifiable credential
issueVC(
signer: Signer, // From @synet/keys
subject: BaseCredentialSubject,
type: string,
issuerDid: string
): Promise<Result<SynetVerifiableCredential>>
// Verify verifiable credential
verifyVC(
signer: Signer, // From @synet/keys
credential: SynetVerifiableCredential
): Promise<Result<VerificationResult>>
// Validate credential structure
validateCredentialStructure(
credential: any
): boolean
// Create credential subject
createCredentialSubject(
data: Record<string, any>
): BaseCredentialSubject
```
### Types
```typescript
interface SynetVerifiableCredential<T = BaseCredentialSubject> {
'': string[];
id: string;
type: string[];
issuer: { id: string };
issuanceDate: string;
expirationDate?: string;
credentialSubject: T;
proof: {
type: 'JwtProof2020';
jwt: string;
verificationMethod: string;
};
}
interface VerificationResult {
verified: boolean;
issuer: string;
subject: string;
issuanceDate: string;
expirationDate?: string;
}
interface BaseCredentialSubject {
holder: {
id: string;
name?: string;
};
[key: string]: any;
}
```
## Installation
```bash
npm install /credential @synet/keys /did
```
The package requires `/keys` for cryptographic operations and `@synet/did` for proper DID creation.
## Error Handling
All functions return `Result<T>` for type-safe error handling:
```typescript
import { issueVC } from '/credential';
const result = await issueVC(signer, subject, 'TestCredential', issuerDid);
if (result.isSuccess()) {
const credential = result.getData();
console.log('✅ Credential issued:', credential.id);
} else {
const error = result.getError();
console.error('❌ Failed to issue:', error.message);
}
```
## Common Patterns
### Multiple Credentials
```typescript
// Issue multiple credentials for a single holder
const credentials = await Promise.all([
issueVC(signer, degreeSubject, 'UniversityDegreeCredential', universityDid),
issueVC(signer, employmentSubject, 'EmploymentCredential', employerDid),
issueVC(signer, certificationSubject, 'CertificationCredential', certifierDid)
]);
console.log(`Issued ${credentials.length} credentials`);
```
### Credential Chain Verification
```typescript
// Verify a chain of related credentials
const verifyCredentialChain = async (credentials: SynetVerifiableCredential[]) => {
const results = await Promise.all(
credentials.map(cred => verifyVC(signer, cred))
);
return results.every(result => result.isSuccess());
};
```
### Custom Credential Types
```typescript
interface SkillCredentialSubject extends BaseCredentialSubject {
skill: string;
level: 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert';
endorsedBy: string;
validUntil?: string;
}
const skillCredential = await issueVC(
signer,
{
holder: { id: 'did:key:dev123', name: 'Jane Developer' },
skill: 'TypeScript',
level: 'Expert',
endorsedBy: 'Senior Tech Lead'
} as SkillCredentialSubject,
'SkillCredential',
organizationDid
);
```
## Testing
The architecture makes testing simple with predictable pure functions:
```typescript
import { issueVC, verifyVC } from '/credential';
import { generateKeyPair, Signer } from '/keys';
describe('Credential Tests', () => {
let signer: Signer;
let issuerDid: string;
beforeEach(() => {
const keyPair = generateKeyPair('ed25519');
signer = Signer.create({
privateKeyPEM: keyPair.privateKey,
publicKeyPEM: keyPair.publicKey,
keyType: 'ed25519'
});
issuerDid = `did:key:${keyPair.publicKey}`;
});
test('should issue and verify credential', async () => {
const subject = {
holder: { id: 'did:key:test', name: 'Test User' },
test: true
};
const issueResult = await issueVC(signer, subject, 'TestCredential', issuerDid);
expect(issueResult.isSuccess()).toBe(true);
const credential = issueResult.getData();
const verifyResult = await verifyVC(signer, credential);
expect(verifyResult.isSuccess()).toBe(true);
});
});
```
---
Built on [/keys](../keys) • Part of the [Synet](/) ecosystem
// Create proper standards-compliant DID from Key unit
const issuerDid = createDIDKey(key.publicKeyHex, 'Ed25519');
```
### Credential Operations
```typescript
import { issueVC, verifyVC } from '/credential';
// Issue a credential
const result = await issueVC(
key, // Key unit from @synet/keys
subject,
'IdentityCredential',
issuerDid,
options
);
// Verify a credential
const verification = await verifyVC(key, credential);
```
### Utility Functions
```typescript
import {
generateCredentialId,
createCredentialPayload,
validateCredential,
CredentialUtils
} from '/credential';
// Generate credential ID
const id = generateCredentialId('IdentityCredential');
// Create credential payload
const payload = createCredentialPayload(subject, 'IdentityCredential', issuerDid);
// Validate credential structure
const validation = validateCredential(credential);
// Utility operations
const isExpired = CredentialUtils.isExpired(credential);
const age = CredentialUtils.getCredentialAge(credential);
```
## CredentialKey Interface
### Version Conflicts
When two packages depend on different versions of the same library, you can get **"Type incompatibility"** errors even when the functionality is identical. This is especially problematic with key types:
```typescript
// This fails if @synet/keys v1.0.0 and v1.0.1 are used together
import { Key as KeyV1 } from '/keys@1.0.0';
import { Key as KeyV2 } from '/keys@1.0.1';
// Type error: KeyV1 is not assignable to KeyV2
function useKey(key: KeyV2) { ... }
useKey(keyV1Instance); // ❌ Type error!
```
### The Solution: CredentialKey Interface
`/credential` defines its own `CredentialKey` interface that acts as a **dependency moat**:
```typescript
// In @synet/credential/src/key.ts
export interface CredentialKey {
readonly id: string;
readonly publicKeyHex: string;
readonly type: string;
readonly meta: Record<string, unknown>;
canSign(): boolean;
getPublicKey(): string;
sign(data: string): Promise<string>;
verify(data: string, signature: string): Promise<boolean>;
toJSON(): object;
toVerificationMethod(controller: string): object;
}
```
### How It Works
1. **Credential functions only use the interface**:
```typescript
// Uses interface, not concrete type
export async function issueVC(
key: CredentialKey, // ← Interface, not @synet/keys Key
subject: BaseCredentialSubject,
type: string,
issuerDid: string
): Promise<Result<W3CVerifiableCredential>>
```
2. **Any key provider can implement the interface**:
```typescript
// @synet/keys Key implements CredentialKey
import { Key } from '/keys';
const key = await Key.generate();
await issueVC(key, subject, type, issuerDid); // ✅ Works!
// Custom key provider
class MyCustomKey implements CredentialKey {
// ... implement interface methods
}
const customKey = new MyCustomKey();
await issueVC(customKey, subject, type, issuerDid); // ✅ Also works!
```
3. **Version conflicts are prevented**:
```typescript
// Different versions of @synet/keys both implement CredentialKey
import { Key as KeyV1 } from '/keys@1.0.0';
import { Key as KeyV2 } from '/keys@1.0.1';
const keyV1 = await KeyV1.generate();
const keyV2 = await KeyV2.generate();
// Both work with credential functions
await issueVC(keyV1, subject, type, issuerDid); // ✅ Works!
await issueVC(keyV2, subject, type, issuerDid); // ✅ Works!
```
### Example: Multiple Key Providers
```typescript
import { Key as SynetKey } from '/keys';
import { VaultKey } from '/vault-keys';
import { HSMKey } from '/hsm-keys';
import { issueVC } from '/credential';
// All implement CredentialKey interface
const synetKey = await SynetKey.generate();
const vaultKey = new VaultKey(vaultConfig);
const hsmKey = new HSMKey(hsmConfig);
// All work with credential functions
await issueVC(synetKey, subject, type, issuerDid); // ✅
await issueVC(vaultKey, subject, type, issuerDid); // ✅
await issueVC(hsmKey, subject, type, issuerDid); // ✅
```
This pattern ensures that `/credential` remains stable and compatible while allowing maximum flexibility in key management solutions.