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 3 ecdsa-sd-2023 proof value tests (D -> C) #101

Open
wants to merge 7 commits into
base: add-cbor-tests-sd
Choose a base branch
from
Open
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
46 changes: 40 additions & 6 deletions tests/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {klona} from 'klona';
import varint from 'varint';

const should = chai.should();
const {expect} = chai;

// RegExp with bs58 characters in it
const bs58 =
Expand All @@ -29,18 +30,18 @@ export const proofBytes = {
'P-384': 96
};

export const shouldHaveByteLength = async (
export const shouldHaveByteLength = (
multibaseString,
expectedByteLength
) => {
const bytes = await getBs58Bytes(multibaseString);
const bytes = getBs58Bytes(multibaseString);
bytes.length.should.eql(
expectedByteLength,
`Expected byteLength of ${expectedByteLength} received ${bytes.length}.`);
};

export const shouldHaveHeaderBytes = async (multibaseString, headerBytes) => {
const bytes = await getBs64UrlBytes(multibaseString);
export const shouldHaveHeaderBytes = (multibaseString, headerBytes) => {
const bytes = getBs64UrlBytes(multibaseString);
const actualHeaderBytes = Array.from(bytes.slice(0, headerBytes.length));
actualHeaderBytes.should.eql(
Array.from(headerBytes),
Expand All @@ -53,7 +54,7 @@ export const shouldBeMulticodecEncoded = async s => {
if(s.startsWith(multibaseMultikeyHeaderP256)) {
// example of a P-256 publicKeyMultibase -
// zDnaepHgv4AU1btQ8dp6EYbvgJ6M1ovzKnSpXJUPU2hshXLvp
const bytes = await getBs58Bytes(s);
const bytes = getBs58Bytes(s);
bytes.length.should.equal(35);
// bytes example => Uint8Array(35) [
// 128, 36, 3, 98, 121, 153, 205, 199,
Expand All @@ -71,7 +72,7 @@ export const shouldBeMulticodecEncoded = async s => {
}

if(s.startsWith(multibaseMultikeyHeaderP384)) {
const bytes = await getBs58Bytes(s);
const bytes = getBs58Bytes(s);
bytes.length.should.equal(51);
// get the two-byte prefix
const prefix = Array.from(bytes.slice(0, 2));
Expand Down Expand Up @@ -182,3 +183,36 @@ export function itRejectsInvalidCryptosuite(expectedValidSuites, {
await verificationFail({credential, verifier: endpoint});
});
}

export async function shouldBeBaseProofValue({proof, name}) {
expect(
proof,
`Expected VC from issuer ${name} to have an ' +
'"ecdsa-sd-2023" proof`).to.exist;
expect(
proof.proofValue,
`Expected VC from issuer ${name} to have a ' +
'"proof.proofValue"`
).to.exist;
expect(
proof.proofValue,
`Expected VC "proof.proofValue" from issuer ${name} to be ` +
'a string.'
Comment on lines +190 to +200
Copy link
Member

@TallTed TallTed Nov 18, 2024

Choose a reason for hiding this comment

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

Either all Expect ... clauses should end with a fullstop ., or none should. I prefer the former.

I also note mixed up use of ' and `. I strongly advise that these be used consistently, especially but not only in any given statement.

I'm not going to make all the suggestions the above would call for, unless you request that I do so. (I'm betting that changes will be needed throughout the documents touched by this PR, and possibly across other documents.)

Suggested change
`Expected VC from issuer ${name} to have an ' +
'"ecdsa-sd-2023" proof`).to.exist;
expect(
proof.proofValue,
`Expected VC from issuer ${name} to have a ' +
'"proof.proofValue"`
).to.exist;
expect(
proof.proofValue,
`Expected VC "proof.proofValue" from issuer ${name} to be ` +
'a string.'
'Expected VC from issuer ${name} to have an ' +
'"ecdsa-sd-2023" proof.').to.exist;
expect(
proof.proofValue,
'Expected VC from issuer ${name} to have a ' +
'"proof.proofValue".'
).to.exist;
expect(
proof.proofValue,
'Expected VC "proof.proofValue" from issuer ${name} to be ' +
'a string.'

).to.be.a.string;
//Ensure the proofValue string starts with u, indicating that it
//is a multibase-base64url-no-pad-encoded value, throwing an
//error if it does not.
expect(
proof.proofValue.startsWith('u'),
`Expected "proof.proofValue" to start with u received ` +
`${proof.proofValue[0]}`).to.be.true;
Comment on lines +207 to +208
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
`Expected "proof.proofValue" to start with u received ` +
`${proof.proofValue[0]}`).to.be.true;
'Expected "proof.proofValue" to start with u received ' +
'${proof.proofValue[0]}.').to.be.true;

// now test the encoding which is bs64 url no pad for this suite
expect(
shouldBeBs64UrlNoPad(proof.proofValue),
'Expected "proof.proofValue" to be bs64 url no pad encoded.'
Comment on lines +209 to +212
Copy link
Member

Choose a reason for hiding this comment

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

When aimed at humans, best to use the full string for "bs64 url no pad encoded".

).to.be.true;
shouldHaveHeaderBytes(
proof.proofValue,
new Uint8Array([0xd9, 0x5d, 0x00])
);
}
5 changes: 3 additions & 2 deletions tests/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ export const require = createRequire(import.meta.url);

// takes a multibase string starting with z lops the z off
// and gets the bytes
export const getBs58Bytes = async s => bs58.decode(s.slice(1));
export const getBs64UrlBytes = async s => bs64.decode(s.slice(1));
export const getBs58Bytes = s => bs58.decode(s.slice(1));
export const getBs64UrlBytes = s => bs64.decode(s.slice(1));
export const encodeBs64Url = bytes => bs64.encode(bytes);

// Javascript's default ISO timestamp contains milliseconds.
// This lops off the MS part of the UTC RFC3339 TimeStamp and replaces
Expand Down
94 changes: 39 additions & 55 deletions tests/suites/algorithms-sd.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import {
issueCloned
} from 'data-integrity-test-suite-assertion';
import {
shouldBeBs64UrlNoPad,
shouldHaveHeaderBytes,
} from '../assertions.js';
import {createInitialVc} from '../helpers.js';
createInitialVc,
encodeBs64Url,
getBs64UrlBytes
} from '../helpers.js';
import {expect} from 'chai';
import {getMultiKey} from '../vc-generator/key-gen.js';
import {getSuites} from './helpers.js';
import {invalidCborTagProxy} from './proxies.js';
import {shouldBeBaseProofValue} from '../assertions.js';

export function sd2023Algorithms({
credential,
Expand Down Expand Up @@ -93,38 +94,8 @@ export function sd2023Algorithms({
'specific cryptosuite proof generation algorithm.', async function() {
this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#base-proof-serialization-ecdsa-sd-2023:~:text=When%20generating%20ECDSA%20signatures%2C%20the%20signature%20value%20MUST%20be%20expressed%20according%20to%20section%207';
assertIssuer();
const _proof = proofs.find(p =>
p?.cryptosuite === 'ecdsa-sd-2023');
expect(
_proof,
`Expected VC from issuer ${name} to have an ' +
'"ecdsa-sd-2023" proof`).to.exist;
expect(
_proof.proofValue,
`Expected VC from issuer ${name} to have a ' +
'"proof.proofValue"`
).to.exist;
expect(
_proof.proofValue,
`Expected VC "proof.proofValue" from issuer ${name} to be ` +
'a string.'
).to.be.a.string;
//Ensure the proofValue string starts with u, indicating that it
//is a multibase-base64url-no-pad-encoded value, throwing an
//error if it does not.
expect(
_proof.proofValue.startsWith('u'),
`Expected "proof.proofValue" to start with u received ` +
`${_proof.proofValue[0]}`).to.be.true;
// now test the encoding which is bs64 url no pad for this suite
expect(
shouldBeBs64UrlNoPad(_proof.proofValue),
'Expected "proof.proofValue" to be bs64 url no pad encoded.'
).to.be.true;
await shouldHaveHeaderBytes(
_proof.proofValue,
new Uint8Array([0xd9, 0x5d, 0x00])
);
const proof = proofs.find(p => p?.cryptosuite === 'ecdsa-sd-2023');
await shouldBeBaseProofValue({proof, name});
});
it('If source has an id that is not a blank node identifier, set ' +
'selection.id to its value. Note: All non-blank node identifiers ' +
Expand Down Expand Up @@ -173,46 +144,33 @@ export function sd2023Algorithms({
'MUST be raised and SHOULD convey an error type of ' +
'PROOF_VERIFICATION_ERROR.', async function() {
this.test.link = 'https://w3c.github.io/vc-di-ecdsa/#selective-disclosure-functions:~:text=produced%20as%20output.-,If%20the%20proofValue%20string%20does%20not%20start%20with%20u%2C%20indicating%20that%20it%20is%20a%20multibase%2Dbase64url%2Dno%2Dpad%2Dencoded%20value%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.,-Initialize%20decodedProofValue%20to';
this.test.cell.skipMessage = 'Not Implemented';
this.skip();
/*
await assertions.verificationFail({
verifier,
credential: fixtures.get('invalidProofValuePrefix'),
credential: fixtures.get(keyType).get('invalidProofValuePrefix'),
reason: 'Should not verify VC with invalid proofValue prefix'
});
*/
});
it('If the decodedProofValue does not start with the ECDSA-SD ' +
'base proof header bytes 0xd9, 0x5d, and 0x00, 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-ecdsa/#selective-disclosure-functions:~:text=If%20the%20decodedProofValue%20does%20not%20start%20with%20the%20ECDSA%2DSD%20base%20proof%20header%20bytes%200xd9%2C%200x5d%2C%20and%200x00%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.';
this.test.cell.skipMessage = 'Not Implemented';
this.skip();
/*
await assertions.verificationFail({
verifier,
credential: fixtures.get('invalidBaseProofHeader'),
reason: 'Should not verify VC with invalid base proof header'
});
*/
assertIssuer();
const proof = proofs.find(p => p?.cryptosuite === 'ecdsa-sd-2023');
await shouldBeBaseProofValue({proof, name});
});
it('If the decodedProofValue does not start with the ECDSA-SD ' +
'disclosure proof header bytes 0xd9, 0x5d, and 0x01, 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-ecdsa/#selective-disclosure-functions:~:text=If%20the%20decodedProofValue%20does%20not%20start%20with%20the%20ECDSA%2DSD%20disclosure%20proof%20header%20bytes%200xd9%2C%200x5d%2C%20and%200x01%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_VERIFICATION_ERROR.';
this.test.cell.skipMessage = 'Not Implemented';
this.skip();
/*
await assertions.verificationFail({
verifier,
credential: fixtures.get('invalidDisclosureProofHeader'),
credential: fixtures.get(keyType).get(
'invalidDisclosureProofHeader'),
reason: 'Should not verify VC with invalid disclosure proof ' +
'header'
});
*/
});
it('If the result is not an array of the following five elements ' +
'— a byte array of length 64; a byte array of length 36; an array ' +
Expand Down Expand Up @@ -367,5 +325,31 @@ async function _setup({
suite: cborTagSuites.suite,
selectiveSuite: invalidCborTagProxy(cborTagSuites.selectiveSuite)
}));
const securedCredential = await issueCloned({
credential: _credential,
...getSuites({
signer,
suiteName,
selectivePointers,
mandatoryPointers
})
});
const nonbase64ProofValue = structuredClone(securedCredential);
nonbase64ProofValue.proof.proofValue =
nonbase64ProofValue.proof.proofValue.substring(1);
credentials.set('invalidProofValuePrefix', nonbase64ProofValue);
const invalidProofValueHeader = structuredClone(securedCredential);
// get the bytes
const disclosureBytes = getBs64UrlBytes(
invalidProofValueHeader?.proof?.proofValue);
// remove the 3 byte prefix and replace with an invalid prefix
const invalidBuffer = Buffer.concat(
[Buffer.from([0xA1, 0x44, 0x01]), disclosureBytes.slice(3)],
disclosureBytes.length
);
// replace the proofValue with a newer proofValue with an
// invalid 3 byte header
invalidProofValueHeader.proof.proofValue = `u${encodeBs64Url(invalidBuffer)}`;
credentials.set('invalidDisclosureProofHeader', invalidProofValueHeader);
return credentials;
}