Skip to content

Commit

Permalink
add local proof chain issuer
Browse files Browse the repository at this point in the history
Signed-off-by: PatStLouis <[email protected]>
  • Loading branch information
PatStLouis committed Dec 16, 2024
1 parent cab6b26 commit d8b6034
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 0 deletions.
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
62 changes: 62 additions & 0 deletions tests/75-proof-chains.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*!
* Copyright 2024 Digital Bazaar, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
import {
addProof,
createVc,
} from './vc-issuer/index.js';
import {
setupReportableTestSuite,
setupRow,
} from './helpers.js';
import {endpoints} from 'vc-test-suite-implementations';

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

const {match: issuers} = endpoints.filterByTag({

Check failure on line 19 in tests/75-proof-chains.js

View workflow job for this annotation

GitHub Actions / lint (20.x)

'issuers' is assigned a value but never used
tags: cryptosuites,
property: '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;

Check failure on line 33 in tests/75-proof-chains.js

View workflow job for this annotation

GitHub Actions / lint (20.x)

'verifier' is assigned a value but never used
let issuedCredential;
let issuedProofSet;
let issuedProofChain;

Check failure on line 36 in tests/75-proof-chains.js

View workflow job for this annotation

GitHub Actions / lint (20.x)

'issuedProofChain' is assigned a value but never used
before(async function() {
// signedCredential = await addProof(
// generateCredential());
issuedCredential = await createVc();
issuedProofSet = await addProof(
structuredClone(issuedCredential));
issuedProofChain = await addProof(
structuredClone(issuedProofSet), issuedCredential.proof[0].id);
});
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';
});
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';
});
});
}
});
24 changes: 24 additions & 0 deletions tests/vc-issuer/documentLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*!
* 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(
dataIntegrityContext.constants.CONTEXT_URL,
dataIntegrityContext.contexts.get(dataIntegrityContext.constants.CONTEXT_URL)
);

loader.addStatic(
multikeyContext.constants.CONTEXT_URL,
multikeyContext.contexts.get(multikeyContext.constants.CONTEXT_URL)
);
124 changes: 124 additions & 0 deletions tests/vc-issuer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*!
* 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 {DataIntegrityProof} from '@digitalbazaar/data-integrity';
// import {cryptosuite as ecdsaRdfc2019Cryptosuite} from
// '@digitalbazaar/ecdsa-rdfc-2019-cryptosuite';
// import jsigs from 'jsonld-signatures';
import jsonld from 'jsonld';
// const {purposes: {AssertionProofPurpose}} = jsigs;
import {loader} from './documentLoader.js';

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

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 suite
// const suite = new DataIntegrityProof({
// signer: keyPair.signer(), cryptosuite: ecdsaRdfc2019Cryptosuite
// });

// 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 unsecuredDocument = structuredClone(unsignedCredential);
// const signedDocument = await jsigs.sign(credential, {
// suite,
// purpose: new AssertionProofPurpose(),
// documentLoader
// });
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();

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

const hashData = Buffer.concat([proofConfigHash, transformedDataHash]);

const proofbytes = await keyPair.signer().sign({data: hashData});

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

return proof;
}

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

0 comments on commit d8b6034

Please sign in to comment.