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

Add proof chain tests #110

Merged
merged 13 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export function checkDataIntegrityProofFormat({
optionalTests = {
dates: false,
contextInjection: false,
domain: false
domain: false,
proofChain: false
}
} = {}) {
return describe(testDescription, function() {
Expand Down Expand Up @@ -106,7 +107,8 @@ export function checkDataIntegrityProofVerifyErrors({
testDataOptions,
optionalTests = {
dates: false,
authentication: false
authentication: false,
proofChain: false
}
} = {}) {
return describe(testDescription, async function() {
Expand Down
30 changes: 13 additions & 17 deletions suites/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,23 +216,6 @@ export function runDataIntegrityProofFormatTests({
shouldHaveProofValue({proof, expectedPrefix, encodingName});
}
});
it('If "proof.previousProof" property exists, it MUST be a string value ' +
'or unordered list of string values.',
function() {
for(const proof of proofs) {
if(proof.previousProof) {
proof.previousProof.should.be.
oneOf(['string', 'object'], 'Expected ' +
'"proof.previousProof" to be a string or an array.');
if(typeof proof.previousProof === 'object') {
for(const previousProof in proof.previousProof) {
previousProof.should.be.a('string', 'Expected ' +
'"previousProof" items to be a string.');
}
}
}
}
});
it('Cryptographic suite designers MUST use mandatory proof value ' +
'properties defined in Section 2.1 Proofs, and MAY define other ' +
'properties specific to their cryptographic suite.', async function() {
Expand Down Expand Up @@ -420,6 +403,19 @@ export function runDataIntegrityProofFormatTests({
}
});
}
if(optionalTests.proofChain) {
it('If "proof.previousProof" property exists, it MUST be a string ' +
'value or unordered list of string values.', function() {
for(const proof of proofs) {
if(proof.previousProof) {
aljones15 marked this conversation as resolved.
Show resolved Hide resolved
isStringOrArrayOfStrings(proof.previousProof).should.equal(
true,
'"proof.previousProof" should be a string or an Array of strings.'
);
}
}
});
}
});
}

60 changes: 60 additions & 0 deletions suites/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,66 @@ export function runDataIntegrityProofVerifyTests({
});
});
}
if(optionalTests.proofChain) {
it('An OPTIONAL string value (proof.previousProof) or unordered list ' +
'of string values. Each value identifies another data integrity proof ' +
'that MUST verify before the current proof is processed.',
async function() {
this.test.link = 'https://w3c.github.io/vc-data-integrity/#proofs:~:text=An%20OPTIONAL%20string%20value%20or%20unordered%20list%20of%20string%20values.%20Each%20value%20identifies%20another%20data%20integrity%20proof%20that%20MUST%20verify%20before%20the%20current%20proof%20is%20processed';
await verificationSuccess({
credential: credentials.clone('previousProofString'),
verifier,
reason: 'Should verify VC with a string "proof.previousProof".'
});
await verificationSuccess({
credential: credentials.clone('previousProofArray'),
verifier,
reason: 'Should verify VC with an Array "proof.previousProof".'
});
});
it('If an unordered list (proof), all referenced proofs in ' +
'the array MUST verify.', async function() {
this.test.link = 'https://w3c.github.io/vc-data-integrity/#proofs:~:text=If%20an%20unordered%20list%2C%20all%20referenced%20proofs%20in%20the%20array%20MUST%20verify';
await verificationSuccess({
credential: credentials.clone('proofSet'),
verifier,
reason: 'Should verify VC with multiple proofs.'
});
});
it('If a proof with id equal to 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://w3c.github.io/vc-data-integrity/#:~:text=If%20a%20proof%20with%20id%20equal%20to%20previousProof%20does%20not%20exist%20in%20allProofs%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR';
await verificationFail({
credential: credentials.clone('missingPreviousProofString'),
verifier,
reason: 'Should not verify VC with invalid "proof.previousProof".'
});
});
it('If any element of previousProof list has an id attribute that ' +
'does not match the id attribute 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://w3c.github.io/vc-data-integrity/#:~:text=If%20any%20element%20of%20previousProof%20list%20has%20an%20id%20attribute%20that%20does%20not%20match%20the%20id%20attribute%20of%20any%20element%20of%20allProofs%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.';
await verificationFail({
credential: credentials.clone('missingPreviousProofArray'),
verifier,
reason: 'Should not verify VC with invalid "proof.previousProof".'
});
});
it('Each value identifies another data integrity proof, all of which ' +
'MUST also verify for the current proof to be considered verified',
async function() {
this.test.link = 'https://w3c.github.io/vc-data-integrity/#:~:text=Each%20value%20identifies%20another%20data%20integrity%20proof%2C%20all%20of%20which%20MUST%20also%20verify%20for%20the%20current%20proof%20to%20be%20considered%20verified';
await verificationFail({
credential: credentials.clone('previousProofFail'),
verifier,
reason: 'Should not verify VC with a "previousProof" that does ' +
'not verify.'
});

});
}
});
}

Expand Down
9 changes: 6 additions & 3 deletions tests/fixtures/cryptosuites.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export const cryptosuites = [{
keyType: 'P-256',
optionalTests: {
dates: true,
authentication: true
authentication: true,
proofChain: true
},
cryptosuite: ecdsaRdfc2019Cryptosuite,
multikey: EcdsaMultikey,
Expand All @@ -65,7 +66,8 @@ export const cryptosuites = [{
keyType: 'P-384',
optionalTests: {
dates: true,
authentication: true
authentication: true,
proofChain: true
},
cryptosuite: ecdsaRdfc2019Cryptosuite,
multikey: EcdsaMultikey,
Expand All @@ -85,7 +87,8 @@ export const cryptosuites = [{
suiteName: 'eddsa-rdfc-2022',
optionalTests: {
dates: true,
authentication: true
authentication: true,
proofChain: true
},
cryptosuite: eddsaRdfc2022CryptoSuite,
multikey: Ed25519Multikey,
Expand Down
141 changes: 140 additions & 1 deletion vc-generator/generators.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import jsigs from 'jsonld-signatures';
const {AuthenticationProofPurpose} = jsigs.purposes;
const {CredentialIssuancePurpose} = vc;

// default gen just passes params to issueCloned
const defaultGen = params => params;

// generator categories
export const generators = {
// creates test vectors for `proof.created` & `proof.expires`
Expand Down Expand Up @@ -36,7 +39,13 @@ export const generators = {
invalidProofType,
invalidBaseUrl,
invalidVm,
undefinedTerm
undefinedTerm,
previousProofString: defaultGen,
previousProofFail: defaultGen,
previousProofArray: defaultGen,
missingPreviousProofString: defaultGen,
missingPreviousProofArray: defaultGen,
proofSet: defaultGen
},
// creates a set of shared test vector generators
// not necessarily used in DI Assertion itself, but used
Expand All @@ -57,6 +66,131 @@ export const setups = {
});
}
return getSuites({...args});
},
previousProofString({
cryptosuite,
signer,
mandatoryPointers,
selectivePointers,
proofId = 'urn:uuid:test:first:proof'
}) {
const {suite: firstSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
firstSuite.proof = {id: proofId};
const {suite: secondSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
secondSuite.proof = {previousProof: proofId};
return {
suites: [firstSuite, secondSuite]
};
},
previousProofFail({
cryptosuite,
signer,
mandatoryPointers,
selectivePointers,
proofId = 'urn:uuid:test:first:proof'

}) {
const {suite: firstSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
firstSuite.proof = {id: proofId};
const {suite: secondSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
secondSuite.proof = {previousProof: proofId};
return {
suites: [firstSuite, secondSuite]
};
},
previousProofArray({
cryptosuite,
signer,
mandatoryPointers,
selectivePointers,
proofId = 'urn:uuid:test:first:proof'
}) {
const {suite: firstSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
firstSuite.proof = {id: proofId};
const {suite: secondSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
secondSuite.proof = {previousProof: [proofId]};
return {
suites: [firstSuite, secondSuite]
};

},
missingPreviousProofString({
cryptosuite,
signer,
mandatoryPointers,
selectivePointers,
proofId = 'urn:uuid:test:first:proof'
}) {
const {suite: firstSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
firstSuite.proof = {id: proofId};
const {suite: secondSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
secondSuite.proof = {previousProof: 'urn:uuid:test:missing:proof'};
return {
suites: [firstSuite, secondSuite]
};
},
missingPreviousProofArray({
cryptosuite,
signer,
mandatoryPointers,
selectivePointers,
proofId = 'urn:uuid:test:first:proof'
}) {
const {suite: firstSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
firstSuite.proof = {id: proofId};
const {suite: secondSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
secondSuite.proof = {previousProof: ['urn:uuid:test:missing:proof']};
return {
suites: [firstSuite, secondSuite]
};
},
proofSet({
cryptosuite,
signer,
mandatoryPointers,
selectivePointers,
}) {
const {suite: firstSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
const {suite: secondSuite} = getSuites({
cryptosuite, signer,
mandatoryPointers, selectivePointers
});
return {
suites: [firstSuite, secondSuite]
};
}
};

Expand All @@ -70,6 +204,11 @@ export const cleanups = {
return !('@base' in c);
});
return issuedCredential;
},
previousProofFail({issuedCredential}) {
// make the first proof fail verification
issuedCredential.proof[0].proofValue = 'invalidProofValue';
return issuedCredential;
}
};
/**
Expand Down
4 changes: 2 additions & 2 deletions vc-generator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ export async function generateTestData({
// if a generator has a specific setup use it
// otherwise getSuites is fine
const setup = setups[id] || getSuites;
const {suite, selectiveSuite} = setup({
const {suite, suites, selectiveSuite} = setup({
cryptosuite,
signer,
mandatoryPointers,
selectivePointers,
verify
});
const issuedCredential = await issueCloned(generator({
suite, selectiveSuite,
suite, suites, selectiveSuite,
credential, loader: documentLoader
}));
const cleanup = cleanups[id];
Expand Down
18 changes: 11 additions & 7 deletions vc-generator/issuer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {CredentialIssuancePurpose} = vc;
*
* @param {object} options - Options to use.
* @param {object} options.suite - A DataIntegrityProof.
* @param {Array<object>} options.suites - DataIntegrityProof(s).
* @param {object} [options.selectiveSuite] - A D.I. Proof for a selective
* suite.
* @param {object} options.credential - A credential to be signed.
Expand All @@ -23,15 +24,18 @@ const {CredentialIssuancePurpose} = vc;
* @returns {Promise<object>} - An issued VC.
*/
export async function issueCloned({
suite, selectiveSuite, credential, loader = defaultLoader,
suite, suites, selectiveSuite, credential, loader = defaultLoader,
purpose = new CredentialIssuancePurpose(),
}) {
const verifiableCredential = await vc.issue({
credential: structuredClone(credential),
suite,
documentLoader: loader,
purpose
});
let verifiableCredential = structuredClone(credential);
for(const _suite of (suites ? suites : [suite])) {
verifiableCredential = await vc.issue({
credential: verifiableCredential,
suite: _suite,
documentLoader: loader,
purpose
});
}
if(!selectiveSuite) {
return verifiableCredential;
}
Expand Down
Loading