diff --git a/package.json b/package.json index 45743a64..6f196abd 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@digitalcredentials/did-context": "^1.0.0", "base58-universal": "^2.0.0", "base64url-universal": "^2.0.0", + "cbor": "^10.0.3", "chai": "^4.3.7", "chai-string": "^1.5.0", "data-integrity-test-suite-assertion": "github:w3c-ccg/data-integrity-test-suite-assertion", @@ -61,6 +62,7 @@ "json-canon": "^1.0.1", "klona": "^2.0.6", "multibase": "^4.0.6", + "multiformats": "^13.3.1", "mocha": "^10.2.0", "uuid": "^9.0.0", "varint": "^6.0.0", diff --git a/tests/40-sd-create.js b/tests/40-sd-create.js deleted file mode 100644 index b20fe775..00000000 --- a/tests/40-sd-create.js +++ /dev/null @@ -1,224 +0,0 @@ -/*! - * Copyright 2023 Digital Bazaar, Inc. - * SPDX-License-Identifier: BSD-3-Clause - */ -import * as ecdsaSd2023Cryptosuite - from '@digitalbazaar/ecdsa-sd-2023-cryptosuite'; -import {endpointCheck, secureCredential} from './helpers.js'; -import { - shouldBeBs58, - shouldBeBs64UrlNoPad, - shouldBeMulticodecEncoded, - shouldHaveHeaderBytes, - verificationSuccess -} from './assertions.js'; -import {deriveCredential} from './vc-generator/index.js'; -import {documentLoader} from './documentLoader.js'; -import {endpoints} from 'vc-test-suite-implementations'; -import {expect} from 'chai'; -import {getSuiteConfig} from './test-config.js'; -import {localVerifier} from './vc-verifier/index.js'; - -const {tags, credentials, vectors} = getSuiteConfig('ecdsa-sd-2023'); -const {match} = endpoints.filterByTag({ - tags: [...tags], - property: 'issuers' -}); - -const verifier = localVerifier({ - cryptosuite: ecdsaSd2023Cryptosuite.createVerifyCryptosuite() -}); - -describe('ecdsa-sd-2023 (create)', function() { - for(const vcVersion of vectors.vcTypes) { - describe(`ecdsa-sd-2023 (issuers) VC ${vcVersion}`, function() { - this.matrix = true; - this.report = true; - this.implemented = []; - this.rowLabel = 'Test Name'; - this.columnLabel = 'Implementation'; - for(const [name, {endpoints: issuers}] of match) { - for(const keyType of vectors.keyTypes) { - for(const issuer of issuers) { - // does the endpoint support this test? - if(!endpointCheck({endpoint: issuer, keyType, vcVersion})) { - continue; - } - // add implementation name and keyType to report - this.implemented.push(`${name}: ${keyType}`); - describe(`${name}: ${keyType}`, function() { - let issuedVc; - let proofs; - const verificationMethodDocuments = []; - before(async function() { - issuedVc = await secureCredential({ - issuer, - vc: credentials.create[vcVersion].document, - mandatoryPointers: - credentials.create[vcVersion].mandatoryPointers, - vcVersion - }); - // Support multiple proofs - proofs = Array.isArray(issuedVc?.proof) ? issuedVc.proof : - [issuedVc?.proof]; - // only look for verificationMethods if a valid proof is there - if(proofs.filter(Boolean).length) { - const verificationMethods = proofs.map( - proof => proof.verificationMethod); - for(const verificationMethod of verificationMethods) { - const verificationMethodDocument = await documentLoader({ - url: verificationMethod - }); - verificationMethodDocuments.push( - verificationMethodDocument); - } - } - }); - it('The field "cryptosuite" MUST be "ecdsa-sd-2023".', - function() { - this.test.cell = { - columnId: `${name}: ${keyType}`, rowId: this.test.title - }; - proofs.some( - proof => proof.cryptosuite === 'ecdsa-sd-2023' - ).should.equal(true, 'Expected at least one proof to have ' + - '"cryptosuite" property "ecdsa-sd-2023".' - ); - }); - it('the signature value (proofValue) MUST be expressed ' + - 'according to section 7 of [RFC4754] (sometimes referred to ' + - 'as the IEEE P1363 format) and encoded according to the ' + - 'specific cryptosuite proof generation algorithm.', - async function() { - this.test.cell = { - columnId: `${name}: ${keyType}`, rowId: this.test.title - }; - const _proof = proofs.find(p => - p?.cryptosuite === 'ecdsa-sd-2023'); - expect( - _proof, - `Expected VC from issuer ${name} to have an ' + - '"ecdsa-sd-2023" proof`).to.exist; - expect( - _proof.proofValue, - `Expected VC from issuer ${name} to have a ' + - '"proof.proofValue"` - ).to.exist; - expect( - _proof.proofValue, - `Expected VC "proof.proofValue" from issuer ${name} to be ` + - 'a string.' - ).to.be.a.string; - //Ensure the proofValue string starts with u, indicating that it - //is a multibase-base64url-no-pad-encoded value, throwing an - //error if it does not. - expect( - _proof.proofValue.startsWith('u'), - `Expected "proof.proofValue" to start with u received ` + - `${_proof.proofValue[0]}`).to.be.true; - // now test the encoding which is bs64 url no pad for this suite - expect( - shouldBeBs64UrlNoPad(_proof.proofValue), - 'Expected "proof.proofValue" to be bs64 url no pad encoded.' - ).to.be.true; - await shouldHaveHeaderBytes( - _proof.proofValue, - new Uint8Array([0xd9, 0x5d, 0x00]) - ); - }); - it('The field "proofValue" MUST start with "u".', function() { - this.test.cell = { - columnId: `${name}: ${keyType}`, rowId: this.test.title - }; - proofs.some( - proof => proof.proofValue.startsWith('u') - ).should.equal(true, 'Expected at least one proof to have ' + - '"proofValue" property that starts with "u".' - ); - }); - it('The "proof" MUST verify when using a conformant verifier.', - async function() { - this.test.cell = { - columnId: `${name}: ${keyType}`, rowId: this.test.title - }; - const derivedCredential = await deriveCredential({ - verifiableCredential: issuedVc, - suite: 'ecdsa-sd-2023', - selectivePointers: ['/credentialSubject/id'] - }); - - await verificationSuccess({ - credential: derivedCredential, - verifier - }); - }); - it('The "proof.proofPurpose" field MUST match the verification ' + - 'relationship expressed by the verification method controller.', - async function() { - this.test.cell = { - columnId: `${name}: ${keyType}`, rowId: this.test.title - }; - verificationMethodDocuments.should.not.eql([], 'Expected ' + - 'at least one "verificationMethodDocument".'); - verificationMethodDocuments.some( - verificationMethodDocument => - verificationMethodDocument?.type === 'Multikey' - ).should.equal(true, 'Expected at least one proof to have ' + - '"type" property value "Multikey".' - ); - const controllerDocuments = []; - for(const verificationMethodDocument of - verificationMethodDocuments) { - const controllerDocument = await documentLoader({ - url: verificationMethodDocument.controller - }); - controllerDocuments.push(controllerDocument); - } - proofs.some( - proof => controllerDocuments.some(controllerDocument => - controllerDocument.hasOwnProperty(proof.proofPurpose)) - ).should.equal(true, 'Expected "proof.proofPurpose" field ' + - 'to match the verification method controller.' - ); - }); - it('Dereferencing "verificationMethod" MUST result in an ' + - 'object containing a type property with "Multikey" value.', - async function() { - this.test.cell = { - columnId: `${name}: ${keyType}`, rowId: this.test.title - }; - verificationMethodDocuments.should.not.eql([], 'Expected ' + - 'at least one "verificationMethodDocument".'); - verificationMethodDocuments.some( - verificationMethodDocument => - verificationMethodDocument?.type === 'Multikey' - ).should.equal(true, 'Expected at least one proof to have ' + - '"type" property value "Multikey".' - ); - }); - it('The "publicKeyMultibase" property of the verification ' + - 'method MUST be public key encoded according to MULTICODEC and ' + - 'formatted according to MULTIBASE.', async function() { - this.test.cell = { - columnId: `${name}: ${keyType}`, rowId: this.test.title - }; - verificationMethodDocuments.should.not.eql([], 'Expected ' + - '"verificationMethodDocuments" to not be empty.'); - verificationMethodDocuments.some( - verificationMethodDocument => { - const multibase = 'z'; - const {publicKeyMultibase} = verificationMethodDocument; - return publicKeyMultibase.startsWith(multibase) && - shouldBeBs58(publicKeyMultibase) && - shouldBeMulticodecEncoded(publicKeyMultibase); - } - ).should.equal(true, 'Expected at "publicKeyMultibase" to ' + - 'be MULTIBASE formatted and MULTICODEC encoded.'); - }); - }); - } - } - } - }); - } -}); diff --git a/tests/50-sd-verify.js b/tests/50-sd-verify.js deleted file mode 100644 index c1ae32c0..00000000 --- a/tests/50-sd-verify.js +++ /dev/null @@ -1,176 +0,0 @@ -/*! - * Copyright 2023 Digital Bazaar, Inc. - * SPDX-License-Identifier: BSD-3-Clause - */ -import {verificationFail, verificationSuccess} from './assertions.js'; -import {defineSuiteConformanceTests} from './suiteTests.js'; -import {getSuiteConfig} from './test-config.js'; -import {klona} from 'klona'; -import {sdVerifySetup} from './setup.js'; - -const suite = 'ecdsa-sd-2023'; -const { - credentials, - vectors -} = getSuiteConfig(suite); -const {keyTypes} = vectors; - -describe('ecdsa-sd-2019 (verify)', async function() { - let testVectors = new Map(); - let signedCredentials = []; - let disclosedCredentials = []; - let nestedDisclosedCredentials = []; - let disclosedDlCredentialNoIds = []; - let disclosedCredentialsWithFullArray = []; - let disclosedCredentialsWithLessThanFullSubArray = []; - let disclosedCredentialsWithoutFirstArrayElement = []; - - function defineTestVectorMapping(vcVersion) { - // Make sure sdVerifySetup is called first - if(Object.keys(testVectors).length === 0) { - throw new Error('testVectors not populated. Was map instantiated?'); - } - - const getImplementationVectors = ({vectors}) => - keyTypes.map(type => vectors.get(type). - get(vcVersion)).filter(Boolean); - - signedCredentials = getImplementationVectors({ - vectors: testVectors.signed - }); - disclosedCredentials = getImplementationVectors({ - vectors: testVectors.disclosed.base - }); - nestedDisclosedCredentials = getImplementationVectors({ - vectors: testVectors.disclosed.nested - }); - disclosedDlCredentialNoIds = getImplementationVectors({ - vectors: testVectors.disclosed.noIds - }); - disclosedCredentialsWithFullArray = getImplementationVectors({ - vectors: testVectors.disclosed.array.full - }); - disclosedCredentialsWithLessThanFullSubArray = - getImplementationVectors({ - vectors: testVectors.disclosed.array.lessThanFull - }); - disclosedCredentialsWithoutFirstArrayElement = - getImplementationVectors({ - vectors: testVectors.disclosed.array.missingElements - }); - } - - await defineSuiteConformanceTests({ - suite, - testCategory: 'verifiers', - // We provide a custom mapped test vector for each VC version instead. - buildTestVectorsFn: () => {}, - }, function({endpoint: verifier, vcVersion}) { - before(async function() { - testVectors = await sdVerifySetup({ - credentials, - keyTypes, - suite - }); - - // Define the test vector mapping for convienence. - defineTestVectorMapping(vcVersion); - }); - - function assertTestVector(vector) { - if(!vector || vector.length === 0) { - throw new Error('No test vector provided.'); - } - } - - it('MUST verify a valid VC with an ecdsa-sd-2023 proof.', - async function() { - assertTestVector(disclosedCredentials); - for(const credential of disclosedCredentials) { - await verificationSuccess({credential, verifier}); - } - }); - it('MUST verify a valid VC with nested disclosed properties.', - async function() { - assertTestVector(nestedDisclosedCredentials); - for(const credential of nestedDisclosedCredentials) { - await verificationSuccess({credential, verifier}); - } - }); - it('MUST verify a valid VC with disclosed properties and bnodes.', - async function() { - assertTestVector(disclosedDlCredentialNoIds); - for(const credential of disclosedDlCredentialNoIds) { - await verificationSuccess({credential, verifier}); - } - }); - it('MUST verify with full array revealed properties', - async function() { - assertTestVector(disclosedCredentialsWithFullArray); - for(const credential of disclosedCredentialsWithFullArray) { - await verificationSuccess({credential, verifier}); - } - }); - it('MUST verify with fewer array revealed properties', - async function() { - assertTestVector(disclosedCredentialsWithLessThanFullSubArray); - for(const credential of - disclosedCredentialsWithLessThanFullSubArray) { - await verificationSuccess({credential, verifier}); - } - }); - it('MUST verify w/o first element revealed properties', - async function() { - assertTestVector(disclosedCredentialsWithoutFirstArrayElement); - for(const credential of - disclosedCredentialsWithoutFirstArrayElement) { - await verificationSuccess({credential, verifier}); - } - }); - it('If the "proofValue" string does not start with "u", an ' + - 'error MUST be raised.', async function() { - assertTestVector(disclosedCredentials); - for(const credential of disclosedCredentials) { - const signedCredentialCopy = klona(credential); - // intentionally modify proofValue to not start with 'u' - signedCredentialCopy.proof.proofValue = 'a'; - await verificationFail({ - credential: signedCredentialCopy, verifier - }); - } - }); - it('If the "cryptosuite" field is not the string ' + - '"ecdsa-sd-2023", an error MUST be raised.', async function() { - assertTestVector(disclosedCredentials); - for(const credential of disclosedCredentials) { - const signedCredentialCopy = klona(credential); - signedCredentialCopy.proof.cryptosuite = - 'invalid-cryptosuite'; - await verificationFail({ - credential: signedCredentialCopy, verifier - }); - } - }); - it('MUST fail to verify a base proof.', async function() { - assertTestVector(signedCredentials); - for(const credential of signedCredentials) { - const signedCredentialCopy = klona(credential); - await verificationFail({ - credential: signedCredentialCopy, verifier - }); - } - }); - it('MUST fail to verify a modified disclosed credential.', - async function() { - assertTestVector(disclosedCredentials); - for(const credential of disclosedCredentials) { - const signedCredentialCopy = klona(credential); - // intentionally modify `credentialSubject` ID - signedCredentialCopy.credentialSubject.id = 'urn:invalid'; - await verificationFail({ - credential: signedCredentialCopy, verifier - }); - } - }); - }); -}); diff --git a/tests/90-algorithms-jcs.js b/tests/90-algorithms-jcs.js index be69bfe6..e3d64f2d 100644 --- a/tests/90-algorithms-jcs.js +++ b/tests/90-algorithms-jcs.js @@ -4,6 +4,7 @@ */ import { assertAllUtf8, + assertCryptosuiteProof, assertDataIntegrityProof } from './assertions.js'; import { @@ -98,7 +99,7 @@ describe('Algorithms - Verify Proof (ecdsa-jcs-2019)', function() { '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'; + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#verify-proof-ecdsa-jcs-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 @@ -139,13 +140,12 @@ describe('Algorithms - Transformation (ecdsa-jcs-2019)', function() { 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('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 ' + @@ -153,14 +153,7 @@ describe('Algorithms - Transformation (ecdsa-jcs-2019)', function() { 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.'); + assertCryptosuiteProof(proof, 'ecdsa-jcs-2019'); }); }); } @@ -196,14 +189,7 @@ describe('Algorithms - Proof Configuration (ecdsa-jcs-2019)', function() { 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.'); + assertCryptosuiteProof(proof, 'ecdsa-jcs-2019'); }); it('If proofConfig.created is set and if the value is not a ' + 'valid [XMLSCHEMA11-2] datetime, an error MUST be raised and ' + diff --git a/tests/90-algorithms-rdfc.js b/tests/90-algorithms-rdfc.js index aea1789e..9d1bb621 100644 --- a/tests/90-algorithms-rdfc.js +++ b/tests/90-algorithms-rdfc.js @@ -2,19 +2,25 @@ * Copyright 2024 Digital Bazaar, Inc. * SPDX-License-Identifier: BSD-3-Clause */ +import { + assertAllUtf8, + assertCryptosuiteProof, + assertDataIntegrityProof +} from './assertions.js'; import { generateCredential, isValidDatetime, proofExists, secureCredential, setupReportableTestSuite, - setupRow + setupRow, + verifyError, + verifySuccess } from './helpers.js'; -import { - assertAllUtf8 -} from './assertions.js'; import chai from 'chai'; +import {ecdsaRdfcVectors} from './vectors.js'; import {endpoints} from 'vc-test-suite-implementations'; +import {expect} from 'chai'; const should = chai.should(); @@ -27,6 +33,74 @@ const {match: issuers} = endpoints.filterByTag({ property: 'issuers' }); +const {match: verifiers} = endpoints.filterByTag({ + tags: cryptosuites, + property: 'verifiers' +}); + +describe('Algorithms - Create Proof (ecdsa-rdfc-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-rdfc-2019'; + const proof = proofExists(securedCredential); + assertDataIntegrityProof(proof); + // Since we are not sending proof options, we only do a positive test + }); + 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-rdfc-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-rdfc-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(ecdsaRdfcVectors[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-rdfc-2019)', function() { setupReportableTestSuite(this); this.implemented = [...issuers.keys()]; @@ -40,8 +114,8 @@ describe('Algorithms - Transformation (ecdsa-rdfc-2019)', function() { }); beforeEach(setupRow); 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'; const proof = proofExists(securedCredential); @@ -50,13 +124,12 @@ describe('Algorithms - Transformation (ecdsa-rdfc-2019)', function() { 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-rdfc-2019'; - const proof = proofExists(securedCredential); - assertAllUtf8(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-rdfc-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-rdfc-2019, ' + 'an error MUST be raised and SHOULD convey an error type ' + @@ -64,14 +137,7 @@ describe('Algorithms - Transformation (ecdsa-rdfc-2019)', function() { async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-rdfc-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-rdfc-2019', - 'Expected ecdsa-rdfc-2019 cryptosuite.'); + assertCryptosuiteProof(proof, 'ecdsa-rdfc-2019'); }); }); } @@ -107,14 +173,7 @@ describe('Algorithms - Proof Configuration (ecdsa-rdfc-2019)', function() { async function() { this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-configuration-ecdsa-rdfc-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-rdfc-2019', - 'Expected ecdsa-rdfc-2019 cryptosuite.'); + assertCryptosuiteProof(proof, 'ecdsa-rdfc-2019'); }); it('If proofConfig.created is set and if the value is not a ' + 'valid [XMLSCHEMA11-2] datetime, an error MUST be raised and ' + diff --git a/tests/90-algorithms-sd.js b/tests/90-algorithms-sd.js new file mode 100644 index 00000000..dcaf4418 --- /dev/null +++ b/tests/90-algorithms-sd.js @@ -0,0 +1,246 @@ +/*! + * Copyright 2024 Digital Bazaar, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ +import { + assertAllUtf8, + assertCryptosuiteProof, + assertDataIntegrityProof +} from './assertions.js'; +import { + encodeSdDerivedProofValue, + generateCredential, + inspectSdBaseProofValue, + inspectSdDerivedProofValue, + isValidDatetime, + proofExists, + secureCredential, + setupReportableTestSuite, + setupRow, + verifyError, + verifySuccess +} from './helpers.js'; +import chai from 'chai'; +import {ecdsaSdVectors} from './vectors.js'; +import {endpoints} from 'vc-test-suite-implementations'; +import {expect} from 'chai'; + +const should = chai.should(); + +const cryptosuites = [ + 'ecdsa-sd-2023', +]; + +const {match: issuers} = endpoints.filterByTag({ + tags: cryptosuites, + property: 'issuers' +}); + +const {match: verifiers} = endpoints.filterByTag({ + tags: cryptosuites, + property: 'verifiers' +}); + +describe('Algorithms - Create Base Proof (ecdsa-sd-2023)', function() { + setupReportableTestSuite(this); + this.implemented = [...issuers.keys()]; + for(const [columnId, {endpoints}] of issuers) { + describe(columnId, function() { + const [issuer] = endpoints; + let securedCredential; + before(async function() { + const mandatoryPointers = ['/credentialSubject/name']; + securedCredential = await secureCredential( + {issuer, vc: generateCredential(), mandatoryPointers}); + }); + 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-sd-2023'; + const proof = proofExists(securedCredential); + assertDataIntegrityProof(proof); + // We only do a positive test + }); + it('Let proof.proofValue be a base64-url-encoded ' + + 'Multibase value of the proofBytes.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#create-proof-ecdsa-sd-2023'; + // Shallow multibase test + // TODO try decoding + const proof = proofExists(securedCredential); + should.exist(proof.proofValue, + 'Expected proof to have proofValue.'); + expect(proof.proofValue.startsWith('u')).to.be.true; + }); + }); + } +}); + +describe('Algorithms - Base Proof Transformation (ecdsa-sd-2023)', function() { + setupReportableTestSuite(this); + this.implemented = [...issuers.keys()]; + for(const [columnId, {endpoints}] of issuers) { + describe(columnId, function() { + const [issuer] = endpoints; + let securedCredential; + before(async function() { + const mandatoryPointers = ['/credentialSubject/name']; + securedCredential = await secureCredential( + {issuer, vc: generateCredential(), mandatoryPointers}); + }); + beforeEach(setupRow); + it('The transformation options MUST contain a type identifier for the ' + + 'cryptographic suite (type), a cryptosuite identifier (cryptosuite), ' + + 'and a verification method (verificationMethod).', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-serialization-ecdsa-sd-2023'; + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite on the proof.'); + should.exist(proof.verificationMethod, + 'Expected a verificationMethod on the proof.'); + }); + it('The transformation options MUST contain an array of mandatory ' + + 'JSON pointers (mandatoryPointers) and MAY contain additional ' + + 'options, such as a JSON-LD document loader.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-sd-2023'; + + // Send an issuance request without mandatoryPointers + const securedCredentialNoPointers = await secureCredential( + {issuer, vc: generateCredential()}); + const proof = proofExists(securedCredentialNoPointers); + const decodedProof = + await inspectSdBaseProofValue(proof); + // Ensure default mandatoryPointers are injected by application + should.exist(decodedProof.mandatoryPointers, + 'Expected mandatoryPointers to be included in the proofValue.'); + }); + 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-sd-2023'; + const proof = proofExists(securedCredential); + assertAllUtf8(proof); + }); + it('Per the recommendations of [RFC2104], the HMAC key MUST be the ' + + 'same length as the digest size; for SHA-256, this is 256 bits ' + + 'or 32 bytes.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#transformation-ecdsa-sd-2023'; + const proof = proofExists(securedCredential); + const decodedProof = await inspectSdBaseProofValue(proof); + decodedProof.hmacKey.length.should.equal(32, + 'Expected HMAC key to be the same length as the digest size.' + ); + }); + }); + } +}); + +describe('Algorithms - Base Proof Configuration (ecdsa-sd-2023)', function() { + setupReportableTestSuite(this); + this.implemented = [...issuers.keys()]; + for(const [columnId, {endpoints}] of issuers) { + describe(columnId, function() { + const [issuer] = endpoints; + let securedCredential; + before(async function() { + const mandatoryPointers = ['/credentialSubject/name']; + securedCredential = await secureCredential( + {issuer, vc: generateCredential(), mandatoryPointers}); + }); + 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-sd-2023'; + 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-sd-2023, ' + + '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-sd-2023'; + const proof = proofExists(securedCredential); + assertCryptosuiteProof(proof, 'ecdsa-sd-2023'); + }); + 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-sd-2023'; + const proof = proofExists(securedCredential); + if(proof?.created) { + isValidDatetime(proof.created).should.equal( + true, + 'Expected created value to be a valid datetime string.' + ); + } + }); + }); + } +}); + +describe('Algorithms - Base Proof Serialization (ecdsa-sd-2023)', function() { + setupReportableTestSuite(this); + this.implemented = [...issuers.keys()]; + for(const [columnId, {endpoints}] of issuers) { + describe(columnId, function() { + const [issuer] = endpoints; + let securedCredential; + before(async function() { + const mandatoryPointers = ['/credentialSubject/name']; + securedCredential = await secureCredential( + {issuer, vc: generateCredential(), mandatoryPointers}); + }); + 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-sd-2023'; + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type identifier on the proof.'); + }); + }); + } +}); + +describe('Algorithms - Verify Derived Proof (ecdsa-sd-2023)', function() { + setupReportableTestSuite(this); + this.implemented = [...verifiers.keys()]; + for(const [columnId, {endpoints}] of verifiers) { + describe(columnId, function() { + const [verifier] = endpoints; + beforeEach(setupRow); + it('If the length of signatures does not match the length of ' + + 'nonMandatory, an error MUST be raised and SHOULD convey an ' + + 'error type of PROOF_VERIFICATION_ERROR, indicating that the ' + + 'signature count does not match the non-mandatory message count.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#proof-serialization-ecdsa-sd-2023'; + + const validDerivedProof = structuredClone(ecdsaSdVectors.derivedProof); + await verifySuccess(verifier, validDerivedProof); + + // Instanciate a new signed credential and remove a signature + const invalidDerivedProof = structuredClone(validDerivedProof); + const decodedDerivedProofValue = + await inspectSdDerivedProofValue(invalidDerivedProof.proof); + decodedDerivedProofValue.signatures = + decodedDerivedProofValue.signatures.slice(1); + invalidDerivedProof.proof.proofValue = + encodeSdDerivedProofValue(decodedDerivedProofValue); + await verifyError(verifier, invalidDerivedProof); + }); + }); + } +}); diff --git a/tests/assertions.js b/tests/assertions.js index e7869fb9..e144d870 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -205,6 +205,17 @@ export function assertAllUtf8(proof) { } } +export function assertCryptosuiteProof(proof, cryptosuite) { + should.exist(proof.type, + 'Expected a type on the proof.'); + proof.type.should.equal('DataIntegrityProof', + 'Expected DataIntegrityProof type.'); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + proof.cryptosuite.should.equal(cryptosuite, + `Expected {cryptosuite} cryptosuite.`); +} + export function assertDataIntegrityProof(proof) { if(proof?.id) { } diff --git a/tests/helpers.js b/tests/helpers.js index 0d946ca3..53865d58 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -6,10 +6,13 @@ import * as bs58 from 'base58-universal'; import * as bs64 from 'base64url-universal'; import * as didKey from '@digitalbazaar/did-method-key'; import * as EcdsaMultikey from '@digitalbazaar/ecdsa-multikey'; +import {bases} from 'multiformats/basics'; import {CachedResolver} from '@digitalbazaar/did-io'; +import cbor from 'cbor'; import chai from 'chai'; import {createRequire} from 'node:module'; import {contexts as credContexts} from '@digitalbazaar/credentials-context'; +import {expect} from 'chai'; import {isUtf8} from 'node:buffer'; import {JsonLdDocumentLoader} from 'jsonld-document-loader'; import {klona} from 'klona'; @@ -344,3 +347,49 @@ export async function multikeyFromVerificationMethod( } return null; } + +export async function inspectSdBaseProofValue(proof) { + const proofValue = proof.proofValue; + expect(proof.proofValue.startsWith('u')).to.be.true; + const cborProof = bases.base64url.decode(proofValue); + const decodedProof = await cbor.decodeFirst(cborProof, (error, obj) => { + return obj; + }); + const decodedProofValues = decodedProof.value; + decodedProofValues.length.should.equal(5, + 'Expected decoded proof value to be of length 5.' + ); + return { + baseSignature: decodedProofValues[0], + publicKey: decodedProofValues[1], + hmacKey: decodedProofValues[2], + signatures: decodedProofValues[3], + mandatoryPointers: decodedProofValues[4] + }; +} + +export async function inspectSdDerivedProofValue(proof) { + const proofValue = proof.proofValue; + expect(proof.proofValue.startsWith('u')).to.be.true; + const cborProof = bases.base64url.decode(proofValue); + const decodedProof = await cbor.decodeFirst(cborProof, (error, obj) => { + return obj; + }); + const decodedProofValues = decodedProof.value; + decodedProofValues.length.should.equal(5, + 'Expected decoded proof value to be of length 5.' + ); + return { + baseSignature: decodedProofValues[0], + publicKey: decodedProofValues[1], + signatures: decodedProofValues[2], + labelMap: decodedProofValues[3], + mandatoryIndexes: decodedProofValues[4] + }; +} + +export async function encodeSdDerivedProofValue(decodedPproof) { + const cborProof = await cbor.encode(decodedPproof); + const proofValue = bases.base64url.encode(cborProof); + return proofValue; +} diff --git a/tests/vectors.js b/tests/vectors.js index 3f05f26e..1869f744 100644 --- a/tests/vectors.js +++ b/tests/vectors.js @@ -7,6 +7,168 @@ test vectors. */ /* eslint-disable max-len */ /* eslint-disable quote-props */ /* eslint-disable quotes */ +export const ecdsaSdVectors = { + baseProof: { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 5.5, + "sailName": "Kihei", + "year": 2023 + }, + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7, + "sailName": "Lahaina", + "year": 2020 + }, + { + "size": 7.8, + "sailName": "Lahaina", + "year": 2023 + } + ], + "boards": [ + { + "boardName": "CompFoil170", + "brand": "Wailea", + "year": 2022 + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + }, + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-sd-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0AhVhAkWKMO8zpRmfcUMksHUMtZM7cJt8PmNLsljKTYhSi8gZ7wAWnK4BrOZkrH3dZvxKWlnxGG_0xlFXmU5sa5-j71VgjgCQCKnLOGbY_FuM-ASpSkkOxsIR2E8n7Ml2q1UQ6tEwzi5NYIAARIjNEVWZ3iJmqu8zd7v8AESIzRFVmd4iZqrvM3e7_jlhAKVYKM250DDcNWOYQpUmYC1Z5NZhJRwie8vVUev94QGst83WhoW7_UM6JULsKjNVHjxZlZQyovN4xw1M_mhn6TFhAsqMSgz0EeaPe0Hmo5SN1JNZmCjiZ-CNJB4ScmyK46s7hDotNZuGHxKGaAFC43O0FxcKeUC96q_z9PGeF5C3VpVhAmeoEU8I1ZzxFyR-QMxwoSkqRG9E8_CaSrhH8TD2t-tV32HKAC4hJkKl6xHuz6XL2G-V0cm6d_rWozjhmmVaMjVhAbQMMckpcMAEo15WC6C8Mo3bCEWFGtOTkMxND-LJMdfkCSovB7RnCR7SXzk5-0YVigtJ5Fzg71AAob5yg1WNNk1hApQHlYRGlUVkv-WX1OjJYJ19Ow7ipvVwUvm90Sn3IjNRLuy9pr5DHm3wVlVMPVpLqjS-E8_jJDeJV5pY0bfK_A1hAas2wx9bcEj0Sh7t8w9Cj-2FpceGpdRhaLZxYs1ZEG8-obUjb0CHOyH8S7uwDtn7oSW2oCW2SpZvlX-2jW17rmlhAe34eQ8-gJHyQahY0EmZh8mZoy0svnpTjkdcLnroLIBsiVkfCzMKLOWeEtWZUVnIBeugT8I2C7mnmpHNjdo2d4lhAM8okCUX8F4GYx9rlnSDvr5pTHPOjOOJ47JzFdDtX_Q4bZxWwLGwqltYojDecyt4oxQHYz55ZRnhTXLHqa74B7VhAO_Hj0vxsuJZzpVGtgoMKK2ZlGKvhLX3_vUCvdL-MTlszVr2iC3XJpCbOc8B_W_On-csaLPzUSvlSDtNec1ZVk1hAdm2Ht4sv_ec3s1HRqeul--yEGx4SrpwyNQRdLa5ZKyJDgqr4h-EtVNzc-J-VllvKrHN8wBKtUqarqI4Npnrx7VhAORMLXYz3l59Ozc7SDk2ej7clrer9Bn6eaBUQG773AqQ56bc-oGXeemekwZCNHjFLOESNoNq7qetO8FRbiFHb4FhAW-otSFVlUPFmg119n3TeSE7up5hBS34AqP2TGUQA5pDGyOTetrf8qq3bWj1lpCu1Z6yEZJlQ6nrLiCoaNVhpL1hA1wW_HhsTPUfUlqMX6ZMsLem8hbWaFe_rZDpPp5NN02vMHInDjO1Gn0BrXUyVAMnTY3fGrDjsuy2sGgMzR-bo11hAvOGSXH51eRoCWtV9LlpZD10ix0IuuVCnat5fRxU7hqGs0AzM09kGsmuDMRjowp51xhiFJ3iMajIOOhWUhPxHCoVnL2lzc3VlcngdL2NyZWRlbnRpYWxTdWJqZWN0L3NhaWxOdW1iZXJ4Gi9jcmVkZW50aWFsU3ViamVjdC9zYWlscy8xeCAvY3JlZGVudGlhbFN1YmplY3QvYm9hcmRzLzAveWVhcngaL2NyZWRlbnRpYWxTdWJqZWN0L3NhaWxzLzI" + } + }, + derivedProof: { + "@context": [ + "https://www.w3.org/ns/credentials/v2", + { + "@vocab": "https://windsurf.grotto-networking.com/selective#" + } + ], + "type": [ + "VerifiableCredential" + ], + "issuer": "https://vc.example/windsurf/racecommittee", + "credentialSubject": { + "sailNumber": "Earth101", + "sails": [ + { + "size": 6.1, + "sailName": "Lahaina", + "year": 2023 + }, + { + "size": 7, + "sailName": "Lahaina", + "year": 2020 + } + ], + "boards": [ + { + "year": 2022, + "boardName": "CompFoil170", + "brand": "Wailea" + }, + { + "boardName": "Kanaha Custom", + "brand": "Wailea", + "year": 2019 + } + ] + }, + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "ecdsa-sd-2023", + "created": "2023-08-15T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "u2V0BhVhAkWKMO8zpRmfcUMksHUMtZM7cJt8PmNLsljKTYhSi8gZ7wAWnK4BrOZkrH3dZvxKWlnxGG_0xlFXmU5sa5-j71VgjgCQCKnLOGbY_FuM-ASpSkkOxsIR2E8n7Ml2q1UQ6tEwzi5OGWEBtAwxySlwwASjXlYLoLwyjdsIRYUa05OQzE0P4skx1-QJKi8HtGcJHtJfOTn7RhWKC0nkXODvUAChvnKDVY02TWEClAeVhEaVRWS_5ZfU6MlgnX07DuKm9XBS-b3RKfciM1Eu7L2mvkMebfBWVUw9WkuqNL4Tz-MkN4lXmljRt8r8DWEBqzbDH1twSPRKHu3zD0KP7YWlx4al1GFotnFizVkQbz6htSNvQIc7IfxLu7AO2fuhJbagJbZKlm-Vf7aNbXuuaWEA78ePS_Gy4lnOlUa2CgworZmUYq-Etff-9QK90v4xOWzNWvaILdcmkJs5zwH9b86f5yxos_NRK-VIO015zVlWTWEB2bYe3iy_95zezUdGp66X77IQbHhKunDI1BF0trlkrIkOCqviH4S1U3Nz4n5WWW8qsc3zAEq1Spquojg2mevHtWEA5EwtdjPeXn07NztIOTZ6PtyWt6v0Gfp5oFRAbvvcCpDnptz6gZd56Z6TBkI0eMUs4RI2g2rup607wVFuIUdvgpgBYIOGCDmZ9TBxEtWeCI9oVmRt0eHRGAaoOXx08gxL2IQt_AVggVkUuBrlOaELGVQWJD4M_qW5bcKEHWGNbOrPA_qAOKKwCWCBD6o5lQOWjNGwaTjq7H2Cn1-NPbwXLeDedy2YyiqL9TQNYIJEdvfdRibsv05I3pv8e6S1aUuAuBpGQHLhrYj4QX0knBFggk0AeXgJ4e6m1XsV5-xFud0L_1mUjZ9Mffhg5aZGTyDkFWCDYgT4e07o_IdCwae6qE7WZfpXtGRFESEXR3SxZmXE05o4AAQIFBggJCg4PEBESEw" + } + } +}; +export const ecdsaRdfcVectors = { + '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", + "cryptosuite": "ecdsa-rdfc-2019", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "did:key:zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP", + "proofPurpose": "assertionMethod", + "proofValue": "zaHXrr7AQdydBk3ahpCDpWbxfLokDqmCToYm2dyWvpcFVyWooC2he63w1f7UNQoAMKdhaRtcnaE2KTo5o5vTCcfw" + } + }, + '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", + "cryptosuite": "ecdsa-rdfc-2019", + "created": "2023-02-24T23:36:38Z", + "verificationMethod": "did:key:z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ#z82LkuBieyGShVBhvtE2zoiD6Kma4tJGFtkAhxR5pfkp5QPw4LutoYWhvQCnGjdVn14kujQ", + "proofPurpose": "assertionMethod", + "proofValue": "z967Mvv5bxtmLNqTzPZ8KmJjFmFXaAKeQNzq7GWnQkMcLtaGSSmuozE5WtJ8PipMe178B1tE28K1vsJur9bGVJhz6jgSJsRHFSQeqgH8hhjcg8gZDFJC1b9FsR5ggNmDBqHv" + } + } +}; export const ecdsaJcsVectors = { 'P-256': { "@context": [