diff --git a/package.json b/package.json index 7799a75f..6e2c0913 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,12 @@ "base58-universal": "^2.0.0", "base64url-universal": "^2.0.0", "chai": "^4.3.7", + "chai-string": "^1.5.0", "data-integrity-test-suite-assertion": "github:w3c-ccg/data-integrity-test-suite-assertion", "jsonld-document-loader": "^2.0.0", + "json-canon": "^1.0.1", "klona": "^2.0.6", + "multibase": "^4.0.6", "mocha": "^10.2.0", "uuid": "^9.0.0", "varint": "^6.0.0", diff --git a/tests/30-rdfc-interop.js b/tests/30-rdfc-interop.js index cbe6a6be..4561ec99 100644 --- a/tests/30-rdfc-interop.js +++ b/tests/30-rdfc-interop.js @@ -3,9 +3,9 @@ * SPDX-License-Identifier: BSD-3-Clause */ import chai from 'chai'; -import {createInitialVc} from './helpers.js'; import {endpoints} from 'vc-test-suite-implementations'; import {getSuiteConfig} from './test-config.js'; +import {secureCredential} from './helpers.js'; import {verificationSuccess} from './assertions.js'; const { @@ -81,7 +81,7 @@ const { } let issuedVc; before(async function() { - issuedVc = await createInitialVc({ + issuedVc = await secureCredential({ issuer: issuerEndpoint, vc: credentials.interop[vcVersion].document, vcVersion diff --git a/tests/40-sd-create.js b/tests/40-sd-create.js index 23181536..b20fe775 100644 --- a/tests/40-sd-create.js +++ b/tests/40-sd-create.js @@ -4,7 +4,7 @@ */ import * as ecdsaSd2023Cryptosuite from '@digitalbazaar/ecdsa-sd-2023-cryptosuite'; -import {createInitialVc, endpointCheck} from './helpers.js'; +import {endpointCheck, secureCredential} from './helpers.js'; import { shouldBeBs58, shouldBeBs64UrlNoPad, @@ -51,7 +51,7 @@ describe('ecdsa-sd-2023 (create)', function() { let proofs; const verificationMethodDocuments = []; before(async function() { - issuedVc = await createInitialVc({ + issuedVc = await secureCredential({ issuer, vc: credentials.create[vcVersion].document, mandatoryPointers: diff --git a/tests/60-sd-interop.js b/tests/60-sd-interop.js index 2e738910..9618076d 100644 --- a/tests/60-sd-interop.js +++ b/tests/60-sd-interop.js @@ -2,7 +2,7 @@ * Copyright 2023 Digital Bazaar, Inc. * SPDX-License-Identifier: BSD-3-Clause */ -import {createDisclosedVc, createInitialVc} from './helpers.js'; +import {createDisclosedVc, secureCredential} from './helpers.js'; import chai from 'chai'; import {endpoints} from 'vc-test-suite-implementations'; import {getSuiteConfig} from './test-config.js'; @@ -83,7 +83,7 @@ const { } let disclosedCredential; before(async function() { - const issuedVc = await createInitialVc({ + const issuedVc = await secureCredential({ issuer: issuerEndpoint, vc: credentials.interop[vcVersion].document, mandatoryPointers: credentials.interop[vcVersion].mandatoryPointers, diff --git a/tests/90-algorithms-jcs.js b/tests/90-algorithms-jcs.js new file mode 100644 index 00000000..4ac18127 --- /dev/null +++ b/tests/90-algorithms-jcs.js @@ -0,0 +1,248 @@ +/*! + * Copyright 2024 Digital Bazaar, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ +import { + assertAllUtf8, + assertDataIntegrityProof +} from './assertions.js'; +import { + generateCredential, + isValidDatetime, + proofExists, + secureCredential, + setupReportableTestSuite, + setupRow, + verifyError, + verifySuccess +} from './helpers.js'; +import canonicalize from 'json-canon'; +import chai from 'chai'; +import {ecdsaJcsVectors} from './vectors.js'; +import {endpoints} from 'vc-test-suite-implementations'; +import {expect} from 'chai'; + +const should = chai.should(); + +const cryptosuites = [ + 'ecdsa-jcs-2019', +]; + +const {match: issuers} = endpoints.filterByTag({ + tags: cryptosuites, + property: 'issuers' +}); + +const {match: verifiers} = endpoints.filterByTag({ + tags: cryptosuites, + property: 'verifiers' +}); + +describe('Algorithms - Create Proof (ecdsa-jcs-2019)', function() { + setupReportableTestSuite(this); + this.implemented = [...issuers.keys()]; + for(const [columnId, {endpoints}] of issuers) { + describe(columnId, function() { + const [issuer] = endpoints; + let securedCredential; + before(async function() { + securedCredential = await secureCredential( + {issuer, vc: generateCredential()}); + }); + beforeEach(setupRow); + it('A data integrity proof (map), or an error, is produced as output.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#create-proof-ecdsa-jcs-2019'; + const proof = proofExists(securedCredential); + assertDataIntegrityProof(proof, 'ecdsa-jcs-2019'); + // Since we are not sending proof options, we only do a positive test + }); + it('If unsecuredDocument.@context is present, ' + + 'set proof.@context to unsecuredDocument.@context.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#create-proof-ecdsa-jcs-2019'; + // NOTE, for backwards compatibility reason, this step is not mandatory + // This feature is designed to be used with proof sets/chains, + // when adding new context in subsequent proofs + const proof = proofExists(securedCredential); + should.exist(proof['@context'], + 'Expected proof to have context.'); + canonicalize(proof['@context']).should.equal( + canonicalize(securedCredential['@context']), + 'Expected proof context to match document context.' + ); + }); + it('Let proof.proofValue be a base58-btc-encoded ' + + 'Multibase value of the proofBytes.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#create-proof-ecdsa-jcs-2019'; + // Shallow multibase test + // TODO try decoding + const proof = proofExists(securedCredential); + should.exist(proof.proofValue, + 'Expected proof to have proofValue.'); + expect(proof.proofValue.startsWith('z')).to.be.true; + }); + }); + } +}); + +describe('Algorithms - Verify Proof (ecdsa-jcs-2019)', function() { + setupReportableTestSuite(this); + for(const [columnId, {endpoints}] of verifiers) { + describe(columnId, function() { + const [verifier] = endpoints; + beforeEach(setupRow); + it('The following algorithm specifies how to verify a ' + + 'data integrity proof given an secured data document. ' + + 'Required inputs are an secured data document (map securedDocument). ' + + 'This algorithm returns a verification result.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#verify-proof-ecdsa-rdfc-2019'; + for(const curve of verifier.settings.supportedEcdsaKeyTypes) { + // Send a valid VC and an invalid VC to the verifier + // Check for success/error on response + const testVector = structuredClone(ecdsaJcsVectors[curve]); + await verifySuccess(verifier, testVector); + + // Slice the proof + testVector.proof.proofValue = + testVector.proof.proofValue.slice(0, -1); + await verifyError(verifier, testVector); + // TODO, create a verifyProblemDetails function + } + }); + }); + } +}); + +describe('Algorithms - Transformation (ecdsa-jcs-2019)', function() { + setupReportableTestSuite(this); + this.implemented = [...issuers.keys()]; + for(const [columnId, {endpoints}] of issuers) { + describe(columnId, function() { + const [issuer] = endpoints; + let securedCredential; + before(async function() { + securedCredential = await secureCredential( + {issuer, vc: generateCredential()}); + }); + beforeEach(setupRow); + it('The transformation options MUST contain a type identifier ' + + 'for the cryptographic suite (type) and a cryptosuite identifier ' + + '(cryptosuite).', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-jcs-2019'; + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + }); + it('Whenever this algorithm encodes strings, ' + + 'it MUST use UTF-8 encoding.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-jcs-2019'; + const proof = proofExists(securedCredential); + assertAllUtf8(proof); + }); + it('If options.type is not set to the string DataIntegrityProof or ' + + 'options.cryptosuite is not set to the string ecdsa-jcs-2019, ' + + 'an error MUST be raised and SHOULD convey an error type ' + + 'of PROOF_TRANSFORMATION_ERROR.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-jcs-2019'; + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + proof.type.should.equal('DataIntegrityProof', + 'Expected DataIntegrityProof type.'); + proof.cryptosuite.should.equal('ecdsa-jcs-2019', + 'Expected ecdsa-jcs-2019 cryptosuite.'); + }); + }); + } +}); + +describe('Algorithms - Proof Configuration (ecdsa-jcs-2019)', function() { + setupReportableTestSuite(this); + this.implemented = [...issuers.keys()]; + for(const [columnId, {endpoints}] of issuers) { + describe(columnId, function() { + const [issuer] = endpoints; + let securedCredential; + before(async function() { + securedCredential = await secureCredential( + {issuer, vc: generateCredential()}); + }); + beforeEach(setupRow); + it('The proof options MUST contain a type identifier for the ' + + 'cryptographic suite (type) and MUST contain a cryptosuite ' + + 'identifier (cryptosuite).', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-configuration-ecdsa-jcs-2019'; + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + }); + it('If proofConfig.type is not set to DataIntegrityProof ' + + 'and/or proofConfig.cryptosuite is not set to ecdsa-jcs-2019, ' + + 'an error MUST be raised and SHOULD convey an error type ' + + 'of PROOF_GENERATION_ERROR.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-configuration-ecdsa-jcs-2019'; + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + proof.type.should.equal('DataIntegrityProof', + 'Expected DataIntegrityProof type.'); + proof.cryptosuite.should.equal('ecdsa-jcs-2019', + 'Expected ecdsa-jcs-2019 cryptosuite.'); + }); + it('If proofConfig.created is set and if the value is not a ' + + 'valid [XMLSCHEMA11-2] datetime, an error MUST be raised and ' + + 'SHOULD convey an error type of PROOF_GENERATION_ERROR.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-configuration-ecdsa-jcs-2019'; + const proof = proofExists(securedCredential); + if(proof?.created) { + isValidDatetime(proof.created).should.equal( + true, + 'Expected created value to be a valid datetime string.' + ); + } + }); + }); + } +}); + +describe('Algorithms - Proof Serialization (ecdsa-jcs-2019)', function() { + setupReportableTestSuite(this); + this.implemented = [...issuers.keys()]; + for(const [columnId, {endpoints}] of issuers) { + describe(columnId, function() { + const [issuer] = endpoints; + let securedCredential; + before(async function() { + securedCredential = await secureCredential( + {issuer, vc: generateCredential()}); + }); + beforeEach(setupRow); + it('The proof options MUST contain a type identifier for the ' + + 'cryptographic suite (type) and MAY contain a cryptosuite identifier ' + + '(cryptosuite).', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-serialization-ecdsa-jcs-2019'; + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + }); + }); + } +}); diff --git a/tests/90-algorithms-rdfc.js b/tests/90-algorithms-rdfc.js index cab9baf2..aea1789e 100644 --- a/tests/90-algorithms-rdfc.js +++ b/tests/90-algorithms-rdfc.js @@ -3,224 +3,156 @@ * SPDX-License-Identifier: BSD-3-Clause */ import { - config, - createInitialVc, - createValidCredential, - getProofs, + generateCredential, isValidDatetime, - isValidUtf8, + proofExists, + secureCredential, setupReportableTestSuite, setupRow } from './helpers.js'; +import { + assertAllUtf8 +} from './assertions.js'; import chai from 'chai'; import {endpoints} from 'vc-test-suite-implementations'; const should = chai.should(); -const cryptosuite = 'ecdsa-rdfc-2019'; -const {tags} = config.suites[ - cryptosuite +const cryptosuites = [ + 'ecdsa-rdfc-2019', ]; + const {match: issuers} = endpoints.filterByTag({ - tags: [...tags], + tags: cryptosuites, property: 'issuers' }); -describe('ecdsa-rdfc-2019 - Algorithms - Transformation', function() { +describe('Algorithms - Transformation (ecdsa-rdfc-2019)', function() { setupReportableTestSuite(this); this.implemented = [...issuers.keys()]; - let validCredential; - before(async function() { - validCredential = await createValidCredential(); - }); for(const [columnId, {endpoints}] of issuers) { describe(columnId, function() { const [issuer] = endpoints; - let issuedVc; - let proofs; - let rdfc2019Proofs = []; + let securedCredential; before(async function() { - issuedVc = await createInitialVc({issuer, vc: validCredential}); - proofs = getProofs(issuedVc); - if(proofs?.length) { - rdfc2019Proofs = proofs.filter( - proof => proof?.cryptosuite === cryptosuite); - } + securedCredential = await secureCredential( + {issuer, vc: generateCredential()}); }); beforeEach(setupRow); - const assertBefore = () => { - should.exist(issuedVc, 'Expected issuer to have issued a ' + - 'credential.'); - should.exist(proofs, 'Expected credential to have a proof.'); - rdfc2019Proofs.length.should.be.gte(1, 'Expected at least one ' + - 'ecdsa-rdfc-2019 cryptosuite.'); - }; it('The transformation options MUST contain a type identifier ' + - 'for the cryptographic suite (type) and a cryptosuite identifier ' + - '(cryptosuite).', + 'for the cryptographic suite (type) and a cryptosuite identifier ' + + '(cryptosuite).', async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-rdfc-2019'; - assertBefore(); - for(const proof of rdfc2019Proofs) { - should.exist(proof.type, 'Expected a type identifier on ' + - 'the proof.'); - should.exist(proof.cryptosuite, - 'Expected a cryptosuite identifier on the proof.'); - } + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); }); it('Whenever this algorithm encodes strings, ' + - 'it MUST use UTF-8 encoding.', + 'it MUST use UTF-8 encoding.', async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-rdfc-2019'; - assertBefore(); - for(const proof of rdfc2019Proofs) { - should.exist(proof?.proofValue, - 'Expected proofValue to exist.'); - isValidUtf8(proof.proofValue).should.equal( - true, - 'Expected proofValue value to be a valid UTF-8 encoded string.' - ); - } + const proof = proofExists(securedCredential); + assertAllUtf8(proof); }); it('If options.type is not set to the string DataIntegrityProof or ' + - 'options.cryptosuite is not set to the string ecdsa-rdfc-2019, ' + - 'an error MUST be raised and SHOULD convey an error type ' + - 'of PROOF_TRANSFORMATION_ERROR.', + 'options.cryptosuite is not set to the string ecdsa-rdfc-2019, ' + + 'an error MUST be raised and SHOULD convey an error type ' + + 'of PROOF_TRANSFORMATION_ERROR.', async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-rdfc-2019'; - assertBefore(); - for(const proof of rdfc2019Proofs) { - should.exist(proof.type, - 'Expected a type identifier on the proof.'); - should.exist(proof.cryptosuite, - 'Expected a cryptosuite identifier on the proof.'); - proof.type.should.equal('DataIntegrityProof', - 'Expected DataIntegrityProof type.'); - proof.cryptosuite.should.equal('ecdsa-rdfc-2019', - 'Expected ecdsa-rdfc-2019 cryptosuite.'); - } + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + proof.type.should.equal('DataIntegrityProof', + 'Expected DataIntegrityProof type.'); + proof.cryptosuite.should.equal('ecdsa-rdfc-2019', + 'Expected ecdsa-rdfc-2019 cryptosuite.'); }); }); } }); -describe('ecdsa-rdfc-2019 - Algorithms - Proof Configuration', function() { +describe('Algorithms - Proof Configuration (ecdsa-rdfc-2019)', function() { setupReportableTestSuite(this); this.implemented = [...issuers.keys()]; - let validCredential; - before(async function() { - validCredential = await createValidCredential(); - }); for(const [columnId, {endpoints}] of issuers) { describe(columnId, function() { const [issuer] = endpoints; - let issuedVc; - let proofs; - let rdfc2019Proofs = []; + let securedCredential; before(async function() { - issuedVc = await createInitialVc({issuer, vc: validCredential}); - proofs = getProofs(issuedVc); - if(proofs?.length) { - rdfc2019Proofs = proofs.filter( - proof => proof?.cryptosuite === cryptosuite); - } + securedCredential = await secureCredential( + {issuer, vc: generateCredential()}); }); beforeEach(setupRow); - const assertBefore = () => { - should.exist(issuedVc, 'Expected issuer to have issued a ' + - 'credential.'); - should.exist(proofs, 'Expected credential to have a proof.'); - rdfc2019Proofs.length.should.be.gte(1, 'Expected at least one ' + - 'ecdsa-rdfc-2019 cryptosuite.'); - }; it('The proof options MUST contain a type identifier for the ' + - 'cryptographic suite (type) and MUST contain a cryptosuite ' + - 'identifier (cryptosuite).', + 'cryptographic suite (type) and MUST contain a cryptosuite ' + + 'identifier (cryptosuite).', async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-configuration-ecdsa-rdfc-2019'; - assertBefore(); - for(const proof of rdfc2019Proofs) { - should.exist(proof.type, - 'Expected a type identifier on the proof.'); - should.exist(proof.cryptosuite, - 'Expected a cryptosuite identifier on the proof.'); - } + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); }); it('If proofConfig.type is not set to DataIntegrityProof ' + - 'and/or proofConfig.cryptosuite is not set to ecdsa-rdfc-2019, ' + - 'an error MUST be raised and SHOULD convey an error type ' + - 'of PROOF_GENERATION_ERROR.', + 'and/or proofConfig.cryptosuite is not set to ecdsa-rdfc-2019, ' + + 'an error MUST be raised and SHOULD convey an error type ' + + 'of PROOF_GENERATION_ERROR.', async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-configuration-ecdsa-rdfc-2019'; - assertBefore(); - for(const proof of rdfc2019Proofs) { - should.exist(proof.type, - 'Expected a type identifier on the proof.'); - should.exist(proof.cryptosuite, - 'Expected a cryptosuite identifier on the proof.'); - proof.type.should.equal('DataIntegrityProof', - 'Expected DataIntegrityProof type.'); - proof.cryptosuite.should.equal('ecdsa-rdfc-2019', - 'Expected ecdsa-rdfc-2019 cryptosuite.'); - } + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + proof.type.should.equal('DataIntegrityProof', + 'Expected DataIntegrityProof type.'); + proof.cryptosuite.should.equal('ecdsa-rdfc-2019', + 'Expected ecdsa-rdfc-2019 cryptosuite.'); }); it('If proofConfig.created is set and if the value is not a ' + - 'valid [XMLSCHEMA11-2] datetime, an error MUST be raised and ' + - 'SHOULD convey an error type of PROOF_GENERATION_ERROR.', + 'valid [XMLSCHEMA11-2] datetime, an error MUST be raised and ' + + 'SHOULD convey an error type of PROOF_GENERATION_ERROR.', async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-configuration-ecdsa-rdfc-2019'; - for(const proof of rdfc2019Proofs) { - if(proof?.created) { - isValidDatetime(proof.created).should.equal( - true, - 'Expected created value to be a valid datetime string.' - ); - } + const proof = proofExists(securedCredential); + if(proof?.created) { + isValidDatetime(proof.created).should.equal( + true, + 'Expected created value to be a valid datetime string.' + ); } }); }); } }); -describe('ecdsa-rdfc-2019 - Algorithms - Proof Serialization', function() { +describe('Algorithms - Proof Serialization (ecdsa-rdfc-2019)', function() { setupReportableTestSuite(this); this.implemented = [...issuers.keys()]; - let validCredential; - before(async function() { - validCredential = await createValidCredential(); - }); for(const [columnId, {endpoints}] of issuers) { describe(columnId, function() { const [issuer] = endpoints; - let issuedVc; - let proofs; - let rdfc2019Proofs = []; + let securedCredential; before(async function() { - issuedVc = await createInitialVc({issuer, vc: validCredential}); - proofs = getProofs(issuedVc); - if(proofs?.length) { - rdfc2019Proofs = proofs.filter( - proof => proof?.cryptosuite === cryptosuite); - } + securedCredential = await secureCredential( + {issuer, vc: generateCredential()}); }); beforeEach(setupRow); - const assertBefore = () => { - should.exist(issuedVc, 'Expected issuer to have issued a ' + - 'credential.'); - should.exist(proofs, 'Expected credential to have a proof.'); - rdfc2019Proofs.length.should.be.gte(1, 'Expected at least one ' + - 'ecdsa-rdfc-2019 cryptosuite.'); - }; it('The proof options MUST contain a type identifier for the ' + 'cryptographic suite (type) and MAY contain a cryptosuite identifier ' + '(cryptosuite).', async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-serialization-ecdsa-rdfc-2019'; - assertBefore(); - for(const proof of rdfc2019Proofs) { - should.exist(proof.type, - 'Expected a type identifier on the proof.'); - } + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); }); }); } diff --git a/tests/assertions.js b/tests/assertions.js index 5072e82f..0c4ff092 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -5,6 +5,9 @@ import { getBs58Bytes, getBs64UrlBytes, + getProofs, + isValidDatetime, + isValidUtf8, multibaseMultikeyHeaderP256, multibaseMultikeyHeaderP384, } from './helpers.js'; @@ -182,3 +185,97 @@ export function itRejectsInvalidCryptosuite(expectedValidSuites, { await verificationFail({credential, verifier: endpoint}); }); } + +export function assertSecuredCredential(securedCredential) { + should.exist(securedCredential, + 'Expected issuer to have issued a credential.'); + const proofs = getProofs(securedCredential); + should.exist(proofs, + 'Expected credential to have a proof.'); + proofs.length.should.equal(1, + 'Expected credential to have a single proof.'); +} + +export function assertAllUtf8(proof) { + for(const [key, value] of Object.entries(proof)) { + isValidUtf8(value).should.equal( + true, + `Expected ${key} value to be a valid UTF-8 encoded string.` + ); + } +} + +export function assertDataIntegrityProof(proof, cryptosuite) { + if(proof?.id) { + } + should.exist(proof.type, + 'Expected a type on the proof.'); + proof.type.should.equal('DataIntegrityProof', + 'Expected DataIntegrityProof type.'); + isValidUtf8(proof.type).should.equal( + true, + 'Expected type value to be a valid UTF-8 encoded string.' + ); + should.exist(proof.proofPurpose, + 'Expected a proofPurpose on the proof.'); + isValidUtf8(proof.proofPurpose).should.equal( + true, + 'Expected proofPurpose value to be a valid UTF-8 encoded string.' + ); + if(proof?.verificationMethod) { + isValidUtf8(proof.verificationMethod).should.equal( + true, + 'Expected verificationMethod value to be a valid UTF-8 encoded string.' + ); + } + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + proof.cryptosuite.should.equal(cryptosuite, + `Expected {cryptosuite} cryptosuite.`); + isValidUtf8(proof.cryptosuite).should.equal( + true, + 'Expected cryptosuite value to be a valid UTF-8 encoded string.' + ); + if(proof?.created) { + isValidDatetime(proof.created).should.equal( + true, + 'Expected created value to be a valid datetime string.' + ); + isValidUtf8(proof.created).should.equal( + true, + 'Expected created value to be a valid UTF-8 encoded string.' + ); + } + if(proof?.expires) { + isValidDatetime(proof.expires).should.equal( + true, + 'Expected created value to be a valid datetime string.' + ); + isValidUtf8(proof.expires).should.equal( + true, + 'Expected expires value to be a valid UTF-8 encoded string.' + ); + } + if(proof?.domain) { + isValidUtf8(proof.domain).should.equal( + true, + 'Expected domain value to be a valid UTF-8 encoded string.' + ); + } + if(proof?.challenge) { + isValidUtf8(proof.challenge).should.equal( + true, + 'Expected challenge value to be a valid UTF-8 encoded string.' + ); + } + should.exist(proof.proofValue, + 'Expected proof to have proofValue.'); + isValidUtf8(proof.proofValue).should.equal( + true, + 'Expected proofValue value to be a valid UTF-8 encoded string.' + ); + if(proof?.previousProof) { + } + if(proof?.nonce) { + } +} diff --git a/tests/helpers.js b/tests/helpers.js index 3d1f9fcf..4ee64466 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -4,12 +4,15 @@ */ import * as bs58 from 'base58-universal'; import * as bs64 from 'base64url-universal'; +import chai from 'chai'; import {createRequire} from 'node:module'; import {isUtf8} from 'node:buffer'; import {klona} from 'klona'; import {readFileSync} from 'fs'; import {v4 as uuidv4} from 'uuid'; +const should = chai.should(); + export const require = createRequire(import.meta.url); // takes a multibase string starting with z lops the z off @@ -39,7 +42,7 @@ export const ISOTimeStamp = ({date = new Date()} = {}) => { export const config = JSON.parse(readFileSync('./config/runner.json')); -export const createInitialVc = async ({ +export const secureCredential = async ({ issuer, vc, mandatoryPointers, @@ -247,7 +250,7 @@ export function getProofs(issuedVc) { return proofs; } -export function createValidCredential(version = 2) { +export function generateCredential(version = 2) { let credential = { type: ['VerifiableCredential'], id: `urn:uuid:${uuidv4()}`, @@ -269,6 +272,7 @@ export function createValidCredential(version = 2) { 'https://www.w3.org/ns/credentials/v2' ] }, credential); + credential.credentialSubject.name = 'Alice'; } else { return null; } @@ -282,3 +286,30 @@ export function setupRow() { rowId: this.currentTest.title }; } + +export function proofExists(securedCredential) { + should.exist(securedCredential, + 'Expected issuer to have issued a credential.'); + const proofs = getProofs(securedCredential); + should.exist(proofs, + 'Expected credential to have a proof.'); + proofs.length.should.be.gte(1, + 'Expected credential to have at least one proof.'); + return proofs[0]; +} + +export async function verifySuccess(verifier, securedCredential) { + const body = { + verifiableCredential: securedCredential + }; + const response = await verifier.post({json: body}); + should.exist(response.result, 'Expected a result from verifier.'); +} + +export async function verifyError(verifier, securedCredential) { + const body = { + verifiableCredential: securedCredential + }; + const response = await verifier.post({json: body}); + should.exist(response.error, 'Expected an error from verifier.'); +} diff --git a/tests/vectors.js b/tests/vectors.js new file mode 100644 index 00000000..3f05f26e --- /dev/null +++ b/tests/vectors.js @@ -0,0 +1,73 @@ +/*! + * Copyright (c) 2024 Digital Bazaar, Inc. All rights reserved. + */ +/* Note: This file contains data generated from the vc-di-ecdsa specification +test vectors. */ + +/* eslint-disable max-len */ +/* eslint-disable quote-props */ +/* eslint-disable quotes */ +export const ecdsaJcsVectors = { + 'P-256': { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", + "type": [ + "VerifiableCredential", + "AlumniCredential" + ], + "name": "Alumni Credential", + "description": "A minimum viable example of an Alumni Credential.", + "issuer": "https://vc.example/issuers/5678", + "validFrom": "2023-01-01T00:00:00Z", + "credentialSubject": { + "id": "did:example:abcdefgh", + "alumniOf": "The School of Examples" + }, + "proof": { + "type": "DataIntegrityProof", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "cryptosuite": "ecdsa-jcs-2019", + "proofPurpose": "assertionMethod", + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "proofValue": "z48fSpWLud2PXMmBjRnacU3oE4WMHX4J1hx7qjc2K31x1aoVfLPCcpEincvjUg8ptbDnrYgcytmSj51Uj2Ap3a7WB" + } + }, + 'P-384': { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "id": "urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33", + "type": [ + "VerifiableCredential", + "AlumniCredential" + ], + "name": "Alumni Credential", + "description": "A minimum viable example of an Alumni Credential.", + "issuer": "https://vc.example/issuers/5678", + "validFrom": "2023-01-01T00:00:00Z", + "credentialSubject": { + "id": "did:example:abcdefgh", + "alumniOf": "The School of Examples" + }, + "proof": { + "type": "DataIntegrityProof", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "did:key:z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ#z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ", + "cryptosuite": "ecdsa-jcs-2019", + "proofPurpose": "assertionMethod", + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://www.w3.org/ns/credentials/examples/v2" + ], + "proofValue": "zasuhHXVzmepTMgSuyWvCCY3gYbLpANXDYWSuemx7WjopwRr6DDN532dKSMoxqNeQm7BWZxqyurFLSPBDmzaHe57k7JgGRaNtJhjfYjgww19nPWdP7dYqqf1X1LvNEcZJP75" + } + } +};