Skip to content

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

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
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 ' +
'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;
}