@synet/credential
Version:
VC Credentials - Simple, Robust, Unit-based Verifiable Credentials service
339 lines (283 loc) • 12.4 kB
text/typescript
/**
* Real-world data tests for Credential using actual Veramo data
*
* These tests use real credential data from Veramo implementation to validate that:
* 1. We can recreate signers from existing private keys
* 2. We can verify existing credentials
* 3. We can issue new credentials that match the format
* 4. "Same data in, same data out" principle works
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { Signer, hexToPem } from '@synet/keys';
import { Credential } from '../src/credential';
import type { BaseCredentialSubject, W3CVerifiableCredential } from '../src/types-base';
import * as crypto from 'node:crypto';
// Import real-world test data
import realWorldData from './example-data/0en.json';
/**
* Convert Ed25519 private key from hex to PEM format
*/
function hexPrivateKeyToPem(hexKey: string): string {
// Ed25519 private keys are 64 bytes (32 private + 32 public)
// Extract the private key part (first 32 bytes)
const privateKeyBytes = Buffer.from(hexKey.substring(0, 64), 'hex');
// Create PKCS8 DER format for Ed25519 private key
const pkcs8Header = Buffer.from([
0x30, 0x2e, // SEQUENCE, 46 bytes
0x02, 0x01, 0x00, // INTEGER version 0
0x30, 0x05, // SEQUENCE, 5 bytes
0x06, 0x03, 0x2b, 0x65, 0x70, // OID for Ed25519
0x04, 0x22, // OCTET STRING, 34 bytes
0x04, 0x20 // OCTET STRING, 32 bytes (the actual private key)
]);
const derKey = Buffer.concat([pkcs8Header, privateKeyBytes]);
// Create private key object and export as PEM
const privateKeyObj = crypto.createPrivateKey({
key: derKey,
format: 'der',
type: 'pkcs8'
});
return privateKeyObj.export({
type: 'pkcs8',
format: 'pem'
}).toString();
}
describe('Credential Real-World Data Tests', () => {
let signer: Signer;
let key: ReturnType<typeof signer.createKey>;
let credential: Credential;
// Extract data from the real-world example
const testData = {
did: realWorldData.identity.did,
privateKeyHex: realWorldData.identity.privateKeyHex,
publicKeyHex: realWorldData.identity.publicKeyHex,
existingCredential: realWorldData.identity.credential as W3CVerifiableCredential,
kid: realWorldData.identity.kid
};
beforeEach(async () => {
// Convert hex keys to PEM format
const privateKeyPEM = hexPrivateKeyToPem(testData.privateKeyHex);
const publicKeyPEM = hexToPem(testData.publicKeyHex, 'ed25519');
if (!privateKeyPEM || !publicKeyPEM) {
throw new Error('Failed to convert hex keys to PEM format');
}
// Create signer from existing private key material
const createdSigner = Signer.create({privateKeyPEM, publicKeyPEM, keyType:'ed25519', metadata: {
name: '0en-test-signer'
}});
if (!createdSigner) {
throw new Error('Failed to create signer from existing key material');
}
signer = createdSigner;
key = signer.createKey();
if (!key) {
throw new Error('Failed to create key from signer');
}
credential = Credential.create();
// Learn crypto capabilities
credential.learn([key.teach()]);
});
describe('Verification of Existing Real Credentials', () => {
it('should verify the existing Veramo credential', async () => {
const result = await credential.verifyCredential(testData.existingCredential);
if (result.isFailure) {
console.log('Verification failed with error:', result.errorMessage);
}
expect(result.isSuccess).toBe(true);
if (result.isSuccess) {
const verificationResult = result.value;
expect(verificationResult.verified).toBe(true);
expect(verificationResult.issuer).toBe(testData.did);
expect(verificationResult.subject).toBe(testData.did);
expect(verificationResult.issuanceDate).toBe('2025-07-09T11:43:25.000Z');
}
});
it('should validate the structure of existing credential', async () => {
const result = await credential.validateStructure(testData.existingCredential);
expect(result.valid).toBe(true);
expect(result.reason).toBeUndefined();
});
it('should decode and verify the JWT from existing credential', async () => {
const { proof } = testData.existingCredential;
expect(proof.type).toBe('JwtProof2020');
expect(proof.jwt).toBeDefined();
if (proof.jwt) {
// The JWT should be properly formatted
const jwtParts = proof.jwt.split('.');
expect(jwtParts).toHaveLength(3);
// Verify using our unit
const result = await credential.verifyCredential(testData.existingCredential);
expect(result.isSuccess).toBe(true);
if (result.isSuccess) {
expect(result.value.verified).toBe(true);
}
}
});
});
describe('Same Data In, Same Data Out', () => {
it('should recreate identical credential with same input data', async () => {
// Extract the original credential data
const originalSubject = testData.existingCredential.credentialSubject;
const originalIssuer = testData.existingCredential.issuer.id;
const originalType = testData.existingCredential.type.filter(t => t !== 'VerifiableCredential');
const originalIssuanceDate = testData.existingCredential.issuanceDate;
// Issue new credential with same data
const newCredentialResult = await credential.issueCredential(
originalSubject,
originalType,
originalIssuer,
{
vcId: testData.existingCredential.id,
issuanceDate: originalIssuanceDate
}
);
expect(newCredentialResult.isSuccess).toBe(true);
if (newCredentialResult.isSuccess) {
const newCredential = newCredentialResult.value;
// Should have identical structure (except proof)
expect(newCredential.id).toBe(testData.existingCredential.id);
expect(newCredential.type).toEqual(testData.existingCredential.type);
expect(newCredential.issuer).toEqual(testData.existingCredential.issuer);
expect(newCredential.issuanceDate).toBe(testData.existingCredential.issuanceDate);
expect(newCredential.credentialSubject).toEqual(testData.existingCredential.credentialSubject);
expect(newCredential['@context']).toEqual(testData.existingCredential['@context']);
// Proof will be different (new JWT) but should be valid
expect(newCredential.proof.type).toBe('JwtProof2020');
expect(newCredential.proof.jwt).toBeDefined();
expect(newCredential.proof.jwt).not.toBe(testData.existingCredential.proof.jwt); // Different signature
// But the new credential should verify
const verifyResult = await credential.verifyCredential(newCredential);
expect(verifyResult.isSuccess).toBe(true);
if (verifyResult.isSuccess) {
expect(verifyResult.value.verified).toBe(true);
}
}
});
it('should create new credential with same issuer identity', async () => {
// Create a new credential for the same identity
const subject: BaseCredentialSubject = {
holder: {
id: testData.did,
name: '0en'
},
issuedBy: {
id: testData.did,
name: '0en'
},
// Add some new data
testField: 'new credential data'
};
const newCredentialResult = await credential.issueCredential(
subject,
'IdentityCredential',
testData.did
);
expect(newCredentialResult.isSuccess).toBe(true);
if (newCredentialResult.isSuccess) {
const newCredential = newCredentialResult.value;
expect(newCredential.issuer.id).toBe(testData.did);
expect(newCredential.credentialSubject.holder.id).toBe(testData.did);
expect(newCredential.credentialSubject.holder.name).toBe('0en');
// Should verify with same key
const verifyResult = await credential.verifyCredential(newCredential);
expect(verifyResult.isSuccess).toBe(true);
if (verifyResult.isSuccess) {
expect(verifyResult.value.verified).toBe(true);
expect(verifyResult.value.issuer).toBe(testData.did);
}
}
});
});
describe('Key Material Validation', () => {
it('should work with the exact key material from Veramo', async () => {
// Verify we're using the right key material
expect(testData.privateKeyHex).toBe('ce4ad6a60ee4331e711033a500bf935b21ef209b2501bd63a1dbb2a85e2648f8878403abd8b32881ce5ceb125ed97563ad483ea5f43d71b012184657ce07d8cb');
expect(testData.publicKeyHex).toBe('878403abd8b32881ce5ceb125ed97563ad483ea5f43d71b012184657ce07d8cb');
expect(testData.did).toBe('did:key:z6MkoaFqc5NmLYjYC7RACiYcC9g2C2PA3ss9e4jeCwbNKmvz');
// The signer should be created from this material
const publicKey = await signer.getPublicKey();
expect(publicKey).toBeDefined();
// Should be able to sign and verify
const testMessage = 'test signing with real key material';
const signature = await signer.sign(testMessage);
expect(signature).toBeDefined();
const isValid = await signer.verify(testMessage, signature);
expect(isValid).toBe(true);
});
it('should fail verification with wrong key material', async () => {
// Create a different signer
const wrongSigner = Signer.generate('ed25519', { metadata: { name: 'wrong-signer' } });
if (!wrongSigner) {
throw new Error('Failed to create wrong signer');
}
const wrongKey = wrongSigner.createKey();
if (!wrongKey) {
throw new Error('Failed to create wrong key');
}
const wrongCredential = Credential.create();
wrongCredential.learn([wrongKey.teach()]);
// Try to verify the existing credential with wrong key
const result = await wrongCredential.verifyCredential(testData.existingCredential);
expect(result.isFailure).toBe(true);
expect(result.errorMessage).toContain('Invalid signature');
});
});
describe('Format Compatibility', () => {
it('should produce JWT proofs compatible with Veramo format', async () => {
const subject: BaseCredentialSubject = {
holder: {
id: testData.did,
name: '0en'
},
issuedBy: {
id: testData.did,
name: '0en'
}
};
const newCredentialResult = await credential.issueCredential(
subject,
'IdentityCredential',
testData.did
);
expect(newCredentialResult.isSuccess).toBe(true);
if (newCredentialResult.isSuccess) {
const newCredential = newCredentialResult.value;
// Should have same JWT structure as Veramo
expect(newCredential.proof.type).toBe('JwtProof2020');
expect(newCredential.proof.jwt).toBeDefined();
if (newCredential.proof.jwt) {
const jwtParts = newCredential.proof.jwt.split('.');
expect(jwtParts).toHaveLength(3);
// Decode header
const header = JSON.parse(atob(jwtParts[0].replace(/-/g, '+').replace(/_/g, '/')));
expect(header.alg).toBe('EdDSA');
expect(header.typ).toBe('JWT');
// Decode payload
const payload = JSON.parse(atob(jwtParts[1].replace(/-/g, '+').replace(/_/g, '/')));
expect(payload.vc).toBeDefined();
expect(payload.iss).toBe(testData.did);
expect(payload.jti).toBe(newCredential.id);
}
}
});
it('should maintain W3C context and type arrays', async () => {
const subject: BaseCredentialSubject = {
holder: {
id: testData.did,
name: '0en'
}
};
const newCredentialResult = await credential.issueCredential(
subject,
['IdentityCredential', 'TestCredential'],
testData.did
);
expect(newCredentialResult.isSuccess).toBe(true);
if (newCredentialResult.isSuccess) {
const newCredential = newCredentialResult.value;
expect(newCredential['@context']).toEqual(['https://www.w3.org/2018/credentials/v1']);
expect(newCredential.type).toEqual(['VerifiableCredential', 'IdentityCredential', 'TestCredential']);
}
});
});
});