Skip to content

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion integration-tests/verification-flow/cheqd-revocation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {createVerificationController} from '@docknetwork/wallet-sdk-core/src/ver
import {ProofTemplateIds, createProofRequest} from '../helpers/certs-helpers';
import { cheqdRevocationCredential } from './bbs-plus-revocation-credentials';

describe('BBS+ revocation cheqd', () => {
jest.retryTimes(5);

// Skipped: will be handled in https://dock-team.atlassian.net/browse/DCKA-5410
describe.skip('BBS+ revocation cheqd', () => {
it('should verify a revokable bbs+ credential issued on cheqd', async () => {
const wallet: IWallet = await getWallet();

Expand Down
129 changes: 129 additions & 0 deletions integration-tests/verification-flow/default-presentation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {IWallet} from '@docknetwork/wallet-sdk-core/lib/types';
import {
closeWallet,
getWallet,
getDIDProvider,
getCredentialProvider,
} from '../helpers/wallet-helpers';
import {createVerificationController} from '@docknetwork/wallet-sdk-core/src/verification-controller';
import universityDegree from '../data/default-presentation-tests/university-degree.json';
import universityDegree2 from '../data/default-presentation-tests/university-degree-2.json';
import equinetCreditScore from '../data/default-presentation-tests/equinet-credit-score.json';
import {createProofRequest} from '../helpers/certs-helpers';

jest.retryTimes(0);

// Request a university degree and reveal dateOfBirth
const template1 = '91138d81-1b54-4955-a5bc-4e2d90d8f7b1';
// 1 range proof credential is requested, creditScore greater than 50
const template2 = 'a7786f42-03ee-4f49-80b8-ce3027b5a903';
// Any credential with dateOfBirth is revealed
const template3 = 'a3e775bb-aaab-4489-b31b-746dc74f76c5';
// 2 range proofs
const template4 = '9b434ed1-3b65-4b7c-b678-afc7e218f063';

let wallet: IWallet;
let didProvider;

describe('Default presentation', () => {
beforeAll(async () => {
wallet = await getWallet();
didProvider = getDIDProvider();
const credentialProvider = getCredentialProvider();

await didProvider.ensureDID();
await credentialProvider.addCredential(universityDegree);
await credentialProvider.addCredential(universityDegree2);
await credentialProvider.addCredential(equinetCreditScore);
});

afterAll(() => closeWallet());

it('should create a default presentation for university degree', async () => {
const proofRequest = await createProofRequest(template1);

const controller = createVerificationController({
wallet,
didProvider,
});

await controller.start({
template: proofRequest,
});

const presentation = await controller.createDefaultPresentation();

expect(presentation).toBeDefined();
expect(presentation.type).toEqual(['VerifiablePresentation']);
expect(presentation.verifiableCredential).toBeDefined();
expect(presentation.verifiableCredential.length).toBe(1);

const result = controller.evaluatePresentation(presentation);

expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});

it('should create a default presentation with range proof', async () => {
const proofRequest = await createProofRequest(template2);

const controller = createVerificationController({
wallet,
didProvider,
});

await controller.start({
template: proofRequest,
});

const presentation = await controller.createDefaultPresentation();

expect(presentation).toBeDefined();
expect(presentation.type).toEqual(['VerifiablePresentation']);
expect(presentation.verifiableCredential).toBeDefined();
expect(presentation.verifiableCredential.length).toBe(1);

});

it('should create a default presentation for any credential with dateOfBirth', async () => {
const proofRequest = await createProofRequest(template3);

const controller = createVerificationController({
wallet,
didProvider,
});

await controller.start({
template: proofRequest,
});

const presentation = await controller.createDefaultPresentation();

expect(presentation).toBeDefined();
expect(presentation.type).toEqual(['VerifiablePresentation']);
expect(presentation.verifiableCredential).toBeDefined();
expect(presentation.verifiableCredential.length).toBe(1);

});

it('should create a default presentation with 2 range proofs', async () => {
const proofRequest = await createProofRequest(template4);

const controller = createVerificationController({
wallet,
didProvider,
});

await controller.start({
template: proofRequest,
});

const presentation = await controller.createDefaultPresentation();

expect(presentation).toBeDefined();
expect(presentation.type).toEqual(['VerifiablePresentation']);
expect(presentation.verifiableCredential).toBeDefined();
expect(presentation.verifiableCredential.length).toBeGreaterThanOrEqual(1);

});
});
23 changes: 23 additions & 0 deletions packages/core/src/verification-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ describe('Verification provider', () => {
expect(result.isValid).toBe(true);
});

it('expect to create a default presentation using filtered credentials', async () => {
const controller = createVerificationController({
wallet,
didProvider,
});

await controller.start({
template: anyCredentialProofRequest,
});

const credentials = controller.getFilteredCredentials();

const presentation = await controller.createDefaultPresentation();

expect(presentation.credentials[0]).toStrictEqual(credentials[0]);
expect(presentation.type).toEqual(['VerifiablePresentation']);

// validate the presentation
const result = await controller.evaluatePresentation(presentation);

expect(result.isValid).toBe(true);
});

it('expect to generate presentation iiw credential', async () => {
const controller = createVerificationController({
wallet,
Expand Down
94 changes: 81 additions & 13 deletions packages/core/src/verification-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export function createVerificationController({
*/
let statusData = null;
let filteredCredentials = [];
let filteredMatches = [];
let selectedCredentials: CredentialSelectionMap = new Map();
let selectedDID = null;

Expand Down Expand Up @@ -110,6 +111,7 @@ export function createVerificationController({
});

filteredCredentials = result.verifiableCredential;
filteredMatches = result.matches || [];
} catch (err) {
console.error(
`Unable to filter credentials using the template: \n ${JSON.stringify(
Expand Down Expand Up @@ -171,6 +173,67 @@ export function createVerificationController({
});
}

async function createDefaultPresentation() {
assert(filteredCredentials.length > 0, 'No filtered credentials available');

selectedCredentials.clear();

if (filteredMatches.length > 0) {
// Group matches by requirement: matches with distinct `from` values are separate requirements,
// matches without `from` sharing the same name/id are alternatives for the same requirement
const requirements: Array<typeof filteredMatches> = [];
const groupKey = (match) => match.from ? JSON.stringify(match.from) : (match.name || match.id || '');
const seen = new Map<string, number>();

for (const match of filteredMatches) {
const key = groupKey(match);
if (match.from || !seen.has(key)) {
// Distinct requirement: new `from` group or first match without `from`
seen.set(key, requirements.length);
requirements.push([match]);
} else {
// Alternative for an existing requirement
requirements[seen.get(key)].push(match);
}
}

// Select one credential per requirement
for (const group of requirements) {
// Collect all candidate indices from the group
const candidates: number[] = [];
for (const match of group) {
for (const path of match.vc_path || []) {
const indexMatch = path.match(/\[(\d+)\]/);
if (indexMatch) {
candidates.push(parseInt(indexMatch[1], 10));
}
}
}

// Pick the first candidate not already selected, or fall back to the first
const chosen = candidates.find(idx => {
const cred = filteredCredentials[idx];
return cred && !selectedCredentials.has(cred.id);
}) ?? candidates[0];

if (chosen !== undefined) {
const credential = filteredCredentials[chosen];
if (credential) {
selectedCredentials.set(credential.id, { credential });
}
}
}
} else {
for (const credential of filteredCredentials) {
selectedCredentials.set(credential.id, { credential });
}
}

assert(selectedCredentials.size > 0, 'No credentials could be selected for the presentation');

return createPresentation();
}

async function createPresentation() {
assert(!!selectedDID, 'No DID selected');
assert(!!selectedCredentials.size, 'No credentials selected');
Expand Down Expand Up @@ -209,8 +272,8 @@ export function createVerificationController({
})),
);

// Pure BBS+/KVAC: use generatePresentationFromPex end-to-end
if (sdJwtSelections.length === 0 && regularSelections.length === 0) {
// Single BBS+/KVAC credential: use generatePresentationFromPex end-to-end
if (credentialsWithWitness.length === 1 && sdJwtSelections.length === 0 && regularSelections.length === 0) {
return credentialServiceRPC.generatePresentationFromPex({
credentials: credentialsWithWitness,
pexRequest: templateJSON.request,
Expand All @@ -223,16 +286,20 @@ export function createVerificationController({
});
}

// Mixed: derive BBS+/KVAC, then combine with SD-JWT and regular
const derivedCredentials =
await credentialServiceRPC.deriveVCFromPresentation({
proofRequest: templateJSON,
credentials: credentialsWithWitness.map(c => ({
credential: c.credential,
witness: c.witness,
attributesToReveal: c.attributesToReveal,
})),
});
// Multiple BBS+/KVAC or mixed: derive each BBS+/KVAC credential separately, then assemble
const derivedResults = await Promise.all(
credentialsWithWitness.map(c =>
credentialServiceRPC.deriveVCFromPresentation({
proofRequest: templateJSON,
credentials: [{
credential: c.credential,
witness: c.witness,
attributesToReveal: c.attributesToReveal,
}],
}),
),
);
const derivedCredentials = derivedResults.flat();

const nonBbsCredentials = await deriveNonBbsCredentials(sdJwtSelections, regularSelections);
return assembleSignedPresentation(
Expand Down Expand Up @@ -272,7 +339,7 @@ export function createVerificationController({
*/
function evaluatePresentation(presentation) {
const definition = getPresentationDefinition();
const result = credentialServiceRPC.evaluatePresentation({
const result = pexService.evaluatePresentation({
presentation,
presentationDefinition: definition,
});
Expand Down Expand Up @@ -304,6 +371,7 @@ export function createVerificationController({
isBBSPlusCredential,
loadCredentials,
getFilteredCredentials,
createDefaultPresentation,
createPresentation,
evaluatePresentation,
getTemplateJSON() {
Expand Down
Loading