From 55da6a281cb5202b32c46cafaa00d15d8affe1b0 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 18:18:45 +0000 Subject: [PATCH 01/10] Add basic language & direction vectors to mocks. --- ...-issuer-multi-language-description-ok.json | 24 +++++++++++++++++++ ...dential-issuer-multi-language-name-ok.json | 24 +++++++++++++++++++ ...-issuer-name-language-direction-en-ok.json | 19 +++++++++++++++ ...credential-issuer-name-language-en-ok.json | 18 ++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 test/mocks/credential-issuer-multi-language-description-ok.json create mode 100644 test/mocks/credential-issuer-multi-language-name-ok.json create mode 100644 test/mocks/credential-issuer-name-language-direction-en-ok.json create mode 100644 test/mocks/credential-issuer-name-language-en-ok.json diff --git a/test/mocks/credential-issuer-multi-language-description-ok.json b/test/mocks/credential-issuer-multi-language-description-ok.json new file mode 100644 index 00000000..692ba1de --- /dev/null +++ b/test/mocks/credential-issuer-multi-language-description-ok.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2" + ], + "type": [ + "VerifiableCredential" + ], + "issuer": { + "id": "did:issuer:dog", + "description": [{ + "@value":"Dog", + "@language": "en" + }, { + "@value":"Chien", + "@language": "fr" + }, { + "@value":"Cane", + "@language": "it" + }] + }, + "credentialSubject": { + "id": "did:example:subject" + } +} diff --git a/test/mocks/credential-issuer-multi-language-name-ok.json b/test/mocks/credential-issuer-multi-language-name-ok.json new file mode 100644 index 00000000..ef8d07f4 --- /dev/null +++ b/test/mocks/credential-issuer-multi-language-name-ok.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2" + ], + "type": [ + "VerifiableCredential" + ], + "issuer": { + "id": "did:issuer:dog", + "name": [{ + "@value":"Dog", + "@language": "en" + }, { + "@value":"Chien", + "@language": "fr" + }, { + "@value":"Cane", + "@language": "it" + }] + }, + "credentialSubject": { + "id": "did:example:subject" + } +} diff --git a/test/mocks/credential-issuer-name-language-direction-en-ok.json b/test/mocks/credential-issuer-name-language-direction-en-ok.json new file mode 100644 index 00000000..83434ea6 --- /dev/null +++ b/test/mocks/credential-issuer-name-language-direction-en-ok.json @@ -0,0 +1,19 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2" + ], + "type": [ + "VerifiableCredential" + ], + "issuer": { + "id": "did:example:issuer", + "name": { + "@value":"ExampleIssuer", + "@language": "en", + "@direction": "ltr" + } + }, + "credentialSubject": { + "id": "did:example:subject" + } +} diff --git a/test/mocks/credential-issuer-name-language-en-ok.json b/test/mocks/credential-issuer-name-language-en-ok.json new file mode 100644 index 00000000..24840821 --- /dev/null +++ b/test/mocks/credential-issuer-name-language-en-ok.json @@ -0,0 +1,18 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2" + ], + "type": [ + "VerifiableCredential" + ], + "issuer": { + "id": "did:example:issuer", + "name": { + "@value":"ExampleIssuer", + "@language": "en" + } + }, + "credentialSubject": { + "id": "did:example:subject" + } +} From 53de31647d54279a2af72c98ba737ba6cccf0ba1 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 18:34:10 +0000 Subject: [PATCH 02/10] Separate issuance from verifier tests. --- test/10-issue.spec.js | 364 ++++++++++++++++++ test/{10-verify.spec.js => 20-verify.spec.js} | 272 ------------- ...dateRegex.spec.js => 30-dateRegex.spec.js} | 0 3 files changed, 364 insertions(+), 272 deletions(-) create mode 100644 test/10-issue.spec.js rename test/{10-verify.spec.js => 20-verify.spec.js} (78%) rename test/{20-dateRegex.spec.js => 30-dateRegex.spec.js} (100%) diff --git a/test/10-issue.spec.js b/test/10-issue.spec.js new file mode 100644 index 00000000..ff1bb501 --- /dev/null +++ b/test/10-issue.spec.js @@ -0,0 +1,364 @@ +/*! + * Copyright (c) 2019-2024 Digital Bazaar, Inc. All rights reserved. + */ +import * as Bls12381Multikey from '@digitalbazaar/bls12-381-multikey'; +import * as EcdsaMultikey from '@digitalbazaar/ecdsa-multikey'; +import * as vc from '../lib/index.js'; +import { + documentLoader, + remoteDocuments, +} from './testDocumentLoader.js'; +import {assertionController} from './mocks/assertionController.js'; +import chai from 'chai'; +import {createSkewedTimeStamp} from './helpers.js'; +import {Ed25519Signature2018} from '@digitalbazaar/ed25519-signature-2018'; +import { + Ed25519VerificationKey2018 +} from '@digitalbazaar/ed25519-verification-key-2018'; +import {v4 as uuid} from 'uuid'; +import {versionedCredentials} from './mocks/credential.js'; + +const should = chai.should(); + +// do ed25519 setup... +let suite; +let keyPair; +before(async () => { + // set up the Ed25519 key pair that will be signing and verifying + keyPair = await Ed25519VerificationKey2018.generate({ + id: 'https://example.edu/issuers/keys/1', + controller: 'https://example.edu/issuers/565049' + }); + + // add the key to the controller doc (authorizes its use for assertion) + assertionController.assertionMethod.push(keyPair.id); + // also add the key for authentication (VP) purposes + // FIXME: this shortcut to reuse the same key and sign VPs as issuer can + // confuse developers trying to learn from the test suite and it should + // be changed + assertionController.authentication.push(keyPair.id); + + // register the controller document and the key document with documentLoader + remoteDocuments.set( + 'https://example.edu/issuers/565049', assertionController); + remoteDocuments.set( + 'https://example.edu/issuers/keys/1', + await keyPair.export({publicKey: true})); + + // set up the signature suite, using the generated key + suite = new Ed25519Signature2018({ + verificationMethod: 'https://example.edu/issuers/keys/1', + key: keyPair + }); +}); + +// do ecdsa setup... +let ecdsaKeyPair; +before(async () => { + // set up the ECDSA key pair that will be signing and verifying + ecdsaKeyPair = await EcdsaMultikey.generate({ + curve: 'P-256', + id: 'https://example.edu/issuers/keys/2', + controller: 'https://example.edu/issuers/565049' + }); + + // add the key to the controller doc (authorizes its use for assertion) + assertionController.assertionMethod.push(ecdsaKeyPair.id); + // register the key document with documentLoader + remoteDocuments.set( + 'https://example.edu/issuers/keys/2', + await ecdsaKeyPair.export({publicKey: true})); +}); + +// do BBS setup... +let bbsKeyPair; +before(async () => { + // set up the BBS key pair that will be signing and verifying + bbsKeyPair = await Bls12381Multikey.generateBbsKeyPair({ + algorithm: 'BBS-BLS12-381-SHA-256', + id: 'https://example.edu/issuers/keys/3', + controller: 'https://example.edu/issuers/565049' + }); + + // add the key to the controller doc (authorizes its use for assertion) + assertionController.assertionMethod.push(bbsKeyPair.id); + // register the key document with documentLoader + remoteDocuments.set( + 'https://example.edu/issuers/keys/3', + await bbsKeyPair.export({publicKey: true})); +}); + +// run tests on each version of VCs +for(const [version, mockCredential] of versionedCredentials) { + describe(`Verifiable Credentials Data Model ${version}`, async function() { + describe('vc.issue()', () => { + it('should issue a verifiable credential with proof', async () => { + const credential = mockCredential(); + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + }); + it('should issue a verifiable credential with out id', async () => { + const credential = mockCredential(); + delete credential.id; + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + should.not.exist(verifiableCredential.id, 'Expected no "vc.id".'); + }); + it('should throw an error on missing verificationMethod', async () => { + const suite = new Ed25519Signature2018({ + // Note no key id or verificationMethod passed to suite + key: await Ed25519VerificationKey2018.generate() + }); + let error; + try { + await vc.issue({ + credential: mockCredential(), + suite + }); + } catch(e) { + error = e; + } + should.exist(error, + 'Should throw error when "verificationMethod" property is missing'); + error.should.be.instanceof(TypeError); + error.message.should + .contain('"suite.verificationMethod" property is required.'); + }); + if(version === 1.0) { + it('should issue an expired verifiable credential', async () => { + const keyPair = await Ed25519VerificationKey2018.generate(); + const fp = Ed25519VerificationKey2018 + .fingerprintFromPublicKey({ + publicKeyBase58: keyPair.publicKeyBase58 + }); + keyPair.id = `did:key:${fp}#${fp}`; + const credential = mockCredential(); + credential.id = `urn:uuid:${uuid()}`; + credential.issuer = `did:key:${fp}`; + credential.expirationDate = '2020-05-31T19:21:25Z'; + const verifiableCredential = await vc.issue({ + credential, + suite: new Ed25519Signature2018({ + key: keyPair + }), + // set `now` to expiration date, allowing the credential + // to be issued + // without failing the expired check + now: (new Date('2020-05-31T19:21:25Z')), + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + }); + it('should add "issuanceDate" to verifiable credentials', async () => { + const credential = mockCredential(); + delete credential.issuanceDate; + const now = new Date(); + const expectedIssuanceDate = `${now.toISOString().slice(0, -5)}Z`; + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader, + now + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + verifiableCredential.should.have.property( + 'issuanceDate', + expectedIssuanceDate + ); + }); + } + if(version === 2.0) { + it('should issue "validUntil" in the future', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + // set validUntil one year in the future + credential.validUntil = createSkewedTimeStamp({skewYear: 1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist(error, + 'Should not throw error when issuing "validUntil" in future'); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validUntil is present and has correct timestamp + verifiableCredential.should.have.property( + 'validUntil', + credential.validUntil + ); + }); + it('should issue "validUntil" in the past', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + // set validUntil one year in the past + credential.validUntil = createSkewedTimeStamp({skewYear: -1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist(error, + 'Should not throw error when issuing with "validUntil" in past'); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validUntil is present and has correct timestamp + verifiableCredential.should.have.property( + 'validUntil', + credential.validUntil + ); + }); + it('should issue "validFrom" in the past', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + credential.validFrom = createSkewedTimeStamp({skewYear: -1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist(error, + 'Should not throw error when issuing "validFrom" in past'); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validFrom is present and has correct timestamp + verifiableCredential.should.have.property( + 'validFrom', + credential.validFrom + ); + }); + it('should issue "validFrom" in the future', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + credential.validFrom = createSkewedTimeStamp({skewYear: 1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist(error, + 'Should not throw error when issuing "validFrom" in future'); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validFrom is present and has correct timestamp + verifiableCredential.should.have.property( + 'validFrom', + credential.validFrom + ); + }); + it('should issue both "validFrom" and "validUntil"', async () => { + const credential = mockCredential(); + credential.issuer = 'did:example:12345'; + credential.validFrom = createSkewedTimeStamp({skewYear: -1}); + credential.validUntil = createSkewedTimeStamp({skewYear: 1}); + let error; + let verifiableCredential; + try { + verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + } catch(e) { + error = e; + } + should.not.exist( + error, + 'Should not throw when issuing VC with both "validFrom" and' + + '"validUntil"' + ); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + // ensure validFrom & validUntil are present + // and have correct timestamps + verifiableCredential.should.have.property( + 'validFrom', + credential.validFrom + ); + verifiableCredential.should.have.property( + 'validUntil', + credential.validUntil + ); + }); + } + }); + describe('vc.signPresentation()', () => { + it('should create a signed VP', async () => { + const presentation = vc.createPresentation({ + verifiableCredential: mockCredential(), + id: 'test:ebc6f1c2', + holder: 'did:ex:holder123', + version + }); + const vp = await vc.signPresentation({ + presentation, + suite, // from before() block + challenge: '12ec21', + documentLoader + }); + vp.should.have.property('proof'); + vp.proof.should.have.property('type', 'Ed25519Signature2018'); + vp.proof.should.have.property('proofPurpose', 'authentication'); + vp.proof.should.have.property('verificationMethod', + 'https://example.edu/issuers/keys/1'); + vp.proof.should.have.property('challenge', '12ec21'); + vp.proof.should.have.property('created'); + vp.proof.should.have.property('jws'); + }); + }); + }); +} diff --git a/test/10-verify.spec.js b/test/20-verify.spec.js similarity index 78% rename from test/10-verify.spec.js rename to test/20-verify.spec.js index 968f36df..9c368b23 100644 --- a/test/10-verify.spec.js +++ b/test/20-verify.spec.js @@ -101,252 +101,6 @@ before(async () => { // run tests on each version of VCs for(const [version, mockCredential] of versionedCredentials) { describe(`Verifiable Credentials Data Model ${version}`, async function() { - describe('vc.issue()', () => { - it('should issue a verifiable credential with proof', async () => { - const credential = mockCredential(); - const verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - }); - it('should issue a verifiable credential with out id', async () => { - const credential = mockCredential(); - delete credential.id; - const verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - should.not.exist(verifiableCredential.id, 'Expected no "vc.id".'); - }); - it('should throw an error on missing verificationMethod', async () => { - const suite = new Ed25519Signature2018({ - // Note no key id or verificationMethod passed to suite - key: await Ed25519VerificationKey2018.generate() - }); - let error; - try { - await vc.issue({ - credential: mockCredential(), - suite - }); - } catch(e) { - error = e; - } - should.exist(error, - 'Should throw error when "verificationMethod" property is missing'); - error.should.be.instanceof(TypeError); - error.message.should - .contain('"suite.verificationMethod" property is required.'); - }); - if(version === 1.0) { - it('should issue an expired verifiable credential', async () => { - const keyPair = await Ed25519VerificationKey2018.generate(); - const fp = Ed25519VerificationKey2018 - .fingerprintFromPublicKey({ - publicKeyBase58: keyPair.publicKeyBase58 - }); - keyPair.id = `did:key:${fp}#${fp}`; - const credential = mockCredential(); - credential.id = `urn:uuid:${uuid()}`; - credential.issuer = `did:key:${fp}`; - credential.expirationDate = '2020-05-31T19:21:25Z'; - const verifiableCredential = await vc.issue({ - credential, - suite: new Ed25519Signature2018({ - key: keyPair - }), - // set `now` to expiration date, allowing the credential - // to be issued - // without failing the expired check - now: (new Date('2020-05-31T19:21:25Z')), - documentLoader - }); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - }); - it('should add "issuanceDate" to verifiable credentials', async () => { - const credential = mockCredential(); - delete credential.issuanceDate; - const now = new Date(); - const expectedIssuanceDate = `${now.toISOString().slice(0, -5)}Z`; - const verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader, - now - }); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - verifiableCredential.should.have.property( - 'issuanceDate', - expectedIssuanceDate - ); - }); - } - if(version === 2.0) { - it('should issue "validUntil" in the future', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - // set validUntil one year in the future - credential.validUntil = createSkewedTimeStamp({skewYear: 1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist(error, - 'Should not throw error when issuing "validUntil" in future'); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validUntil is present and has correct timestamp - verifiableCredential.should.have.property( - 'validUntil', - credential.validUntil - ); - }); - it('should issue "validUntil" in the past', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - // set validUntil one year in the past - credential.validUntil = createSkewedTimeStamp({skewYear: -1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist(error, - 'Should not throw error when issuing with "validUntil" in past'); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validUntil is present and has correct timestamp - verifiableCredential.should.have.property( - 'validUntil', - credential.validUntil - ); - }); - it('should issue "validFrom" in the past', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - credential.validFrom = createSkewedTimeStamp({skewYear: -1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist(error, - 'Should not throw error when issuing "validFrom" in past'); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validFrom is present and has correct timestamp - verifiableCredential.should.have.property( - 'validFrom', - credential.validFrom - ); - }); - it('should issue "validFrom" in the future', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - credential.validFrom = createSkewedTimeStamp({skewYear: 1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist(error, - 'Should not throw error when issuing "validFrom" in future'); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validFrom is present and has correct timestamp - verifiableCredential.should.have.property( - 'validFrom', - credential.validFrom - ); - }); - it('should issue both "validFrom" and "validUntil"', async () => { - const credential = mockCredential(); - credential.issuer = 'did:example:12345'; - credential.validFrom = createSkewedTimeStamp({skewYear: -1}); - credential.validUntil = createSkewedTimeStamp({skewYear: 1}); - let error; - let verifiableCredential; - try { - verifiableCredential = await vc.issue({ - credential, - suite, - documentLoader - }); - } catch(e) { - error = e; - } - should.not.exist( - error, - 'Should not throw when issuing VC with both "validFrom" and' + - '"validUntil"' - ); - verifiableCredential.should.exist; - verifiableCredential.should.be.an('object'); - verifiableCredential.should.have.property('proof'); - verifiableCredential.proof.should.be.an('object'); - // ensure validFrom & validUntil are present - // and have correct timestamps - verifiableCredential.should.have.property( - 'validFrom', - credential.validFrom - ); - verifiableCredential.should.have.property( - 'validUntil', - credential.validUntil - ); - }); - } - }); - describe('vc.createPresentation()', () => { it('should create an unsigned presentation', () => { const presentation = vc.createPresentation({ @@ -361,32 +115,6 @@ for(const [version, mockCredential] of versionedCredentials) { presentation.should.not.have.property('proof'); }); }); - - describe('vc.signPresentation()', () => { - it('should create a signed VP', async () => { - const presentation = vc.createPresentation({ - verifiableCredential: mockCredential(), - id: 'test:ebc6f1c2', - holder: 'did:ex:holder123', - version - }); - const vp = await vc.signPresentation({ - presentation, - suite, // from before() block - challenge: '12ec21', - documentLoader - }); - vp.should.have.property('proof'); - vp.proof.should.have.property('type', 'Ed25519Signature2018'); - vp.proof.should.have.property('proofPurpose', 'authentication'); - vp.proof.should.have.property('verificationMethod', - 'https://example.edu/issuers/keys/1'); - vp.proof.should.have.property('challenge', '12ec21'); - vp.proof.should.have.property('created'); - vp.proof.should.have.property('jws'); - }); - }); - describe('verify API (credentials)', () => { it('should verify a vc', async () => { const verifiableCredential = await vc.issue({ diff --git a/test/20-dateRegex.spec.js b/test/30-dateRegex.spec.js similarity index 100% rename from test/20-dateRegex.spec.js rename to test/30-dateRegex.spec.js From 9aaec8da69ac8b2bb76329bc13cd526a7cc4ed2f Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 19:03:02 +0000 Subject: [PATCH 03/10] Add tests for basic language features. --- test/10-issue.spec.js | 25 +++++++++++++++++++++++++ test/mocks/mock.data.js | 8 +++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/test/10-issue.spec.js b/test/10-issue.spec.js index ff1bb501..28a11df4 100644 --- a/test/10-issue.spec.js +++ b/test/10-issue.spec.js @@ -11,6 +11,7 @@ import { import {assertionController} from './mocks/assertionController.js'; import chai from 'chai'; import {createSkewedTimeStamp} from './helpers.js'; +import {credentials} from './mocks/mock.data.js'; import {Ed25519Signature2018} from '@digitalbazaar/ed25519-signature-2018'; import { Ed25519VerificationKey2018 @@ -334,6 +335,30 @@ for(const [version, mockCredential] of versionedCredentials) { credential.validUntil ); }); + it('should issue a VC with multiple languages', async function() { + const credential = structuredClone(credentials.language.multiple); + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + }); + it('should issue a VC with a single language', async function() { + const credential = structuredClone(credentials.language.single); + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + }); } }); describe('vc.signPresentation()', () => { diff --git a/test/mocks/mock.data.js b/test/mocks/mock.data.js index 111bfc8e..5b9ddad0 100644 --- a/test/mocks/mock.data.js +++ b/test/mocks/mock.data.js @@ -1,7 +1,10 @@ /* eslint-disable quotes, quote-props, max-len */ import constants from '../constants.js'; +import {createRequire} from 'node:module'; import {versionedCredentials} from './credential.js'; +const require = createRequire(import.meta.url); + export const mock = {}; const didContexts = [ @@ -31,7 +34,10 @@ const _mixedCredential = () => { export const credentials = mock.credentials = {}; credentials.mixed = _mixedCredential(); - +credentials.language = { + multiple: require('./credential-issuer-multi-language-description-ok.json'), + single: require('./credential-issuer-name-language-en-ok.json') +}; credentials.alpha = { "@context": [ constants.CREDENTIALS_CONTEXT_URL, { From c2f04b6c1ad74425f8bb822e2af09cc343c3f6cd Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 19:12:40 +0000 Subject: [PATCH 04/10] Refactor to use better structure for test data. --- test/10-issue.spec.js | 20 ++++++++++++++++++-- test/mocks/mock.data.js | 12 +++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/test/10-issue.spec.js b/test/10-issue.spec.js index 28a11df4..18766353 100644 --- a/test/10-issue.spec.js +++ b/test/10-issue.spec.js @@ -336,7 +336,8 @@ for(const [version, mockCredential] of versionedCredentials) { ); }); it('should issue a VC with multiple languages', async function() { - const credential = structuredClone(credentials.language.multiple); + const credential = structuredClone( + credentials.features.multiple.languages); const verifiableCredential = await vc.issue({ credential, suite, @@ -348,7 +349,8 @@ for(const [version, mockCredential] of versionedCredentials) { verifiableCredential.proof.should.be.an('object'); }); it('should issue a VC with a single language', async function() { - const credential = structuredClone(credentials.language.single); + const credential = structuredClone( + credentials.features.single.language); const verifiableCredential = await vc.issue({ credential, suite, @@ -359,6 +361,20 @@ for(const [version, mockCredential] of versionedCredentials) { verifiableCredential.should.have.property('proof'); verifiableCredential.proof.should.be.an('object'); }); + it('should issue a VC with a single language & direction', + async function() { + const credential = structuredClone( + credentials.features.single.direction); + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + }); } }); describe('vc.signPresentation()', () => { diff --git a/test/mocks/mock.data.js b/test/mocks/mock.data.js index 5b9ddad0..38c585ea 100644 --- a/test/mocks/mock.data.js +++ b/test/mocks/mock.data.js @@ -34,9 +34,15 @@ const _mixedCredential = () => { export const credentials = mock.credentials = {}; credentials.mixed = _mixedCredential(); -credentials.language = { - multiple: require('./credential-issuer-multi-language-description-ok.json'), - single: require('./credential-issuer-name-language-en-ok.json') +credentials.features = { + multiple: { + languages: require('./credential-issuer-multi-language-description-ok.json'), + directions: null + }, + single: { + language: require('./credential-issuer-name-language-en-ok.json'), + direction: require('./credential-issuer-name-language-direction-en-ok.json') + } }; credentials.alpha = { "@context": [ From 3e626cfec50c4451dc9d6d1ccfda27a377012bdc Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 19:23:45 +0000 Subject: [PATCH 05/10] Add multi direction test. --- test/10-issue.spec.js | 14 ++++++++++ ...ential-issuer-multi-direction-name-ok.json | 28 +++++++++++++++++++ test/mocks/mock.data.js | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 test/mocks/credential-issuer-multi-direction-name-ok.json diff --git a/test/10-issue.spec.js b/test/10-issue.spec.js index 18766353..cbcb483d 100644 --- a/test/10-issue.spec.js +++ b/test/10-issue.spec.js @@ -348,6 +348,20 @@ for(const [version, mockCredential] of versionedCredentials) { verifiableCredential.should.have.property('proof'); verifiableCredential.proof.should.be.an('object'); }); + it('should issue a VC with multiple languages & directions', + async function() { + const credential = structuredClone( + credentials.features.multiple.directions); + const verifiableCredential = await vc.issue({ + credential, + suite, + documentLoader + }); + verifiableCredential.should.exist; + verifiableCredential.should.be.an('object'); + verifiableCredential.should.have.property('proof'); + verifiableCredential.proof.should.be.an('object'); + }); it('should issue a VC with a single language', async function() { const credential = structuredClone( credentials.features.single.language); diff --git a/test/mocks/credential-issuer-multi-direction-name-ok.json b/test/mocks/credential-issuer-multi-direction-name-ok.json new file mode 100644 index 00000000..3b7c9380 --- /dev/null +++ b/test/mocks/credential-issuer-multi-direction-name-ok.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2" + ], + "type": [ + "VerifiableCredential" + ], + "issuer": { + "id": "did:issuer:dog", + "name": [{ + "@value":"Dog", + "@language": "en", + "@direction": "ltr" + }, { + "@value":"Chien", + "@language": "fr", + "@direction": "ltr" + + }, { + "@value":"Cane", + "@language": "it", + "@direction": "ltr" + }] + }, + "credentialSubject": { + "id": "did:example:subject" + } +} diff --git a/test/mocks/mock.data.js b/test/mocks/mock.data.js index 38c585ea..052d5055 100644 --- a/test/mocks/mock.data.js +++ b/test/mocks/mock.data.js @@ -37,7 +37,7 @@ credentials.mixed = _mixedCredential(); credentials.features = { multiple: { languages: require('./credential-issuer-multi-language-description-ok.json'), - directions: null + directions: require('./credential-issuer-multi-direction-name-ok.json') }, single: { language: require('./credential-issuer-name-language-en-ok.json'), From e69ba253e545ed3aadd79c49c74bc4fdac777a81 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 19:26:11 +0000 Subject: [PATCH 06/10] Remove unused test vector. --- ...dential-issuer-multi-language-name-ok.json | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 test/mocks/credential-issuer-multi-language-name-ok.json diff --git a/test/mocks/credential-issuer-multi-language-name-ok.json b/test/mocks/credential-issuer-multi-language-name-ok.json deleted file mode 100644 index ef8d07f4..00000000 --- a/test/mocks/credential-issuer-multi-language-name-ok.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "@context": [ - "https://www.w3.org/ns/credentials/v2" - ], - "type": [ - "VerifiableCredential" - ], - "issuer": { - "id": "did:issuer:dog", - "name": [{ - "@value":"Dog", - "@language": "en" - }, { - "@value":"Chien", - "@language": "fr" - }, { - "@value":"Cane", - "@language": "it" - }] - }, - "credentialSubject": { - "id": "did:example:subject" - } -} From 60e0ab61a7f04d47ba81ae49c9c8b0668fe8b240 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 19:28:09 +0000 Subject: [PATCH 07/10] Use rtl in one test. --- test/mocks/credential-issuer-name-language-direction-en-ok.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mocks/credential-issuer-name-language-direction-en-ok.json b/test/mocks/credential-issuer-name-language-direction-en-ok.json index 83434ea6..31aa5355 100644 --- a/test/mocks/credential-issuer-name-language-direction-en-ok.json +++ b/test/mocks/credential-issuer-name-language-direction-en-ok.json @@ -10,7 +10,7 @@ "name": { "@value":"ExampleIssuer", "@language": "en", - "@direction": "ltr" + "@direction": "rtl" } }, "credentialSubject": { From 1274eb10110eda12d554796d645d5bca4186bf74 Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 19:51:50 +0000 Subject: [PATCH 08/10] Remove unused keyPairs form issue tests. --- test/10-issue.spec.js | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/test/10-issue.spec.js b/test/10-issue.spec.js index cbcb483d..3203233f 100644 --- a/test/10-issue.spec.js +++ b/test/10-issue.spec.js @@ -1,8 +1,6 @@ /*! * Copyright (c) 2019-2024 Digital Bazaar, Inc. All rights reserved. */ -import * as Bls12381Multikey from '@digitalbazaar/bls12-381-multikey'; -import * as EcdsaMultikey from '@digitalbazaar/ecdsa-multikey'; import * as vc from '../lib/index.js'; import { documentLoader, @@ -53,42 +51,6 @@ before(async () => { }); }); -// do ecdsa setup... -let ecdsaKeyPair; -before(async () => { - // set up the ECDSA key pair that will be signing and verifying - ecdsaKeyPair = await EcdsaMultikey.generate({ - curve: 'P-256', - id: 'https://example.edu/issuers/keys/2', - controller: 'https://example.edu/issuers/565049' - }); - - // add the key to the controller doc (authorizes its use for assertion) - assertionController.assertionMethod.push(ecdsaKeyPair.id); - // register the key document with documentLoader - remoteDocuments.set( - 'https://example.edu/issuers/keys/2', - await ecdsaKeyPair.export({publicKey: true})); -}); - -// do BBS setup... -let bbsKeyPair; -before(async () => { - // set up the BBS key pair that will be signing and verifying - bbsKeyPair = await Bls12381Multikey.generateBbsKeyPair({ - algorithm: 'BBS-BLS12-381-SHA-256', - id: 'https://example.edu/issuers/keys/3', - controller: 'https://example.edu/issuers/565049' - }); - - // add the key to the controller doc (authorizes its use for assertion) - assertionController.assertionMethod.push(bbsKeyPair.id); - // register the key document with documentLoader - remoteDocuments.set( - 'https://example.edu/issuers/keys/3', - await bbsKeyPair.export({publicKey: true})); -}); - // run tests on each version of VCs for(const [version, mockCredential] of versionedCredentials) { describe(`Verifiable Credentials Data Model ${version}`, async function() { From 0d7b09165430db8dd9aab305be8fceba20ded5ba Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 20:23:37 +0000 Subject: [PATCH 09/10] Add ed25519Suite2020 to contexts & use as default key in issue tests. --- test/10-issue.spec.js | 27 +++++++++++++++------------ test/contexts/index.js | 21 +++++++++++++++------ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/test/10-issue.spec.js b/test/10-issue.spec.js index 3203233f..afe3012f 100644 --- a/test/10-issue.spec.js +++ b/test/10-issue.spec.js @@ -10,10 +10,10 @@ import {assertionController} from './mocks/assertionController.js'; import chai from 'chai'; import {createSkewedTimeStamp} from './helpers.js'; import {credentials} from './mocks/mock.data.js'; -import {Ed25519Signature2018} from '@digitalbazaar/ed25519-signature-2018'; +import {Ed25519Signature2020} from '@digitalbazaar/ed25519-signature-2020'; import { - Ed25519VerificationKey2018 -} from '@digitalbazaar/ed25519-verification-key-2018'; + Ed25519VerificationKey2020 +} from '@digitalbazaar/ed25519-verification-key-2020'; import {v4 as uuid} from 'uuid'; import {versionedCredentials} from './mocks/credential.js'; @@ -24,7 +24,7 @@ let suite; let keyPair; before(async () => { // set up the Ed25519 key pair that will be signing and verifying - keyPair = await Ed25519VerificationKey2018.generate({ + keyPair = await Ed25519VerificationKey2020.generate({ id: 'https://example.edu/issuers/keys/1', controller: 'https://example.edu/issuers/565049' }); @@ -45,9 +45,12 @@ before(async () => { await keyPair.export({publicKey: true})); // set up the signature suite, using the generated key - suite = new Ed25519Signature2018({ + suite = new Ed25519Signature2020({ verificationMethod: 'https://example.edu/issuers/keys/1', - key: keyPair + key: keyPair, + canonizeOptions: { + rdfDirection: 'i18n-datatype', + } }); }); @@ -82,9 +85,9 @@ for(const [version, mockCredential] of versionedCredentials) { should.not.exist(verifiableCredential.id, 'Expected no "vc.id".'); }); it('should throw an error on missing verificationMethod', async () => { - const suite = new Ed25519Signature2018({ + const suite = new Ed25519Signature2020({ // Note no key id or verificationMethod passed to suite - key: await Ed25519VerificationKey2018.generate() + key: await Ed25519VerificationKey2020.generate() }); let error; try { @@ -103,8 +106,8 @@ for(const [version, mockCredential] of versionedCredentials) { }); if(version === 1.0) { it('should issue an expired verifiable credential', async () => { - const keyPair = await Ed25519VerificationKey2018.generate(); - const fp = Ed25519VerificationKey2018 + const keyPair = await Ed25519VerificationKey2020.generate(); + const fp = Ed25519VerificationKey2020 .fingerprintFromPublicKey({ publicKeyBase58: keyPair.publicKeyBase58 }); @@ -115,7 +118,7 @@ for(const [version, mockCredential] of versionedCredentials) { credential.expirationDate = '2020-05-31T19:21:25Z'; const verifiableCredential = await vc.issue({ credential, - suite: new Ed25519Signature2018({ + suite: new Ed25519Signature2020({ key: keyPair }), // set `now` to expiration date, allowing the credential @@ -368,7 +371,7 @@ for(const [version, mockCredential] of versionedCredentials) { documentLoader }); vp.should.have.property('proof'); - vp.proof.should.have.property('type', 'Ed25519Signature2018'); + vp.proof.should.have.property('type', 'Ed25519Signature2020'); vp.proof.should.have.property('proofPurpose', 'authentication'); vp.proof.should.have.property('verificationMethod', 'https://example.edu/issuers/keys/1'); diff --git a/test/contexts/index.js b/test/contexts/index.js index c2efb969..38569827 100644 --- a/test/contexts/index.js +++ b/test/contexts/index.js @@ -14,9 +14,13 @@ import { contexts as didContexts } from 'did-context'; import { - constants as ed25519Constants, - contexts as ed25519Contexts, + constants as ed25519Constants2018, + contexts as ed25519Contexts2018, } from 'ed25519-signature-2018-context'; +import { + constants as ed25519Constants2020, + contexts as ed25519Contexts2020, +} from 'ed25519-signature-2020-context'; import { CONTEXT_V1 as odrlCtx, CONTEXT_URL_V1 as odrlCtxUrl @@ -38,7 +42,8 @@ import {nullId} from './null_id.js'; import {nullType} from './null_type.js'; import {nullVersion} from './null_version.js'; -const {CONTEXT_URL: ED25519_CONTEXT_URL} = ed25519Constants; +const {CONTEXT_URL: ED25519_2018_CONTEXT_URL} = ed25519Constants2018; +const {CONTEXT_URL: ED25519_2020_CONTEXT_URL} = ed25519Constants2020; const {CREDENTIALS_CONTEXT_V1_URL} = credentialsConstants; const {CONTEXT_URL: CREDENTIALS_CONTEXT_V2_URL} = credentialsV2Constants; const {CREDENTIALS_V2_EXAMPLE_CONTEXT_URL} = vcExamplesV2Constants; @@ -62,9 +67,13 @@ export const validContexts = { url: CREDENTIALS_CONTEXT_V2_URL, value: credentialsV2Contexts.get(CREDENTIALS_CONTEXT_V2_URL) }, - ed25519Context: { - url: ED25519_CONTEXT_URL, - value: ed25519Contexts.get(ED25519_CONTEXT_URL) + ed25519Suite2018Context: { + url: ED25519_2018_CONTEXT_URL, + value: ed25519Contexts2018.get(ED25519_2018_CONTEXT_URL) + }, + ed25519Suite2020Context: { + url: ED25519_2020_CONTEXT_URL, + value: ed25519Contexts2020.get(ED25519_2020_CONTEXT_URL) }, odrl: { url: odrlCtxUrl, From 3a6f0d953b95898b406ddb84aee34483aecdbfef Mon Sep 17 00:00:00 2001 From: Andrew Jones Date: Mon, 10 Jun 2024 20:30:18 +0000 Subject: [PATCH 10/10] Update issuer tests to work with ed25519Sig2020. --- test/10-issue.spec.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/10-issue.spec.js b/test/10-issue.spec.js index afe3012f..b7ef58df 100644 --- a/test/10-issue.spec.js +++ b/test/10-issue.spec.js @@ -107,10 +107,7 @@ for(const [version, mockCredential] of versionedCredentials) { if(version === 1.0) { it('should issue an expired verifiable credential', async () => { const keyPair = await Ed25519VerificationKey2020.generate(); - const fp = Ed25519VerificationKey2020 - .fingerprintFromPublicKey({ - publicKeyBase58: keyPair.publicKeyBase58 - }); + const fp = keyPair.fingerprint(); keyPair.id = `did:key:${fp}#${fp}`; const credential = mockCredential(); credential.id = `urn:uuid:${uuid()}`; @@ -377,7 +374,7 @@ for(const [version, mockCredential] of versionedCredentials) { 'https://example.edu/issuers/keys/1'); vp.proof.should.have.property('challenge', '12ec21'); vp.proof.should.have.property('created'); - vp.proof.should.have.property('jws'); + vp.proof.should.not.have.property('jws'); }); }); });