@sd-jwt/sd-jwt-vc
Version:
sd-jwt draft 7 implementation in typescript
281 lines (242 loc) • 7.09 kB
text/typescript
import Crypto from 'node:crypto';
import { SDJwtVcInstance } from '../src/index';
import type {
DisclosureFrame,
PresentationFrame,
Signer,
Verifier,
} from '@sd-jwt/types';
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, test } from 'vitest';
import { digest, generateSalt } from '@sd-jwt/crypto-nodejs';
const createSignerVerifier = () => {
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
const signer: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const verifier: Verifier = async (data: string, sig: string) => {
return Crypto.verify(
null,
Buffer.from(data),
publicKey,
Buffer.from(sig, 'base64url'),
);
};
return { signer, verifier };
};
const iss = 'ExampleIssuer';
const vct = 'ExampleCredentials';
const iat = Math.floor(Date.now() / 1000); // current time in seconds
describe('App', () => {
test('Example', async () => {
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtVcInstance({
signer,
signAlg: 'EdDSA',
verifier,
hasher: digest,
hashAlg: 'sha-256',
saltGenerator: generateSalt,
});
const claims = {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
id: '1234',
data: {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
list: [{ r: '1' }, 'b', 'c'],
},
data2: {
hi: 'bye',
},
iat,
iss,
vct,
};
const disclosureFrame: DisclosureFrame<typeof claims> = {
_sd: ['firstname', 'id', 'data2'],
data: {
_sd: ['list'],
_sd_decoy: 2,
list: {
_sd: [0, 2],
_sd_decoy: 1,
0: {
_sd: ['r'],
},
},
},
data2: {
_sd: ['hi'],
},
};
const encodedSdjwt = await sdjwt.issue(claims, disclosureFrame);
expect(encodedSdjwt).toBeDefined();
const validated = await sdjwt.validate(encodedSdjwt);
expect(validated).toBeDefined();
const decoded = await sdjwt.decode(encodedSdjwt);
const keys = await decoded.keys(digest);
expect(keys).toEqual([
'data',
'data.firstname',
'data.lastname',
'data.list',
'data.list.0',
'data.list.0.r',
'data.list.1',
'data.list.2',
'data.ssn',
'data2',
'data2.hi',
'firstname',
'iat',
'id',
'iss',
'lastname',
'ssn',
'vct',
]);
const payloads = await decoded.getClaims(digest);
expect(payloads).toEqual(claims);
const presentableKeys = await decoded.presentableKeys(digest);
expect(presentableKeys).toEqual([
'data.list',
'data.list.0',
'data.list.0.r',
'data.list.2',
'data2',
'data2.hi',
'firstname',
'id',
]);
const presentationFrame = {
firstname: true,
id: true,
};
const presentedSDJwt = await sdjwt.present<typeof claims>(
encodedSdjwt,
presentationFrame,
);
expect(presentedSDJwt).toBeDefined();
const presentationClaims = await sdjwt.getClaims(presentedSDJwt);
expect(presentationClaims).toBeDefined();
expect(presentationClaims).toEqual({
lastname: 'Doe',
ssn: '123-45-6789',
data: { firstname: 'John', lastname: 'Doe', ssn: '123-45-6789' },
id: '1234',
firstname: 'John',
iat,
iss,
vct,
});
const requiredClaimKeys = ['firstname', 'id', 'data.ssn'];
const verified = await sdjwt.verify(encodedSdjwt, requiredClaimKeys);
expect(verified).toBeDefined();
});
test('From JSON (complex)', async () => {
await JSONtest('./complex.json');
});
test('From JSON (array_data_types)', async () => {
await JSONtest('./array_data_types.json');
});
test('From JSON (array_full_sd)', async () => {
await JSONtest('./array_full_sd.json');
});
test('From JSON (array_in_sd)', async () => {
await JSONtest('./array_in_sd.json');
});
test('From JSON (array_recursive_sd_some_disclosed)', async () => {
await JSONtest('./array_recursive_sd_some_disclosed.json');
});
test('From JSON (header_mod)', async () => {
await JSONtest('./header_mod.json');
});
test('From JSON (json_serialization)', async () => {
await JSONtest('./json_serialization.json');
});
test('From JSON (key_binding)', async () => {
await JSONtest('./key_binding.json');
});
test('From JSON (no_sd)', async () => {
await JSONtest('./no_sd.json');
});
test('From JSON (object_data_types)', async () => {
await JSONtest('./object_data_types.json');
});
test('From JSON (recursions)', async () => {
await JSONtest('./recursions.json');
});
test('From JSON (array_recursive_sd)', async () => {
await JSONtest('./array_recursive_sd.json');
});
test('From JSON (array_of_scalars)', async () => {
await JSONtest('./array_of_scalars.json');
});
test('From JSON (array_of_objects)', async () => {
await JSONtest('./array_of_objects.json');
});
test('From JSON (array_of_nulls)', async () => {
await JSONtest('./array_of_nulls.json');
});
test('From JSON (array_nested_in_plain)', async () => {
await JSONtest('./array_nested_in_plain.json');
});
});
async function JSONtest(filename: string) {
const test = loadTestJsonFile(filename);
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtVcInstance({
signer,
signAlg: 'EdDSA',
verifier,
hasher: digest,
hashAlg: 'sha-256',
saltGenerator: generateSalt,
});
const payload = { iss, vct, iat, ...test.claims };
const encodedSdjwt = await sdjwt.issue(payload, test.disclosureFrame);
expect(encodedSdjwt).toBeDefined();
const validated = await sdjwt.validate(encodedSdjwt);
expect(validated).toBeDefined();
expect(validated).toStrictEqual({
header: { alg: 'EdDSA', typ: 'dc+sd-jwt' },
payload,
});
const presentedSDJwt = await sdjwt.present<typeof claims>(
encodedSdjwt,
test.presentationFrames,
);
expect(presentedSDJwt).toBeDefined();
const presentationClaims = await sdjwt.getClaims(presentedSDJwt);
expect(presentationClaims).toEqual({
...test.presenatedClaims,
iat,
iss,
vct,
});
const verified = await sdjwt.verify(encodedSdjwt, test.requiredClaimKeys);
expect(verified).toBeDefined();
expect(verified).toStrictEqual({
header: { alg: 'EdDSA', typ: 'dc+sd-jwt' },
kb: undefined,
payload,
});
}
type TestJson = {
claims: object;
disclosureFrame: DisclosureFrame<object>;
presentationFrames: PresentationFrame<object>;
presenatedClaims: object;
requiredClaimKeys: string[];
};
function loadTestJsonFile(filename: string) {
const filepath = path.join(__dirname, filename);
const fileContents = fs.readFileSync(filepath, 'utf8');
return JSON.parse(fileContents) as TestJson;
}