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

Move component count test to verifier suite & update to 7 components #63

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion tests/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const shouldNotUseCborTags = ({proof}) => {

export const baseProofShouldHaveElementCount = ({
proof,
expectedLengths = [5, 6],
expectedLengths = [5, 6, 7],
reason = 'Expected baseProof to have expected number of components'
}) => {
let error;
Expand Down
19 changes: 19 additions & 0 deletions tests/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import {
getMultikeys,
issueCredentials
} from './vc-generator/index.js';
import {
encodeProofValue,
parseDisclosureProofValue
} from './vc-generator/stubMethods.js';
import {generators} from 'data-integrity-test-suite-assertion';
import {writeFile} from 'node:fs/promises';

Expand Down Expand Up @@ -144,6 +148,21 @@ export async function verifySetup({credentials, keyTypes, suite}) {
}
}
disclosed.invalid.valuePrefix = valuePrefix;
// invalid element count means less than 4 components
const componentCount = disclosed.invalid.componentCount = new Map();
// use the basic disclosed vc
for(const [keyType, versions] of disclosed?.basic) {
componentCount.set(keyType, new Map());
for(const [vcVersion, vc] of versions) {
const modifiedVc = structuredClone(vc);
const params = parseDisclosureProofValue({proof: modifiedVc.proof});
// create a payload with only 2 components
const payload = [params.bbsProof, params.presentationHeader];
// replace the existing proofValue with the smaller payload
modifiedVc.proof.proofValue = encodeProofValue({payload});
componentCount.get(keyType).set(vcVersion, modifiedVc);
}
}
return {
base,
disclosed
Expand Down
14 changes: 1 addition & 13 deletions tests/suites/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-3-Clause
*/
import {
baseProofShouldHaveElementCount,
checkEncoding,
checkHmacKeyLength,
shouldBeMultibaseEncoded,
Expand Down Expand Up @@ -266,23 +265,12 @@ export function createSuite({
}
});
it('The transformation options MUST contain an array of mandatory ' +
'JSON pointers (mandatoryPointers)', function() {
'JSON pointers (mandatoryPointers).', function() {
this.test.link = 'https://w3c.github.io/vc-di-bbs/#:~:text=The%20transformation%20options%20MUST%20contain%20an%20array%20of%20mandatory%20JSON%20pointers%20(mandatoryPointers)';
for(const proof of bbsProofs) {
shouldHaveMandatoryPointers({proof});
}
});
it('Initialize components to an array that is the result of ' +
'CBOR-decoding the bytes that follow the three-byte BBS disclosure ' +
'proof header. If the result is not an array of five or six elements ' +
'— a byte array, a map of integers to integers, two arrays of ' +
'integers, and one or two byte arrays; an error MUST be raised and ' +
'SHOULD convey an error type of PROOF_VERIFICATION_ERROR.', function() {
this.test.link = 'https://w3c.github.io/vc-di-bbs/#:~:text=%22pseudonym_hidden_pid%22.-,Initialize%20components%20to%20an%20array%20that%20is%20the%20result%20of%20CBOR,be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.,-Replace%20the%20second';
for(const proof of bbsProofs) {
baseProofShouldHaveElementCount({proof});
}
});
it.skip(' If featureOption is set to "anonymous_holder_binding" or ' +
'"pseudonym_hidden_pid", the commitment_with_proof input MUST be ' +
'supplied.', async function() {
Expand Down
12 changes: 12 additions & 0 deletions tests/suites/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@ export function verifySuite({
const credential = cloneTestVector(disclosed?.invalid?.valuePrefix);
await verificationFail({credential, verifier});
});
it('If the result is not an array of five, six, or seven elements ' +
'— a byte array, a map of integers to integers, two arrays of ' +
'integers, and one or two byte arrays — an error MUST be raised ' +
Comment on lines +147 to +149
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1 byte array
+ 1 map of integers to integers
+ 2 arrays of integers

That's 4 elements.
Add 1 or 2 byte arrays, and that's 5 or 6 elements, total.

What is the possible 7th element?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TallTed this is direct from the latest spec here: https://w3c.github.io/vc-di-bbs/#parsederivedproofvalue

I was under the impression that the 7th element was due to changes in related bbs specs WRT to the key, blind signing, and pseudonym signing, but I'm not aware of exactly why this came in. It looks like those changes were made here: w3c/vc-di-bbs#181

@Wind4Greg do you know what the 7th element refers to in this statement? I believe this statement was merged in because the blind and pseudonym specs changed.

It sounds like this might be an issue for the spec itself though and not the test suite.

Copy link
Contributor Author

@aljones15 aljones15 Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first paragraph of 3.3.7 parseDerivedProofValue

A single derived proof value object is produced as output, which contains a set of six or seven elements, having the names "bbsProof", "labelMap", "mandatoryIndexes", "selectiveIndexes", "presentationHeader", "featureOption", and, depending on the value of the featureOption parameter, "pseudonym" and/or "lengthBBSMessages".

— is internally inconsistent (the set may have six, seven, or eight elements) and it disagrees with the last paragraph of that algorithm (again, should say "six, seven, or eight elements" including "featureOption", "pseudonym" and/or "lengthBBSMessages") —

Return derived proof value as an object with properties set to the five, six, or seven elements, using the names "bbsProof", "labelMap", "mandatoryIndexes", "selectiveIndexes", "presentationHeader", and optional "pseudonym" and/or "lengthBBSMessages", respectively. In addition, add featureOption and its value to the object.

These should be brought into agreement. Whatever the result is, it should then be applied to this part of the test suite.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to #64 which will probably move to the https://github.com/w3c/vc-di-bbs/ repo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#64 is untouched. The enumeration of the elements (a byte array, a map of integers to integers, two arrays of integers, and one or two byte arrays) here does not agree with the possible counts indicated (five, six, or seven elements). The enumeration only leads to five or six elements; there is no indication of a possible seventh.

'and SHOULD convey an error type of PROOF_VERIFICATION_ERROR.',
async function() {
this.test.link = 'https://w3c.github.io/vc-di-bbs/#:~:text=If%20the%20result%20is%20not%20an%20array%20of%20five%2C%20six%2C%20or%20seven%20elements%20%E2%80%94%20a%20byte%20array%2C%20a%20map%20of%20integers%20to%20integers%2C%20two%20arrays%20of%20integers%2C%20and%20one%20or%20two%20byte%20arrays%3B%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR';
await verificationFail({
credential: cloneTestVector(disclosed?.invalid?.componentCount),
verifier,
reason: 'Should not verify a disclosed VC w/ less than 4 components'
});
});
});
}
});
Expand Down
59 changes: 59 additions & 0 deletions tests/vc-generator/stubMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,10 @@ function serializeDisclosureProofValue({
// Uint8Array
presentationHeader
];
return encodeProofValue({payload, typeEncoders});
}

export function encodeProofValue({payload, typeEncoders}) {
const cbor = concatBuffers([
CBOR_PREFIX_DERIVED, cborg.encode(payload, {useMaps: true, typeEncoders})
]);
Expand Down Expand Up @@ -435,3 +439,58 @@ async function _findProof({proofId, proofSet, dataIntegrityProof}) {
export function stringToUtf8Bytes({str, utfOffset = 0}) {
return TEXT_ENCODER.encode(str).map(b => b + utfOffset);
}

export function parseDisclosureProofValue({proof} = {}) {
try {
if(typeof proof?.proofValue !== 'string') {
throw new TypeError('"proof.proofValue" must be a string.');
}
if(proof.proofValue[0] !== 'u') {
throw new Error('Only base64url multibase encoding is supported.');
}

// decode from base64url
const proofValue = base64url.decode(proof.proofValue.slice(1));
if(!_startsWithBytes(proofValue, CBOR_PREFIX_DERIVED)) {
throw new TypeError('"proof.proofValue" must be a derived proof.');
}

const payload = proofValue.subarray(CBOR_PREFIX_DERIVED.length);
const [
bbsProof,
compressedLabelMap,
mandatoryIndexes,
selectiveIndexes,
presentationHeader
] = cborg.decode(payload, {useMaps: true, tags: TAGS});

const labelMap = _decompressLabelMap(compressedLabelMap);
const params = {
bbsProof, labelMap, mandatoryIndexes, selectiveIndexes,
presentationHeader
};
return params;
} catch(e) {
const err = new TypeError(
'The proof does not include a valid "proofValue" property.');
err.cause = e;
throw err;
}
}

function _decompressLabelMap(compressedLabelMap) {
const map = new Map();
for(const [k, v] of compressedLabelMap.entries()) {
map.set(`c14n${k}`, `b${v}`);
}
return map;
}

function _startsWithBytes(buffer, prefix) {
for(let i = 0; i < prefix.length; ++i) {
if(buffer[i] !== prefix[i]) {
return false;
}
}
return true;
}