Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions tests/90-algorithms-jcs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*!
* Copyright 2024 Digital Bazaar, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
import {ecdsaJcs2019Algorithms} from './suites/algorithms-jcs.js';

ecdsaJcs2019Algorithms();
75 changes: 75 additions & 0 deletions tests/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import * as bs58 from 'base58-universal';
import * as bs64 from 'base64url-universal';
import {createRequire} from 'node:module';
import {isUtf8} from 'node:buffer';
import {klona} from 'klona';
import {readFileSync} from 'fs';
import {v4 as uuidv4} from 'uuid';

export const require = createRequire(import.meta.url);
Expand Down Expand Up @@ -216,3 +218,76 @@ export function setupReportableTestSuite(runnerContext, name) {

runnerContext.implemented = [];
}

export function isValidUtf8(string) {
const textEncoder = new TextEncoder();
const uint8Array = textEncoder.encode(string);
if(!isUtf8(uint8Array)) {
return false;
} else {
return true;
}
}

export function isValidDatetime(dateString) {
return !isNaN(Date.parse(dateString));
}

export function setupMatrix(match) {
// this will tell the report
// to make an interop matrix with this suite
this.matrix = true;
this.report = true;
this.implemented = [...match.keys()];
this.rowLabel = 'Test Name';
this.columnLabel = 'Implementer';
}

export const config = JSON.parse(readFileSync('./config/runner.json'));

export function createValidCredential(version = 2) {
let credential = {
type: ['VerifiableCredential'],
id: `urn:uuid:${uuidv4()}`,
credentialSubject: {id: 'did:example:alice'}
};
if(version === 1) {
// add v1.1 context and issuanceDate
credential = Object.assign({}, {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/security/data-integrity/v2'
],
issuanceDate: ISOTimeStamp()
}, credential);
} else if(version === 2) {
// add v2 context
credential = Object.assign({}, {
'@context': [
'https://www.w3.org/ns/credentials/v2'
]
}, credential);
} else {
return null;
}
return credential;
}

export function setupRow() {
// append test meta data to the it/test this.
this.currentTest.cell = {
columnId: this.currentTest.parent.title,
rowId: this.currentTest.title
};
}

export function getProofs(issuedVc) {
// if the implementation failed to issue a VC or to sign the VC, return
// an empty array
if(!issuedVc?.proof) {
return [];
}
const proofs = Array.isArray(issuedVc?.proof) ?
issuedVc.proof : [issuedVc?.proof];
return proofs;
}
228 changes: 228 additions & 0 deletions tests/suites/algorithms-jcs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*!
* Copyright 2024 Digital Bazaar, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
import {
config,
createInitialVc,
createValidCredential,
getProofs,
setupMatrix,
setupRow
} from '../helpers.js';
import {isValidDatetime, isValidUtf8} from './helpers.js';
import chai from 'chai';
import {endpoints} from 'vc-test-suite-implementations';

export function ecdsaJcs2019Algorithms() {
const cryptosuite = 'ecdsa-jcs-2022';
const {tags} = config.suites[
cryptosuite
];
const {issuerMatch} = endpoints.filterByTag({
tags: [...tags],
property: 'issuers'
});
// const {verifierMatch} = endpoints.filterByTag({
// tags: [...tags],
// property: 'verifiers'
// });
const should = chai.should();

describe('ecdsa-jcs-2019 - Algorithms - Transformation', function() {
setupMatrix.call(this, issuerMatch);
let validCredential;
before(async function() {
validCredential = await createValidCredential();
});
for(const [columnId, {endpoints}] of issuerMatch) {
describe(columnId, function() {
const [issuer] = endpoints;
let issuedVc;
let proofs;
let ecdsa2022Proofs = [];
before(async function() {
issuedVc = await createInitialVc({issuer, vc: validCredential});
proofs = getProofs(issuedVc);
if(proofs?.length) {
ecdsa2022Proofs = proofs.filter(
proof => proof?.cryptosuite === cryptosuite);
}
});
beforeEach(setupRow);
const assertBefore = () => {
should.exist(issuedVc, 'Expected issuer to have issued a ' +
'credential.');
should.exist(proofs, 'Expected credential to have a proof.');
ecdsa2022Proofs.length.should.be.gte(1, 'Expected at least one ' +
'ecdsa-jcs-2019 cryptosuite.');
};
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';
assertBefore();
for(const proof of ecdsa2022Proofs) {
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';
assertBefore();
for(const proof of ecdsa2022Proofs) {
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.'
);
}
});
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';
assertBefore();
for(const proof of ecdsa2022Proofs) {
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-2022',
'Expected ecdsa-jcs-2022 cryptosuite.');
}
});
});
}
});

describe('ecdsa-jcs-2019 - Algorithms - Proof Configuration', function() {
setupMatrix.call(this, issuerMatch);
let validCredential;
before(async function() {
validCredential = await createValidCredential();
});
for(const [columnId, {endpoints}] of issuerMatch) {
describe(columnId, function() {
const [issuer] = endpoints;
let issuedVc;
let proofs;
let ecdsa2022Proofs = [];
before(async function() {
issuedVc = await createInitialVc({issuer, vc: validCredential});
proofs = getProofs(issuedVc);
if(proofs?.length) {
ecdsa2022Proofs = proofs.filter(
proof => proof?.cryptosuite === cryptosuite);
}
});
beforeEach(setupRow);
const assertBefore = () => {
should.exist(issuedVc, 'Expected issuer to have issued a ' +
'credential.');
should.exist(proofs, 'Expected credential to have a proof.');
ecdsa2022Proofs.length.should.be.gte(1, 'Expected at least one ' +
'ecdsa-jcs-2019 cryptosuite.');
};
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';
assertBefore();
for(const proof of ecdsa2022Proofs) {
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';
assertBefore();
for(const proof of ecdsa2022Proofs) {
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-2022',
'Expected ecdsa-jcs-2022 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';
for(const proof of ecdsa2022Proofs) {
if(proof?.created) {
isValidDatetime(proof.created).should.equal(
true,
'Expected created value to be a valid datetime string.'
);
}
}
});
});
}
});

describe('ecdsa-jcs-2019 - Algorithms - Transformation', function() {
setupMatrix.call(this, issuerMatch);
let validCredential;
before(async function() {
validCredential = await createValidCredential();
});
for(const [columnId, {endpoints}] of issuerMatch) {
describe(columnId, function() {
const [issuer] = endpoints;
let issuedVc;
let proofs;
let ecdsa2022Proofs = [];
before(async function() {
issuedVc = await createInitialVc({issuer, vc: validCredential});
proofs = getProofs(issuedVc);
if(proofs?.length) {
ecdsa2022Proofs = proofs.filter(
proof => proof?.cryptosuite === cryptosuite);
}
});
beforeEach(setupRow);
const assertBefore = () => {
should.exist(issuedVc, 'Expected issuer to have issued a ' +
'credential.');
should.exist(proofs, 'Expected credential to have a proof.');
ecdsa2022Proofs.length.should.be.gte(1, 'Expected at least one ' +
'ecdsa-jcs-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-jcs-2019';
assertBefore();
for(const proof of ecdsa2022Proofs) {
should.exist(proof.type,
'Expected a type identifier on the proof.');
}
});
});
}
});
}