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

Proof chain tests #110

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@digitalbazaar/http-client": "^4.0.0",
"@digitalbazaar/mocha-w3c-interop-reporter": "^1.6.0",
"@digitalbazaar/multikey-context": "^2.0.1",
"@digitalbazaar/security-document-loader": "^3.0.0",
"@digitalbazaar/vc": "^7.0.0",
"@digitalcredentials/did-context": "^1.0.0",
"base58-universal": "^2.0.0",
Expand Down
4 changes: 4 additions & 0 deletions tests/15-rdfc-di-verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ for(const vcVersion of vectors.vcTypes) {
tags
})
});
const optionalTests = {
proofChain: true
};
// options for the DI Verifier Suite
checkDataIntegrityProofVerifyErrors({
implemented: match,
Expand All @@ -43,5 +46,6 @@ for(const vcVersion of vectors.vcTypes) {
testVector: document,
keyType: 'P-256'
},
optionalTests
});
}
76 changes: 76 additions & 0 deletions tests/75-proof-chains.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*!
* Copyright 2024 Digital Bazaar, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
import {
addProof,
createVc,
generateProofId
} from './vc-issuer/index.js';
import {
setupReportableTestSuite,
setupRow,
verifyError,
verifySuccess
} from './helpers.js';
import {endpoints} from 'vc-test-suite-implementations';

const cryptosuites = [
'ecdsa-rdfc-2019',
];

const {match: issuers} = endpoints.filterByTag({
tags: cryptosuites,
property: 'issuers'
});
issuers;

const {match: verifiers} = endpoints.filterByTag({
tags: cryptosuites,
property: 'verifiers'
});

describe('Proof Chains', function() {
setupReportableTestSuite(this);
for(const [columnId, {endpoints}] of verifiers) {
describe(columnId, function() {
const [verifier] = endpoints;
let issuedCredential;
let issuedProofSet;
let issuedProofChain;
let negativeFixture;
before(async function() {
issuedCredential = await createVc();
issuedProofChain = await addProof(
structuredClone(issuedCredential), issuedCredential.proof[0].id);
issuedProofSet = await addProof(
structuredClone(issuedCredential));
issuedProofSet;
});
beforeEach(setupRow);
it('If a proof with id value equal to the value of previousProof ' +
'does not exist in allProofs, an error MUST be raised and SHOULD ' +
'convey an error type of PROOF_VERIFICATION_ERROR.',
async function() {
this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#verify-proof-sets-and-chains';
await verifySuccess(verifier, issuedProofChain);

negativeFixture = structuredClone(issuedProofChain);
negativeFixture.proof[1].id = generateProofId();
await verifyError(verifier, negativeFixture);
});
it('If any element of previousProof list has an id attribute ' +
'value that does not match the id attribute value of any ' +
'element of allProofs, an error MUST be raised and SHOULD ' +
'convey an error type of PROOF_VERIFICATION_ERROR.',
async function() {
this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#verify-proof-sets-and-chains';
await verifySuccess(verifier, issuedProofChain);

negativeFixture = structuredClone(issuedProofChain);
negativeFixture.proof[1].id = generateProofId();
await verifyError(verifier, negativeFixture);
});
});
}
});
33 changes: 33 additions & 0 deletions tests/vc-issuer/documentLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*!
* Copyright (c) 2023-2024 Digital Bazaar, Inc. All rights reserved.
*/
import dataIntegrityContext from '@digitalbazaar/data-integrity-context';
import multikeyContext from '@digitalbazaar/multikey-context';
import {named} from '@digitalbazaar/credentials-context';
import {securityLoader} from '@digitalbazaar/security-document-loader';

export const loader = securityLoader();

loader.addStatic(
named.get('v2').id,
named.get('v2').context
);

loader.addStatic(
'https://www.w3.org/ns/credentials/examples/v2',
{
'@context': {
'@vocab': 'https://www.w3.org/ns/credentials/examples#'
}
}
);

loader.addStatic(
dataIntegrityContext.constants.CONTEXT_URL,
dataIntegrityContext.contexts.get(dataIntegrityContext.constants.CONTEXT_URL)
);

loader.addStatic(
multikeyContext.constants.CONTEXT_URL,
multikeyContext.contexts.get(multikeyContext.constants.CONTEXT_URL)
);
109 changes: 109 additions & 0 deletions tests/vc-issuer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*!
* Copyright 2024 Digital Bazaar, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
import * as base58 from 'base58-universal';
import * as EcdsaMultikey from '@digitalbazaar/ecdsa-multikey';
import * as rdfCanonize from 'rdf-canonize';
import crypto from 'crypto';
import jsonld from 'jsonld';
import {loader} from './documentLoader.js';

const documentLoader = loader.build();
const publicKeyMultibase = 'zDnaekGZTbQBerwcehBSXLqAg6s55hVEBms1zFy89VHXtJSa9';
const secretKeyMultibase = 'z42tqZ5smVag3DtDhjY9YfVwTMyVHW6SCHJi2ZMrD23DGYS3';
const controller = `did:key:${publicKeyMultibase}`;

export function generateProofId() {
return `urn:uuid:${crypto.randomUUID()}`;
}

const dataIntegrityProof = {
type: 'DataIntegrityProof',
cryptosuite: 'ecdsa-rdfc-2019',
proofPurpose: 'assertionMethod',
verificationMethod: `${controller}#${publicKeyMultibase}`,
};

// create the keypair to use when signing
const keyPair = await EcdsaMultikey.from({
'@context': 'https://w3id.org/security/multikey/v1',
id: `${controller}#${publicKeyMultibase}`,
type: 'Multikey',
controller,
publicKeyMultibase,
secretKeyMultibase
});

// create the unsigned credential
const unsignedCredential = {
'@context': ['https://www.w3.org/ns/credentials/v2'],
type: ['VerifiableCredential'],
issuer: controller,
credentialSubject: {name: 'Alice'}
};

export async function createVc() {
return addProof(unsignedCredential);
}

export async function addProof(credential, previousProof = null) {
const proofSet = credential?.proof || [];
const unsecuredDocument = structuredClone(credential);
delete unsecuredDocument.proof;
const proofOptions = structuredClone(dataIntegrityProof);
if(previousProof) {
// const allProofs = [];
const matchingProofs = proofSet.filter(entry => entry.id === previousProof);
unsecuredDocument.proof = matchingProofs;
proofOptions.previousProof = previousProof;
}

const securedDocument = structuredClone(unsecuredDocument);
const proof = await createProof(unsecuredDocument, proofOptions);
proofSet.push(proof);
securedDocument.proof = proofSet;

return securedDocument;
}

export async function createProof(unsecuredDocument, options) {
// https://www.w3.org/TR/vc-di-ecdsa/#create-proof-ecdsa-rdfc-2019
options.id = generateProofId();
const proof = structuredClone(options);

options['@context'] = unsecuredDocument['@context'];

const proofConfig = await canonize(options);
const proofConfigHash =
crypto.createHash('sha256').update(proofConfig).digest('hex');

const transformedData = await canonize(unsecuredDocument);
const transformedDataHash =
crypto.createHash('sha256').update(transformedData).digest('hex');

const hashData = proofConfigHash + transformedDataHash;
const proofbytes = await keyPair.signer().sign(
{data: Uint8Array.from(Buffer.from(hashData, 'hex'))});

proof.proofValue = `z${base58.encode(proofbytes)}`;

return proof;
}

async function canonize(input) {
const options = {
algorithm: 'RDFC-1.0',
base: null,
documentLoader,
safe: true,
skipExpansion: false,
produceGeneralizedRdf: false,
rdfDirection: 'i18n-datatype',
messageDigestAlgorithm: 'SHA-256',
};
const dataset = await jsonld.toRDF(input, options);
delete options.produceGeneralizedRdf;
options.format = 'application/n-quads';
return rdfCanonize.canonize(dataset, options);
}
Loading