Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JCS Algorithms file #97

Merged
merged 13 commits into from
Nov 13, 2024
12 changes: 11 additions & 1 deletion config/runner.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@
"P-384": 96
},
"interop": {
"vcVersion": "1.1"
"vcVersion": "2.0"
}
},
"ecdsa-jcs-2019": {
"tags": ["ecdsa-jcs-2019"],
"proofLengths": {
"P-256": 64,
aljones15 marked this conversation as resolved.
Show resolved Hide resolved
"P-384": 96
},
"interop": {
"vcVersion": "2.0"
}
},
"ecdsa-sd-2023": {
Expand Down
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();
73 changes: 70 additions & 3 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 @@ -90,7 +92,7 @@ export const endpointCheck = ({endpoint, vcVersion, keyType}) => {
const {
supportedEcdsaKeyTypes,
// assume support for vc 1.1
aljones15 marked this conversation as resolved.
Show resolved Hide resolved
supports = {vc: ['1.1']}
supports = {vc: ['2.0']}
} = endpoint.settings;
// if an issuer does not support the current keyType skip it
const keyTypes = supportedEcdsaKeyTypes || supports?.keyTypes;
Expand Down Expand Up @@ -208,11 +210,76 @@ export function getColumnNameForTestCategory(testCategory) {
}
}

export function setupReportableTestSuite(runnerContext, name) {
export function setupReportableTestSuite(
runnerContext,
name = 'Implementation'
) {
runnerContext.matrix = true;
runnerContext.report = true;
runnerContext.rowLabel = 'Test Name';
runnerContext.columnLabel = 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 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,
isValidDatetime,
isValidUtf8,
setupReportableTestSuite,
setupRow
} from '../helpers.js';
import chai from 'chai';
import {endpoints} from 'vc-test-suite-implementations';

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

describe('ecdsa-jcs-2019 - Algorithms - Transformation', 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 jcs2019Proofs = [];
before(async function() {
issuedVc = await createInitialVc({issuer, vc: validCredential});
proofs = getProofs(issuedVc);
if(proofs?.length) {
jcs2019Proofs = 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.');
jcs2019Proofs.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 jcs2019Proofs) {
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 jcs2019Proofs) {
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 jcs2019Proofs) {
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('ecdsa-jcs-2019 - Algorithms - Proof Configuration', 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 jcs2019Proofs = [];
before(async function() {
issuedVc = await createInitialVc({issuer, vc: validCredential});
proofs = getProofs(issuedVc);
if(proofs?.length) {
jcs2019Proofs = 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.');
jcs2019Proofs.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 jcs2019Proofs) {
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 jcs2019Proofs) {
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';
for(const proof of jcs2019Proofs) {
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() {
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 jcs2019Proofs = [];
before(async function() {
issuedVc = await createInitialVc({issuer, vc: validCredential});
proofs = getProofs(issuedVc);
if(proofs?.length) {
jcs2019Proofs = 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.');
jcs2019Proofs.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 jcs2019Proofs) {
should.exist(proof.type,
'Expected a type identifier on the proof.');
}
});
});
}
});
}