diff --git a/package.json b/package.json index 6e2c091..45743a6 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "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", + "jsonld-document-loader": "^2.2.0", "json-canon": "^1.0.1", "klona": "^2.0.6", "multibase": "^4.0.6", diff --git a/tests/70-data-model.js b/tests/70-data-model.js new file mode 100644 index 0000000..2d8d79b --- /dev/null +++ b/tests/70-data-model.js @@ -0,0 +1,116 @@ +/*! + * Copyright 2024 Digital Bazaar, Inc. + * SPDX-License-Identifier: BSD-3-Clause + */ +import { + generateCredential, + multikeyFromVerificationMethod, + proofExists, + secureCredential, + setupReportableTestSuite, + setupRow +} from './helpers.js'; +import { + assertDataIntegrityProof +} from './assertions.js'; +import chai from 'chai'; +import {endpoints} from 'vc-test-suite-implementations'; +import {expect} from 'chai'; + +const should = chai.should(); + +const cryptosuites = [ + 'ecdsa-sd-2023', + 'ecdsa-jcs-2019', + 'ecdsa-rdfc-2019', +]; + +const {match: issuers} = endpoints.filterByTag({ + tags: cryptosuites, + property: 'issuers' +}); + +describe('Data Model - Verification Methods (Multikey)', 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 publicKeyMultibase value of the verification method ' + + 'MUST start with the base-58-btc prefix (z), as defined in ' + + 'the Multibase section of Controller Documents 1.0.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#dataintegrityproof'; + const proof = proofExists(securedCredential); + const verificationMethod = proof.verificationMethod; + // Only did key is supported + const keyType = issuer.settings.supportedEcdsaKeyTypes[0]; + const multikey = + await multikeyFromVerificationMethod(verificationMethod, keyType); + expect(multikey.startsWith('z')).to.be.true; + }); + it('A Multibase-encoded ECDSA 256-bit public key value or an ' + + 'ECDSA 384-bit public key value follows, as defined in the Multikey ' + + 'section of Controller Documents 1.0. Any other encoding ' + + 'MUST NOT be allowed.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#dataintegrityproof'; + const proof = proofExists(securedCredential); + const verificationMethod = proof.verificationMethod; + // Only did key is supported + const keyType = issuer.settings.supportedEcdsaKeyTypes[0]; + const multikey = + await multikeyFromVerificationMethod(verificationMethod, keyType); + expect(multikey).to.exist; + }); + }); + } +}); + +describe('Data Model - Proof Representations', 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 proof contains the attributes specified in the ' + + 'Proofs section of [VC-DATA-INTEGRITY].', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#dataintegrityproof'; + const proof = proofExists(securedCredential); + assertDataIntegrityProof(proof); + }); + it('The type property MUST be DataIntegrityProof.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#dataintegrityproof'; + const proof = proofExists(securedCredential); + should.exist(proof.type, + 'Expected a type on the proof.'); + proof.type.should.equal('DataIntegrityProof', + 'Expected DataIntegrityProof type.'); + }); + it('The cryptosuite property MUST be ecdsa-rdfc-2019, ' + + 'ecdsa-jcs-2019, or ecdsa-sd-2023.', + async function() { + this.test.link = 'https://www.w3.org/TR/vc-di-ecdsa/#dataintegrityproof'; + const proof = proofExists(securedCredential); + should.exist(proof.cryptosuite, + 'Expected a cryptosuite identifier on the proof.'); + proof.cryptosuite.should.be.oneOf(cryptosuites, + `Expected cryptosuite for be one of ${cryptosuites}.`); + }); + }); + } +}); diff --git a/tests/90-algorithms-jcs.js b/tests/90-algorithms-jcs.js index 4ac1812..be69bfe 100644 --- a/tests/90-algorithms-jcs.js +++ b/tests/90-algorithms-jcs.js @@ -54,7 +54,7 @@ describe('Algorithms - Create Proof (ecdsa-jcs-2019)', function() { 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'); + assertDataIntegrityProof(proof); // Since we are not sending proof options, we only do a positive test }); it('If unsecuredDocument.@context is present, ' + diff --git a/tests/assertions.js b/tests/assertions.js index 0c4ff09..e7869fb 100644 --- a/tests/assertions.js +++ b/tests/assertions.js @@ -205,7 +205,7 @@ export function assertAllUtf8(proof) { } } -export function assertDataIntegrityProof(proof, cryptosuite) { +export function assertDataIntegrityProof(proof) { if(proof?.id) { } should.exist(proof.type, @@ -230,8 +230,6 @@ export function assertDataIntegrityProof(proof, cryptosuite) { } 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.' diff --git a/tests/helpers.js b/tests/helpers.js index 4ee6446..0d946ca 100644 --- a/tests/helpers.js +++ b/tests/helpers.js @@ -4,9 +4,14 @@ */ 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 {CachedResolver} from '@digitalbazaar/did-io'; import chai from 'chai'; import {createRequire} from 'node:module'; +import {contexts as credContexts} from '@digitalbazaar/credentials-context'; import {isUtf8} from 'node:buffer'; +import {JsonLdDocumentLoader} from 'jsonld-document-loader'; import {klona} from 'klona'; import {readFileSync} from 'fs'; import {v4 as uuidv4} from 'uuid'; @@ -313,3 +318,29 @@ export async function verifyError(verifier, securedCredential) { const response = await verifier.post({json: body}); should.exist(response.error, 'Expected an error from verifier.'); } + +export async function multikeyFromVerificationMethod( + verificationMethod, keyType) { + const cachedResolver = new CachedResolver(); + const jdl = new JsonLdDocumentLoader(); + jdl.addDocuments({documents: credContexts}); + jdl.setDidResolver(cachedResolver); + const didKeyDriverMultikey = didKey.driver(); + const prefixes = { + Ed25519: 'z6Mk', + 'P-256': 'zDna', + 'P-384': 'z82L' + }; + try { + didKeyDriverMultikey.use({ + multibaseMultikeyHeader: prefixes[keyType], + fromMultibase: EcdsaMultikey.from + }); + cachedResolver.use(didKeyDriverMultikey); + const response = await jdl.documentLoader(verificationMethod); + return response.document.publicKeyMultibase; + } catch(error) { + // Do nothing on error + } + return null; +}