Skip to content

Commit

Permalink
Merge pull request #422 from docknetwork/feat/ed25519-2020-support
Browse files Browse the repository at this point in the history
Support Ed255192020 keys, open badges example
  • Loading branch information
cykoder authored Apr 25, 2024
2 parents 9252b69 + cbd5f40 commit 7718584
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 3 deletions.
73 changes: 73 additions & 0 deletions example/open-badges.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import VerifiableCredential from '../src/verifiable-credential';
import { DIDKeyResolver } from '../src/resolver';

// Sample credential data from https://gist.githubusercontent.com/ottonomy/6f72f5055220cfa8c6926e1a753f1870/raw/e7882e4a6eebb503359cce4bdc8978331d47544c/asu-tln-unconference-example-credential.json
const credentialJSON = {
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"type": [
"VerifiableCredential",
"OpenBadgeCredential"
],
"issuer": {
"type": "Profile",
"id": "did:key:z6MkoowWdLogChc6mRp18YcKBd2yYTNnQLeHdiT73wjL1h6z",
"name": "Trusted Learner Network (TLN) Unconference Issuer",
"url": "https://tln.asu.edu/",
"image": {
"id": "https://plugfest3-assets-20230928.s3.amazonaws.com/TLN+Gold+Circle.png",
"type": "Image"
}
},
"issuanceDate": "2024-04-04T16:43:31.485Z",
"name": "2024 TLN Unconference Change Agent",
"credentialSubject": {
"type": "AchievementSubject",
"id": "did:key:7af28a8b2b9684073a0884aacd8c31eb5908baf4a1ba7e2ca60582bf585c68ad",
"achievement": {
"id": "https://tln.asu.edu/achievement/369435906932948",
"type": "Achievement",
"name": "2024 TLN Unconference Change Agent",
"description": "This credential certifies attendance, participation, and knowledge-sharing at the 2024 Trusted Learner Network (TLN) Unconference.",
"criteria": {
"type": "Criteria",
"narrative": "* Demonstrates initiative and passion for digital credentialing\n* Shares knowledge, skills and experience to broaden and deepen the community's collective understanding and competency\n* Engages in complex problems by collaborating with others\n* Creates connections and builds coalition to advance the ecosystem"
}
}
},
"id": "https://tln.asu.edu/achievement/369435906932948",
"proof": {
"type": "Ed25519Signature2020",
"created": "2024-04-04T16:43:31Z",
"verificationMethod": "did:key:z6MkoowWdLogChc6mRp18YcKBd2yYTNnQLeHdiT73wjL1h6z#z6MkoowWdLogChc6mRp18YcKBd2yYTNnQLeHdiT73wjL1h6z",
"proofPurpose": "assertionMethod",
"proofValue": "z23JQwSmJKnWXw1HWDMBv1yoZDVyfUsRWihQFsrSLpb8cENqbuqpdnaSY72VmCkY3WQ4GovpNRZPNLRaatXeDJE8G"
}
};

const resolver = new DIDKeyResolver();

async function main() {
// Incrementally build a verifiable credential
const credential = VerifiableCredential.fromJSON(credentialJSON);

// Verify the credential
const verifyResult = await credential.verify({
resolver,
compactProof: true,
});
if (verifyResult.verified) {
console.log('Credential has been verified! Result:', verifyResult);
} else {
console.error('Credential could not be verified!. Got error', verifyResult.error);
process.exit(1);
}

// Exit
process.exit(0);
}

main();
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"did-resolver-example": "npx babel-node example/resolver.js",
"revocation-example": "npx babel-node example/revocation.js",
"vcdm-example": "npx babel-node example/vcdm.js",
"open-badges-example": "npx babel-node example/open-badges.js",
"standard-schemas-example": "npx babel-node example/standard_schemas.js",
"schema-example": "npx babel-node example/schema.js",
"schema-validation-example": "npx babel-node example/schema-validation.js",
Expand Down
2 changes: 2 additions & 0 deletions src/utils/vc/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { ensureValidDatetime } from '../type-helpers';
import {
EcdsaSecp256k1Signature2019,
Ed25519Signature2018,
Ed25519Signature2020,
Sr25519Signature2020,
Bls12381PSSignatureDock2023,
Bls12381PSSignatureProofDock2023,
Expand Down Expand Up @@ -368,6 +369,7 @@ export async function verifyCredential(
};
const fullSuite = [
new Ed25519Signature2018(),
new Ed25519Signature2020(),
new EcdsaSecp256k1Signature2019(),
new Sr25519Signature2020(),
new JsonWebSignature2020(),
Expand Down
41 changes: 41 additions & 0 deletions src/utils/vc/crypto/Ed25519Signature2020.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Ed255192020SigName, Ed255192020VerKeyName } from './constants';
import Ed25519VerificationKey2020 from './Ed25519VerificationKey2020';
import CustomLinkedDataSignature from './common/CustomLinkedDataSignature';

const SUITE_CONTEXT_URL = 'https://w3id.org/security/suites/ed25519-2020/v1';

export default class Ed25519Signature2020 extends CustomLinkedDataSignature {
/**
* Creates a new Ed25519Signature2020 instance
* @constructor
* @param {object} config - Configuration options
*/
constructor({
keypair, verificationMethod, verifier, signer,
} = {}) {
super({
type: Ed255192020SigName,
LDKeyClass: Ed25519VerificationKey2020,
contextUrl: SUITE_CONTEXT_URL,
alg: 'EdDSA',
signer: signer || Ed25519Signature2020.signerFactory(keypair, verificationMethod),
verifier,
});
this.requiredKeyType = Ed255192020VerKeyName;
}

/**
* Generate object with `sign` method
* @param keypair
* @param verificationMethod
* @returns {object}
*/
static signerFactory(keypair, verificationMethod) {
return {
id: verificationMethod,
async sign({ data }) {
return keypair.sign(data);
},
};
}
}
31 changes: 31 additions & 0 deletions src/utils/vc/crypto/Ed25519VerificationKey2020.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import b58 from 'bs58';
import * as base64 from '@juanelas/base64';
import { Ed25519VerKeyName, Ed255192020VerKeyName } from './constants';
import Ed25519VerificationKey2018 from './Ed25519VerificationKey2018';

export default class Ed25519VerificationKey2020 extends Ed25519VerificationKey2018 {
/**
* Construct the public key object from the verification method
* @param verificationMethod
* @returns {Ed25519VerificationKey2020}
*/
static from(verificationMethod) {
const isEd25519Type = verificationMethod.type.indexOf(Ed255192020VerKeyName) !== -1
|| verificationMethod.type.indexOf(Ed25519VerKeyName) !== -1;
if (!verificationMethod.type || !isEd25519Type) {
throw new Error(`verification method should have type ${Ed255192020VerKeyName} - got: ${verificationMethod.type}`);
}

if (verificationMethod.publicKeyBase58) {
return new this(b58.decode(verificationMethod.publicKeyBase58));
}

if (verificationMethod.publicKeyBase64) {
return new this(base64.decode(verificationMethod.publicKeyBase64));
}

throw new Error(`Unsupported signature encoding for ${Ed255192020VerKeyName}`);
}

// NOTE: Ed255192020 has the same cryptography as Ed255192018, so we inherit the verifier methods
}
2 changes: 2 additions & 0 deletions src/utils/vc/crypto/constants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export const EcdsaSecp256k1VerKeyName = 'EcdsaSecp256k1VerificationKey2019';
export const EcdsaSecp256k1SigName = 'EcdsaSecp256k1Signature2019';
export const Ed25519VerKeyName = 'Ed25519VerificationKey2018';
export const Ed255192020VerKeyName = 'Ed25519VerificationKey2020';
export const Ed25519SigName = 'Ed25519Signature2018';
export const Ed255192020SigName = 'Ed25519Signature2020';
export const Sr25519VerKeyName = 'Sr25519VerificationKey2020';
export const Sr25519SigName = 'Sr25519Signature2020';
export const Bls12381BBSSigDockSigName = 'Bls12381BBS+SignatureDock2022';
Expand Down
6 changes: 6 additions & 0 deletions src/utils/vc/custom_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
EcdsaSecp256k1SigName,
Ed25519VerKeyName,
Ed25519SigName,
Ed255192020VerKeyName,
Ed255192020SigName,
Sr25519VerKeyName,
Sr25519SigName,
Bls12381BBSDockVerKeyName,
Expand All @@ -22,6 +24,7 @@ import EcdsaSecp256k1VerificationKey2019 from './crypto/EcdsaSecp256k1Verificati
import EcdsaSecp256k1Signature2019 from './crypto/EcdsaSecp256k1Signature2019';
import Ed25519VerificationKey2018 from './crypto/Ed25519VerificationKey2018';
import Ed25519Signature2018 from './crypto/Ed25519Signature2018';
import Ed25519Signature2020 from './crypto/Ed25519Signature2020';
import Sr25519VerificationKey2020 from './crypto/Sr25519VerificationKey2020';
import Sr25519Signature2020 from './crypto/Sr25519Signature2020';
import Bls12381BBSSignatureDock2022 from './crypto/Bls12381BBSSignatureDock2022';
Expand All @@ -37,12 +40,15 @@ export {
EcdsaSecp256k1SigName,
Ed25519VerKeyName,
Ed25519SigName,
Ed255192020VerKeyName,
Ed255192020SigName,
Sr25519VerKeyName,
Sr25519SigName,
EcdsaSecp256k1VerificationKey2019,
EcdsaSecp256k1Signature2019,
Ed25519VerificationKey2018,
Ed25519Signature2018,
Ed25519Signature2020,
Sr25519VerificationKey2020,
Sr25519Signature2020,
Bls12381BBSSignatureDock2022,
Expand Down
6 changes: 5 additions & 1 deletion src/utils/vc/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
Bls12381PSSignatureDock2023,
Bls12381PSDockVerKeyName,
JsonWebSignature2020,
Ed25519Signature2020,
} from './custom_crypto';
import { Bls12381BDDT16DockVerKeyName, Bls12381BDDT16MacDockName } from './crypto/constants';
import { Bls12381BDDT16DockVerKeyName, Bls12381BDDT16MacDockName, Ed255192020VerKeyName } from './crypto/constants';
import Bls12381BDDT16MACDock2024 from './crypto/Bls12381BDDT16MACDock2024';

/**
Expand Down Expand Up @@ -67,6 +68,9 @@ export async function getSuiteFromKeyDoc(keyDoc, useProofValue, options) {
case Ed25519VerKeyName:
Cls = Ed25519Signature2018;
break;
case Ed255192020VerKeyName:
Cls = Ed25519Signature2020;
break;
case Sr25519VerKeyName:
Cls = Sr25519Signature2020;
break;
Expand Down
2 changes: 2 additions & 0 deletions src/utils/vc/presentations.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { DEFAULT_CONTEXT_V1_URL } from './constants';
import {
EcdsaSecp256k1Signature2019,
Ed25519Signature2018,
Ed25519Signature2020,
Sr25519Signature2020,
JsonWebSignature2020,
Bls12381BBSSignatureDock2022,
Expand Down Expand Up @@ -156,6 +157,7 @@ export async function verifyPresentation(presentation, options = {}) {
resolver: null,
suite: [
new Ed25519Signature2018(),
new Ed25519Signature2020(),
new EcdsaSecp256k1Signature2019(),
new Sr25519Signature2020(),
new JsonWebSignature2020(),
Expand Down
28 changes: 26 additions & 2 deletions tests/integration/issuing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('Verifiable Credential issuance where issuer has a Dock DID', () => {
await dock.disconnect();
}, 10000);

test('Issue a verifiable credential with ed25519 key and verify it', async () => {
test('Issue a verifiable credential with ed25519 key and verify it (2018)', async () => {
const issuerKey = getKeyDoc(issuer1DID, dock.keyring.addFromUri(issuer1KeySeed, null, 'ed25519'), 'Ed25519VerificationKey2018');
const credential = await issueCredential(issuerKey, unsignedCred);
expect(credential).toMatchObject(
Expand All @@ -87,6 +87,23 @@ describe('Verifiable Credential issuance where issuer has a Dock DID', () => {
);
}, 40000);

test('Issue a verifiable credential with ed25519 key and verify it (2020)', async () => {
const issuerKey = getKeyDoc(issuer1DID, dock.keyring.addFromUri(issuer1KeySeed, null, 'ed25519'), 'Ed25519VerificationKey2020');
const credential = await issueCredential(issuerKey, unsignedCred);
expect(credential).toMatchObject(
expect.objectContaining(
getCredMatcherDoc(unsignedCred, issuer1DID, issuerKey.id, 'Ed25519Signature2020'),
),
);

const result = await verifyCredential(credential, { resolver });
expect(result).toMatchObject(
expect.objectContaining(
getProofMatcherDoc(),
),
);
}, 40000);

test('Issue a verifiable credential with secp256k1 key and verify it', async () => {
const issuerKey = getKeyDoc(issuer2DID, generateEcdsaSecp256k1Keypair(issuer2KeyEntropy), 'EcdsaSecp256k1VerificationKey2019');
const credential = await issueCredential(issuerKey, unsignedCred);
Expand Down Expand Up @@ -122,13 +139,20 @@ describe('Verifiable Credential issuance where issuer has a Dock DID', () => {
);
}, 40000);

test('(JWT) Issue a verifiable credential with ed25519 key and verify it', async () => {
test('(JWT) Issue a verifiable credential with ed25519 key and verify it (2018)', async () => {
const issuerKey = getKeyDoc(issuer1DID, dock.keyring.addFromUri(issuer1KeySeed, null, 'ed25519'), 'Ed25519VerificationKey2018');
const credential = await issueCredential(issuerKey, unsignedCred, true, null, null, null, null, false, 'jwt');
const result = await verifyCredential(credential, { resolver });
expect(result.verified).toBeTruthy();
}, 40000);

test('(JWT) Issue a verifiable credential with ed25519 key and verify it (2020)', async () => {
const issuerKey = getKeyDoc(issuer1DID, dock.keyring.addFromUri(issuer1KeySeed, null, 'ed25519'), 'Ed25519VerificationKey2020');
const credential = await issueCredential(issuerKey, unsignedCred, true, null, null, null, null, false, 'jwt');
const result = await verifyCredential(credential, { resolver });
expect(result.verified).toBeTruthy();
}, 40000);

test('(JWT) Issue a verifiable credential with secp256k1 key and verify it', async () => {
const issuerKey = getKeyDoc(issuer2DID, generateEcdsaSecp256k1Keypair(issuer2KeyEntropy), 'EcdsaSecp256k1VerificationKey2019');
const credential = await issueCredential(issuerKey, unsignedCred, true, null, null, null, null, false, 'jwt');
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/presenting.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,13 @@ describe('Verifiable Presentation where both issuer and holder have a Dock DID',
const holder1Key = getKeyDoc(holder1DID, dock.keyring.addFromUri(holder1KeySeed, null, 'ed25519'), 'Ed25519VerificationKey2018');
const holder2Key = getKeyDoc(holder2DID, generateEcdsaSecp256k1Keypair(holder2KeyEntropy), 'EcdsaSecp256k1VerificationKey2019');
const holder3Key = getKeyDoc(holder3DID, dock.keyring.addFromUri(holder3KeySeed, null, 'sr25519'), 'Sr25519VerificationKey2020');
const holder4Key = getKeyDoc(holder1DID, dock.keyring.addFromUri(holder1KeySeed, null, 'ed25519'), 'Ed25519VerificationKey2020');

for (const elem of [
[cred1, 'Ed25519Signature2018', holder1Key],
[cred2, 'EcdsaSecp256k1Signature2019', holder2Key],
[cred3, 'Sr25519Signature2020', holder3Key],
[cred4, 'Ed25519Signature2020', holder4Key],
]) {
const cred = elem[0];
const sigType = elem[1];
Expand Down

0 comments on commit 7718584

Please sign in to comment.