diff --git a/integration-tests/data/default-presentation-tests/equinet-credit-score.json b/integration-tests/data/default-presentation-tests/equinet-credit-score.json new file mode 100644 index 00000000..ea62e965 --- /dev/null +++ b/integration-tests/data/default-presentation-tests/equinet-credit-score.json @@ -0,0 +1,60 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://ld.truvera.io/credentials/extensions-v1", + "https://ld.truvera.io/security/bbs23/v1", + { + "EquiNetCreditScore": "dk:EquiNetCreditScore", + "credit_score": "dk:credit_score", + "dk": "https://ld.truvera.io/credentials#" + }, + "https://ld.truvera.io/credentials/prettyvc" + ], + "credentialStatus": { + "id": "accumulator:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7:d486206d-a051-4175-9700-40592f14684a", + "type": "DockVBAccumulator2022", + "revocationCheck": "membership", + "revocationId": "1" + }, + "id": "https://creds-staging.truvera.io/ff134a47f7649de2eae4b0ae912b35ba1f00deae565c34b3b2f89444319a7eea", + "type": [ + "VerifiableCredential", + "EquiNetCreditScore", + "PrettyVerifiableCredential" + ], + "credentialSubject": { + "id": "did:key:z6Mku9R8zdA8LD6hcFXkn47jLnfcKZNGmwaTrDnaCBkSb8Un", + "credit_score": 200 + }, + "issuanceDate": "2026-03-26T19:10:51.355Z", + "issuer": "did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7", + "credentialSchema": { + "id": "https://schema.truvera.io/EquiNetCreditScore-V2-1738609162316.json", + "type": "JsonSchemaValidator2018", + "details": "{\"jsonSchema\":{\"$id\":\"https://schema.truvera.io/EquiNetCreditScore-V2-1738609162316.json\",\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"additionalProperties\":true,\"description\":\"This schema represents a Verified Credit Score Credential, issued by EquiNet. It standardizes the presentation of credit scores for reliable and efficient verification processes.\",\"name\":\"EquiNet - Credit Score\",\"properties\":{\"@context\":{\"type\":\"string\"},\"credentialSchema\":{\"properties\":{\"details\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"type\":\"object\"},\"credentialStatus\":{\"properties\":{\"id\":{\"type\":\"string\"},\"revocationCheck\":{\"type\":\"string\"},\"revocationId\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"credentialSubject\":{\"properties\":{\"credit_score\":{\"description\":\"This attribute represents the individual's credit score as a numerical value.\",\"title\":\"credit_score\",\"type\":\"number\"},\"id\":{\"description\":\"A unique identifier of the recipient. Example: DID, email address, national ID number, employee ID, student ID etc. If you enter the recipient's DID, the person will automatically receive the credential in their Dock wallet.\",\"title\":\"ID\",\"type\":\"string\"}},\"required\":[\"credit_score\"],\"type\":\"object\"},\"cryptoVersion\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"},\"issuanceDate\":{\"format\":\"date-time\",\"type\":\"string\"},\"issuer\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"prettyVC\":{\"properties\":{\"orientation\":{\"type\":\"string\"},\"proof\":{\"type\":\"string\"},\"size\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"proof\":{\"properties\":{\"@context\":{\"items\":[{\"properties\":{\"proof\":{\"properties\":{\"@container\":{\"type\":\"string\"},\"@id\":{\"type\":\"string\"},\"@type\":{\"type\":\"string\"}},\"type\":\"object\"},\"sec\":{\"type\":\"string\"}},\"type\":\"object\"},{\"type\":\"string\"}],\"type\":\"array\"},\"created\":{\"format\":\"date-time\",\"type\":\"string\"},\"proofPurpose\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"verificationMethod\":{\"type\":\"string\"}},\"type\":\"object\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"parsingOptions\":{\"defaultDecimalPlaces\":4,\"defaultMinimumDate\":-17592186044415,\"defaultMinimumInteger\":-4294967295,\"useDefaults\":true}}", + "version": "0.4.0" + }, + "name": "EquiNet - Credit Score", + "prettyVC": { + "type": "liquid", + "orientation": "landscape", + "size": "a4", + "proof": "
Certificate Title
{{subject.name}}
Presented to
Issued by: {{issuer.name}}
for completing the {{credential.name}}
Issued on {{credential.issuanceDate | date: \"%B %d, %Y\"}}
\"Scan
" + }, + "cryptoVersion": "0.6.0", + "proof": { + "@context": [ + { + "sec": "https://w3id.org/security#", + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"} + }, + "https://ld.truvera.io/security/bbs23/v1" + ], + "type": "Bls12381BBSSignatureDock2023", + "created": "2026-03-26T19:11:36Z", + "verificationMethod": "did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-2", + "proofPurpose": "assertionMethod", + "proofValue": "z2dwGNLsM2XsjsVk78KPB7a8KKpaUkzLrZ1Xxq79cqr2m6gxjtXxQRhug3Y4zdv241SxvmwGk178xAsJwQTDTP2MzuCHFZbFTQX3oktLMFCkMfi" + }, + "$$accum__witness$$": "{\"blockNo\":\"6e1b9610-a0b6-46de-8e20-2f60625e5eae\",\"witness\":\"0x8bc63d0ce34feb8d92c86833b2e6a9b9948ed327756537eecf2b34184f8e5f944a776fbb4c20c2c33a21c4a466d4db8e\"}" +} diff --git a/integration-tests/data/default-presentation-tests/university-degree-2.json b/integration-tests/data/default-presentation-tests/university-degree-2.json new file mode 100644 index 00000000..35af5756 --- /dev/null +++ b/integration-tests/data/default-presentation-tests/university-degree-2.json @@ -0,0 +1,67 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://ld.truvera.io/credentials/extensions-v1", + "https://ld.truvera.io/security/bbs23/v1", + { + "UniversityDegree": "dk:UniversityDegree", + "awardedDate": "dk:awardedDate", + "dateOfBirth": "dk:dateOfBirth", + "degreeName": "dk:degreeName", + "degreeType": "dk:degreeType", + "dk": "https://ld.truvera.io/credentials#" + }, + "https://ld.truvera.io/credentials/prettyvc" + ], + "credentialStatus": { + "id": "accumulator:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7:d486206d-a051-4175-9700-40592f14684a", + "type": "DockVBAccumulator2022", + "revocationCheck": "membership", + "revocationId": "3" + }, + "id": "https://creds-staging.truvera.io/43996053f6bca27d9e6a2cc9e469ad4c39dc61d982366af5d4a27f34277c99c6", + "type": [ + "VerifiableCredential", + "UniversityDegree", + "PrettyVerifiableCredential" + ], + "credentialSubject": { + "id": "did:key:z6Mku9R8zdA8LD6hcFXkn47jLnfcKZNGmwaTrDnaCBkSb8Un", + "degreeName": "University Degree 3", + "degreeType": "Degree 3", + "awardedDate": "2026-03-19", + "name": "Tester", + "dateOfBirth": "2012-06-26" + }, + "issuanceDate": "2026-03-26T19:17:34.530Z", + "issuer": "did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7", + "credentialSchema": { + "id": "https://schema.truvera.io/UniversityDegree-V1-1703767509472.json", + "type": "JsonSchemaValidator2018", + "details": "{\"jsonSchema\":{\"$id\":\"https://schema.truvera.io/UniversityDegree-V1-1703767509472.json\",\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"additionalProperties\":true,\"description\":\"A representation of a university degree issued to a person\",\"name\":\"University Degree\",\"properties\":{\"@context\":{\"type\":\"string\"},\"credentialSchema\":{\"properties\":{\"details\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"type\":\"object\"},\"credentialStatus\":{\"properties\":{\"id\":{\"type\":\"string\"},\"revocationCheck\":{\"type\":\"string\"},\"revocationId\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"credentialSubject\":{\"properties\":{\"awardedDate\":{\"description\":\"The date the degree was awarded.\",\"format\":\"date\",\"title\":\"Awarded Date\",\"type\":\"string\"},\"dateOfBirth\":{\"description\":\"The person's date of birth.\",\"format\":\"date\",\"title\":\"Date of Birth\",\"type\":\"string\"},\"degreeName\":{\"description\":\"The full degree name and field.\",\"title\":\"Degree Name\",\"type\":\"string\"},\"degreeType\":{\"description\":\"The type of degree earned.\",\"title\":\"Degree Type\",\"type\":\"string\"},\"id\":{\"description\":\"The recipient’s identifier that will be stored in the credential\",\"title\":\"Recipient ID\",\"type\":\"string\"},\"name\":{\"description\":\"The person's full name.\",\"title\":\"Full Name\",\"type\":\"string\"}},\"required\":[\"name\",\"awardedDate\"],\"type\":\"object\"},\"cryptoVersion\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"},\"issuanceDate\":{\"format\":\"date-time\",\"type\":\"string\"},\"issuer\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"prettyVC\":{\"properties\":{\"orientation\":{\"type\":\"string\"},\"proof\":{\"type\":\"string\"},\"size\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"proof\":{\"properties\":{\"@context\":{\"items\":[{\"properties\":{\"proof\":{\"properties\":{\"@container\":{\"type\":\"string\"},\"@id\":{\"type\":\"string\"},\"@type\":{\"type\":\"string\"}},\"type\":\"object\"},\"sec\":{\"type\":\"string\"}},\"type\":\"object\"},{\"type\":\"string\"}],\"type\":\"array\"},\"created\":{\"format\":\"date-time\",\"type\":\"string\"},\"proofPurpose\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"verificationMethod\":{\"type\":\"string\"}},\"type\":\"object\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"parsingOptions\":{\"defaultDecimalPlaces\":4,\"defaultMinimumDate\":-17592186044415,\"defaultMinimumInteger\":-4294967295,\"useDefaults\":true}}", + "version": "0.4.0" + }, + "name": "University Degree", + "prettyVC": { + "type": "liquid", + "orientation": "landscape", + "size": "a4", + "proof": "
Certificate Title
{{subject.name}}
Presented to
Issued by: {{issuer.name}}
for completing the {{credential.name}}
Issued on {{credential.issuanceDate | date: \"%B %d, %Y\"}}
\"Scan
" + }, + "cryptoVersion": "0.6.0", + "proof": { + "@context": [ + { + "sec": "https://w3id.org/security#", + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"} + }, + "https://ld.truvera.io/security/bbs23/v1" + ], + "type": "Bls12381BBSSignatureDock2023", + "created": "2026-03-26T19:18:18Z", + "verificationMethod": "did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-2", + "proofPurpose": "assertionMethod", + "proofValue": "z2fSzqajdYmaizLGHPaQyAfuzRyK8AR8XaXFf6ukkUecZHJtJ8SRNbCc1oj7HVmC1TXgFVuVdLM7LrxwcjAY8Xkk3WrqnjVPiH11hL8rwfbZifa" + }, + "$$accum__witness$$": "{\"blockNo\":\"6e1b9610-a0b6-46de-8e20-2f60625e5eae\",\"witness\":\"0x900cce5b36f88006097221efbcf6ef1a7beca2a8d2009235733ece8e31b2e40e22384bdd7206c3c33844557c63161fff\"}" +} diff --git a/integration-tests/data/default-presentation-tests/university-degree.json b/integration-tests/data/default-presentation-tests/university-degree.json new file mode 100644 index 00000000..3857bba8 --- /dev/null +++ b/integration-tests/data/default-presentation-tests/university-degree.json @@ -0,0 +1,69 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://ld.truvera.io/credentials/extensions-v1", + "https://ld.truvera.io/security/bbs23/v1", + { + "UniversityDegree": "dk:UniversityDegree", + "additionalDistinctions": "dk:additionalDistinctions", + "awardedDate": "dk:awardedDate", + "dateOfBirth": "dk:dateOfBirth", + "degreeName": "dk:degreeName", + "degreeType": "dk:degreeType", + "dk": "https://ld.truvera.io/credentials#" + }, + "https://ld.truvera.io/credentials/prettyvc" + ], + "credentialStatus": { + "id": "accumulator:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7:d486206d-a051-4175-9700-40592f14684a", + "type": "DockVBAccumulator2022", + "revocationCheck": "membership", + "revocationId": "2" + }, + "id": "https://creds-staging.truvera.io/f2c3ffa4b226b97278372a0687feb36351c6472c644d44ff3406fcc7e371b7b8", + "type": [ + "VerifiableCredential", + "UniversityDegree", + "PrettyVerifiableCredential" + ], + "credentialSubject": { + "id": "did:key:z6Mku9R8zdA8LD6hcFXkn47jLnfcKZNGmwaTrDnaCBkSb8Un", + "degreeName": "Test", + "degreeType": "Test", + "awardedDate": "2026-03-26", + "name": "Tester", + "dateOfBirth": "2014-06-18", + "additionalDistinctions": "Test" + }, + "issuanceDate": "2026-03-26T19:14:06.392Z", + "issuer": "did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7", + "credentialSchema": { + "id": "https://schema.truvera.io/UniversityDegree-V1-1703767509472.json", + "type": "JsonSchemaValidator2018", + "details": "{\"jsonSchema\":{\"$id\":\"https://schema.truvera.io/UniversityDegree-V1-1703767509472.json\",\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"additionalProperties\":true,\"description\":\"A representation of a university degree issued to a person\",\"name\":\"University Degree\",\"properties\":{\"@context\":{\"type\":\"string\"},\"credentialSchema\":{\"properties\":{\"details\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"type\":\"object\"},\"credentialStatus\":{\"properties\":{\"id\":{\"type\":\"string\"},\"revocationCheck\":{\"type\":\"string\"},\"revocationId\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"credentialSubject\":{\"properties\":{\"additionalDistinctions\":{\"description\":\"The person's additional distinctions.\",\"title\":\"Additional Distinctions\",\"type\":\"string\"},\"awardedDate\":{\"description\":\"The date the degree was awarded.\",\"format\":\"date\",\"title\":\"Awarded Date\",\"type\":\"string\"},\"dateOfBirth\":{\"description\":\"The person's date of birth.\",\"format\":\"date\",\"title\":\"Date of Birth\",\"type\":\"string\"},\"degreeName\":{\"description\":\"The full degree name and field.\",\"title\":\"Degree Name\",\"type\":\"string\"},\"degreeType\":{\"description\":\"The type of degree earned.\",\"title\":\"Degree Type\",\"type\":\"string\"},\"id\":{\"description\":\"The recipient’s identifier that will be stored in the credential\",\"title\":\"Recipient ID\",\"type\":\"string\"},\"name\":{\"description\":\"The person's full name.\",\"title\":\"Full Name\",\"type\":\"string\"}},\"required\":[\"name\",\"awardedDate\"],\"type\":\"object\"},\"cryptoVersion\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"},\"issuanceDate\":{\"format\":\"date-time\",\"type\":\"string\"},\"issuer\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"prettyVC\":{\"properties\":{\"orientation\":{\"type\":\"string\"},\"proof\":{\"type\":\"string\"},\"size\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"proof\":{\"properties\":{\"@context\":{\"items\":[{\"properties\":{\"proof\":{\"properties\":{\"@container\":{\"type\":\"string\"},\"@id\":{\"type\":\"string\"},\"@type\":{\"type\":\"string\"}},\"type\":\"object\"},\"sec\":{\"type\":\"string\"}},\"type\":\"object\"},{\"type\":\"string\"}],\"type\":\"array\"},\"created\":{\"format\":\"date-time\",\"type\":\"string\"},\"proofPurpose\":{\"type\":\"string\"},\"type\":{\"type\":\"string\"},\"verificationMethod\":{\"type\":\"string\"}},\"type\":\"object\"},\"type\":{\"type\":\"string\"}},\"type\":\"object\"},\"parsingOptions\":{\"defaultDecimalPlaces\":4,\"defaultMinimumDate\":-17592186044415,\"defaultMinimumInteger\":-4294967295,\"useDefaults\":true}}", + "version": "0.4.0" + }, + "name": "University Degree", + "prettyVC": { + "type": "liquid", + "orientation": "landscape", + "size": "a4", + "proof": "
Certificate Title
{{subject.name}}
Presented to
Issued by: {{issuer.name}}
for completing the {{credential.name}}
Issued on {{credential.issuanceDate | date: \"%B %d, %Y\"}}
\"Scan
" + }, + "cryptoVersion": "0.6.0", + "proof": { + "@context": [ + { + "sec": "https://w3id.org/security#", + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"} + }, + "https://ld.truvera.io/security/bbs23/v1" + ], + "type": "Bls12381BBSSignatureDock2023", + "created": "2026-03-26T19:15:07Z", + "verificationMethod": "did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-2", + "proofPurpose": "assertionMethod", + "proofValue": "z2a6ngYYzpYKWETZYemNJ1U3hVTbZ8nnbwuuzuV6vY3hmV337rL8xaQ2yS48YR2QC9DfRMchs12hoCxeJjxoB9LKqPLPFu59tx4hGNaL45vdPRh" + }, + "$$accum__witness$$": "{\"blockNo\":\"6e1b9610-a0b6-46de-8e20-2f60625e5eae\",\"witness\":\"0xafc625dd441a5b5e9da4460f7734c2d7e163cc6496f5d677121b9b4feb66ad8c1ffddafef641bbd5d0776d54630015fe\"}" +} diff --git a/integration-tests/verification-flow/cheqd-revocation.test.ts b/integration-tests/verification-flow/cheqd-revocation.test.ts index d0c42687..27e677e2 100644 --- a/integration-tests/verification-flow/cheqd-revocation.test.ts +++ b/integration-tests/verification-flow/cheqd-revocation.test.ts @@ -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(); diff --git a/integration-tests/verification-flow/default-presentation.test.ts b/integration-tests/verification-flow/default-presentation.test.ts new file mode 100644 index 00000000..d8d2a4c8 --- /dev/null +++ b/integration-tests/verification-flow/default-presentation.test.ts @@ -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); + + }); +}); diff --git a/packages/core/src/verification-controller.test.ts b/packages/core/src/verification-controller.test.ts index 794f7506..68849639 100644 --- a/packages/core/src/verification-controller.test.ts +++ b/packages/core/src/verification-controller.test.ts @@ -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, diff --git a/packages/core/src/verification-controller.ts b/packages/core/src/verification-controller.ts index ef499f14..bdcc959d 100644 --- a/packages/core/src/verification-controller.ts +++ b/packages/core/src/verification-controller.ts @@ -52,6 +52,7 @@ export function createVerificationController({ */ let statusData = null; let filteredCredentials = []; + let filteredMatches = []; let selectedCredentials: CredentialSelectionMap = new Map(); let selectedDID = null; @@ -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( @@ -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 = []; + const groupKey = (match) => match.from ? JSON.stringify(match.from) : (match.name || match.id || ''); + const seen = new Map(); + + 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'); @@ -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, @@ -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( @@ -272,7 +339,7 @@ export function createVerificationController({ */ function evaluatePresentation(presentation) { const definition = getPresentationDefinition(); - const result = credentialServiceRPC.evaluatePresentation({ + const result = pexService.evaluatePresentation({ presentation, presentationDefinition: definition, }); @@ -304,6 +371,7 @@ export function createVerificationController({ isBBSPlusCredential, loadCredentials, getFilteredCredentials, + createDefaultPresentation, createPresentation, evaluatePresentation, getTemplateJSON() {