From e521766cc1f77e977f756aa088f4344943a7874e Mon Sep 17 00:00:00 2001 From: Robert Vintila <33718597+gasher@users.noreply.github.com> Date: Fri, 21 Feb 2025 16:04:45 +0200 Subject: [PATCH 1/6] Refactor presentation creation. --- packages/core/src/verification-controller.ts | 52 ++---- .../src/services/credential/bound-check.ts | 28 ++- .../wasm/src/services/credential/service.ts | 175 ++++++++++++++++-- 3 files changed, 189 insertions(+), 66 deletions(-) diff --git a/packages/core/src/verification-controller.ts b/packages/core/src/verification-controller.ts index a1cb13d3..bc3a714d 100644 --- a/packages/core/src/verification-controller.ts +++ b/packages/core/src/verification-controller.ts @@ -163,51 +163,39 @@ export function createVerificationController({ assert(!!provingKey, 'No proving key found'); } - const credentials = []; - - for (const credentialSelection of selectedCredentials.values()) { - const isBBS = await isBBSPlusCredential(credentialSelection.credential); - const isKVAC = await isKvacCredential(credentialSelection.credential); - - if (isBBS || isKVAC) { - // derive credential - const derivedCredentials = - await credentialServiceRPC.deriveVCFromPresentation({ - proofRequest: templateJSON, - - credentials: [ - { - credential: credentialSelection.credential, - witness: await credentialProvider.getMembershipWitness(credentialSelection.credential.id), - attributesToReveal: [ - ...(credentialSelection.attributesToReveal || []), - 'id', - ], - }, - ], - }); - - console.log('Credential derived'); - - credentials.push(derivedCredentials[0]); - } else { - credentials.push(credentialSelection.credential); - } - } - const didKeyPairList = await didProvider.getDIDKeyPairs(); const keyDoc = didKeyPairList.find(doc => doc.controller === selectedDID); assert(keyDoc, `No key pair found for the selected DID ${selectedDID}`); + const credentials = []; + const attributesToReveal = []; + const witnesses = []; + + for (const credentialSelection of selectedCredentials.values()) { + credentials.push(credentialSelection.credential); + attributesToReveal.push([ + ...(credentialSelection.attributesToReveal || []), + 'id', + ]); + witnesses.push( + await credentialProvider.getMembershipWitness( + credentialSelection.credential.id, + ), + ); + } + const presentation = await credentialServiceRPC.createPresentation({ credentials, + attributesToReveal, + witnesses, challenge: templateJSON.nonce, keyDoc, id: keyDoc.controller.startsWith('did:key:') ? keyDoc.id : `${keyDoc.controller}#keys-1`, domain: 'dock.io', + pexForBounds: templateJSON, }); return presentation; diff --git a/packages/wasm/src/services/credential/bound-check.ts b/packages/wasm/src/services/credential/bound-check.ts index b7d62917..60718a38 100644 --- a/packages/wasm/src/services/credential/bound-check.ts +++ b/packages/wasm/src/services/credential/bound-check.ts @@ -58,12 +58,14 @@ export function applyEnforceBounds({ provingKeyId, provingKey, selectedCredentials, + credentialIdx = 0, }: { builder: PresentationBuilder; proofRequest: ProofRequest; selectedCredentials: any[]; provingKeyId: string; provingKey: LegoProvingKey; + credentialIdx?: number; }) { const descriptorBounds = pexToBounds( proofRequest.request, @@ -72,21 +74,17 @@ export function applyEnforceBounds({ let skipProvingKey = false; - descriptorBounds.forEach((items, credentialIdx) => { - if (!selectedCredentials[credentialIdx]) { - return; - } - items.forEach((bound) => { - builder.enforceBounds( - credentialIdx, - bound.attributeName, - bound.min, - bound.max, - provingKeyId, - skipProvingKey ? undefined : provingKey, - ); - skipProvingKey = true; - }); + const items = descriptorBounds[credentialIdx]; + items.forEach((bound) => { + builder.enforceBounds( + 0, + bound.attributeName, + bound.min, + bound.max, + provingKeyId, + skipProvingKey ? undefined : provingKey + ); + skipProvingKey = true; }); return descriptorBounds; diff --git a/packages/wasm/src/services/credential/service.ts b/packages/wasm/src/services/credential/service.ts index dc45fbba..d269463b 100644 --- a/packages/wasm/src/services/credential/service.ts +++ b/packages/wasm/src/services/credential/service.ts @@ -16,6 +16,7 @@ import { verifyPresentation, VerifiableCredential, getSuiteFromKeyDoc, + isAnoncredsProofType, } from '@docknetwork/credential-sdk/vc'; import {PEX} from '@sphereon/pex'; import {keyDocToKeypair} from './utils'; @@ -55,6 +56,150 @@ export function isAnnonymousCredential(credential) { return isBBSPlusCredential(credential) || isKvacCredential(credential); } +export async function createPresentationSDK( + vcs, + { challenge, domain, revealAttribs = [], witnesses = [], pexForBounds, presentationId, presentationOptions = {}, holderKey } = {} +) { + const vcsArray = Array.isArray(vcs) ? vcs : [vcs]; + + const anoncredsVCs = vcsArray.filter(isAnoncredsProofType); + const nonAnoncredsVCs = vcsArray.filter((vc) => !isAnoncredsProofType(vc)); + const revealAttribsArray = Array.isArray(revealAttribs[0]) ? revealAttribs : [revealAttribs]; + + const presentation = new VerifiablePresentation(presentationId); + + let pexRequiredAttributes = []; + if (pexForBounds?.request) { + pexRequiredAttributes = getPexRequiredAttributes( + pexForBounds.request, + vcsArray, + ); + } + + await Promise.all( + anoncredsVCs.map(async (vc) => { + const presentationInstance = new Presentation(); + + const credIdx = vcsArray.indexOf(vc); + const revealAttribsForCredential = revealAttribsArray[credIdx] || []; + const witness = witnesses[credIdx]; + const verifiableCredential = { ...vc }; + + const idx = await presentationInstance.addCredentialToPresent(verifiableCredential, { + resolver: blockchainService.resolver, + }); + + const { credentialStatus } = verifiableCredential; + const isAccumulatorStatus = + credentialStatus && credentialStatus.type === 'DockVBAccumulator2022'; + if (isAccumulatorStatus && witness) { + const details = await getWitnessDetails(verifiableCredential, witness); + + const chainModule = + verifiableCredential.credentialStatus.id.indexOf('dock:accumulator') === 0 + ? blockchainService.modules.accumulator.modules[0] + : blockchainService.modules.accumulator.modules[ + blockchainService.modules.accumulator.modules.length - 1 + ]; + const accumulatorModuleClass = chainModule.constructor; + + presentationInstance.presBuilder.addAccumInfoForCredStatus( + idx, + details.membershipWitness, + accumulatorModuleClass.accumulatedFromHex( + details.accumulator.accumulated, + AccumulatorType.VBPos, + ), + details.pk, + details.params, + ); + } + + let descriptorBounds = []; + if (pexForBounds && hasProvingKey(pexForBounds)) { + const { provingKey, provingKeyId } = await fetchProvingKey(pexForBounds); + try { + descriptorBounds = applyEnforceBounds({ + builder: presentationInstance.presBuilder, + proofRequest: pexForBounds, + provingKeyId, + provingKey, + selectedCredentials: vcsArray, + credentialIdx: credIdx, + }); + + } catch (e) { + console.error(e); + throw new Error( + `Unable to apply enforce bounds: ${e.message} - keyId: ${JSON.stringify(provingKeyId)}` + ); + } + } + + // If subject type exists, reveal that to prevent JSON-LD errors + const subjectType = verifiableCredential.credentialSubject.type; + if (subjectType) { + if (Array.isArray(subjectType)) { + await Promise.all( + subjectType.map((type, typeIdx) => + presentationInstance.addAttributeToReveal(idx, [`credentialSubject.type.${typeIdx}`]) + ) + ); + } else { + await presentationInstance.addAttributeToReveal(idx, ['credentialSubject.type']); + } + } + + const attributesToSkip = descriptorBounds[credIdx] + ? descriptorBounds[credIdx].map(bound => bound.attributeName) + : []; + const filteredAttributes = revealAttribsForCredential.filter( + attribute => !attributesToSkip.includes(attribute), + ); + const _pexRequiredAttributes = pexRequiredAttributes[credIdx] || []; + + _pexRequiredAttributes.forEach(attr => { + if (!filteredAttributes.includes(attr)) { + filteredAttributes.push(attr); + } + }); + + // Custom reveal attributes + if (Array.isArray(filteredAttributes) && filteredAttributes.length > 0) { + await Promise.all( + filteredAttributes.map((attrib) => + presentationInstance.addAttributeToReveal(idx, [attrib]) + ) + ); + } + // Derive a W3C Verifiable Credential JSON from the above presentation + const credentials = await presentationInstance.deriveCredentials(presentationOptions); + presentation.addCredentials(credentials); + return true; + } + )); + + // Any non-anonymous credentials should be added separately + if (nonAnoncredsVCs && nonAnoncredsVCs.length) { + presentation.addCredentials(nonAnoncredsVCs); + } + + // Shouldnt sign with anonymous credentials + if (anoncredsVCs.length === 0) { + // Holder signing (optional) + presentation.setHolder(holderKey.controller); + + holderKey.keypair = keyDocToKeypair(holderKey, blockchainService.dock); + const signedPresentation = await presentation.sign(holderKey, challenge, domain, blockchainService.resolver); + + return signedPresentation; + } + + + + return presentation.toJSON(); +} + class CredentialService { constructor() { this.name = serviceName; @@ -105,25 +250,17 @@ class CredentialService { } async createPresentation(params) { validation.createPresentation(params); - const {credentials, keyDoc, challenge, id, domain} = params; - const vp = new VerifiablePresentation(id); - let shouldSkipSigning = false; - for (const signedVC of credentials) { - vp.addCredential(signedVC); - shouldSkipSigning = shouldSkipSigning || isAnnonymousCredential(signedVC); - } - - if (!shouldSkipSigning) { - vp.setHolder(keyDoc.controller); - } - - keyDoc.keypair = keyDocToKeypair(keyDoc, blockchainService.dock); - - if (shouldSkipSigning) { - return vp.toJSON(); - } - - return vp.sign(keyDoc, challenge, domain, blockchainService.resolver); + const {credentials, attributesToReveal, witnesses, keyDoc, challenge, id, domain, pexForBounds} = params; + + return createPresentationSDK(credentials, { + revealAttribs: attributesToReveal, + challenge, + domain, + presentationId: id, + holderKey: keyDoc, + pexForBounds, + witnesses, + }); } async verifyPresentation({ presentation, options }: any) { From f4a619fc13c2123e87322cc03ea7c3d665843cc2 Mon Sep 17 00:00:00 2001 From: Robert Vintila <33718597+gasher@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:39:25 +0200 Subject: [PATCH 2/6] Fix anoncred order when creating presentation. --- .../wasm/src/services/credential/service.ts | 180 +++++++++--------- 1 file changed, 87 insertions(+), 93 deletions(-) diff --git a/packages/wasm/src/services/credential/service.ts b/packages/wasm/src/services/credential/service.ts index d269463b..982aecf3 100644 --- a/packages/wasm/src/services/credential/service.ts +++ b/packages/wasm/src/services/credential/service.ts @@ -63,7 +63,6 @@ export async function createPresentationSDK( const vcsArray = Array.isArray(vcs) ? vcs : [vcs]; const anoncredsVCs = vcsArray.filter(isAnoncredsProofType); - const nonAnoncredsVCs = vcsArray.filter((vc) => !isAnoncredsProofType(vc)); const revealAttribsArray = Array.isArray(revealAttribs[0]) ? revealAttribs : [revealAttribs]; const presentation = new VerifiablePresentation(presentationId); @@ -76,114 +75,109 @@ export async function createPresentationSDK( ); } - await Promise.all( - anoncredsVCs.map(async (vc) => { - const presentationInstance = new Presentation(); + const { provingKey, provingKeyId } = pexForBounds && hasProvingKey(pexForBounds) ? await fetchProvingKey(pexForBounds) : {}; - const credIdx = vcsArray.indexOf(vc); - const revealAttribsForCredential = revealAttribsArray[credIdx] || []; - const witness = witnesses[credIdx]; - const verifiableCredential = { ...vc }; + for (let i = 0; i < anoncredsVCs.length; i++) { + const vc = anoncredsVCs[i]; + const presentationInstance = new Presentation(); - const idx = await presentationInstance.addCredentialToPresent(verifiableCredential, { - resolver: blockchainService.resolver, - }); + const credIdx = vcsArray.indexOf(vc); + const revealAttribsForCredential = revealAttribsArray[credIdx] || []; + const witness = witnesses[credIdx]; + const verifiableCredential = { ...vc }; - const { credentialStatus } = verifiableCredential; - const isAccumulatorStatus = - credentialStatus && credentialStatus.type === 'DockVBAccumulator2022'; - if (isAccumulatorStatus && witness) { - const details = await getWitnessDetails(verifiableCredential, witness); + const idx = await presentationInstance.addCredentialToPresent(verifiableCredential, { + resolver: blockchainService.resolver, + }); - const chainModule = - verifiableCredential.credentialStatus.id.indexOf('dock:accumulator') === 0 - ? blockchainService.modules.accumulator.modules[0] - : blockchainService.modules.accumulator.modules[ - blockchainService.modules.accumulator.modules.length - 1 - ]; - const accumulatorModuleClass = chainModule.constructor; + const { credentialStatus } = verifiableCredential; + const isAccumulatorStatus = + credentialStatus && credentialStatus.type === 'DockVBAccumulator2022'; + if (isAccumulatorStatus && witness) { + const details = await getWitnessDetails(verifiableCredential, witness); + + const chainModule = + verifiableCredential.credentialStatus.id.indexOf('dock:accumulator') === 0 + ? blockchainService.modules.accumulator.modules[0] + : blockchainService.modules.accumulator.modules[ + blockchainService.modules.accumulator.modules.length - 1 + ]; + const accumulatorModuleClass = chainModule.constructor; + + presentationInstance.presBuilder.addAccumInfoForCredStatus( + idx, + details.membershipWitness, + accumulatorModuleClass.accumulatedFromHex( + details.accumulator.accumulated, + AccumulatorType.VBPos, + ), + details.pk, + details.params, + ); + } - presentationInstance.presBuilder.addAccumInfoForCredStatus( - idx, - details.membershipWitness, - accumulatorModuleClass.accumulatedFromHex( - details.accumulator.accumulated, - AccumulatorType.VBPos, - ), - details.pk, - details.params, + let descriptorBounds = []; + if (pexForBounds && hasProvingKey(pexForBounds)) { + try { + descriptorBounds = applyEnforceBounds({ + builder: presentationInstance.presBuilder, + proofRequest: pexForBounds, + provingKeyId, + provingKey, + selectedCredentials: vcsArray, + credentialIdx: credIdx, + }); + } catch (e) { + console.error(e); + throw new Error( + `Unable to apply enforce bounds: ${e.message} - keyId: ${JSON.stringify(provingKeyId)}` ); } + } - let descriptorBounds = []; - if (pexForBounds && hasProvingKey(pexForBounds)) { - const { provingKey, provingKeyId } = await fetchProvingKey(pexForBounds); - try { - descriptorBounds = applyEnforceBounds({ - builder: presentationInstance.presBuilder, - proofRequest: pexForBounds, - provingKeyId, - provingKey, - selectedCredentials: vcsArray, - credentialIdx: credIdx, - }); - - } catch (e) { - console.error(e); - throw new Error( - `Unable to apply enforce bounds: ${e.message} - keyId: ${JSON.stringify(provingKeyId)}` - ); - } - } - - // If subject type exists, reveal that to prevent JSON-LD errors - const subjectType = verifiableCredential.credentialSubject.type; - if (subjectType) { - if (Array.isArray(subjectType)) { - await Promise.all( - subjectType.map((type, typeIdx) => - presentationInstance.addAttributeToReveal(idx, [`credentialSubject.type.${typeIdx}`]) - ) - ); - } else { - await presentationInstance.addAttributeToReveal(idx, ['credentialSubject.type']); - } - } - - const attributesToSkip = descriptorBounds[credIdx] - ? descriptorBounds[credIdx].map(bound => bound.attributeName) - : []; - const filteredAttributes = revealAttribsForCredential.filter( - attribute => !attributesToSkip.includes(attribute), - ); - const _pexRequiredAttributes = pexRequiredAttributes[credIdx] || []; - - _pexRequiredAttributes.forEach(attr => { - if (!filteredAttributes.includes(attr)) { - filteredAttributes.push(attr); - } - }); - - // Custom reveal attributes - if (Array.isArray(filteredAttributes) && filteredAttributes.length > 0) { + // If subject type exists, reveal that to prevent JSON-LD errors + const subjectType = verifiableCredential.credentialSubject.type; + if (subjectType) { + if (Array.isArray(subjectType)) { await Promise.all( - filteredAttributes.map((attrib) => - presentationInstance.addAttributeToReveal(idx, [attrib]) + subjectType.map((type, typeIdx) => + presentationInstance.addAttributeToReveal(idx, [`credentialSubject.type.${typeIdx}`]) ) ); + } else { + await presentationInstance.addAttributeToReveal(idx, ['credentialSubject.type']); } - // Derive a W3C Verifiable Credential JSON from the above presentation - const credentials = await presentationInstance.deriveCredentials(presentationOptions); - presentation.addCredentials(credentials); - return true; } - )); - // Any non-anonymous credentials should be added separately - if (nonAnoncredsVCs && nonAnoncredsVCs.length) { - presentation.addCredentials(nonAnoncredsVCs); + const attributesToSkip = descriptorBounds[credIdx] + ? descriptorBounds[credIdx].map(bound => bound.attributeName) + : []; + const filteredAttributes = revealAttribsForCredential.filter( + attribute => !attributesToSkip.includes(attribute), + ); + const _pexRequiredAttributes = pexRequiredAttributes[credIdx] || []; + + _pexRequiredAttributes.forEach(attr => { + if (!filteredAttributes.includes(attr)) { + filteredAttributes.push(attr); + } + }); + + // Custom reveal attributes + if (Array.isArray(filteredAttributes) && filteredAttributes.length > 0) { + await Promise.all( + filteredAttributes.map((attrib) => + presentationInstance.addAttributeToReveal(idx, [attrib]) + ) + ); + } + // Derive a W3C Verifiable Credential JSON from the above presentation + const credentials = await presentationInstance.deriveCredentials(presentationOptions); + vcsArray[credIdx] = credentials[0]; // overwrite in vcsarray to preserve order } + presentation.addCredentials(vcsArray); + // Shouldnt sign with anonymous credentials if (anoncredsVCs.length === 0) { // Holder signing (optional) From 78d9c124b9d947a6d55b7d6327f608842c29f99d Mon Sep 17 00:00:00 2001 From: Robert Vintila <33718597+gasher@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:39:39 +0200 Subject: [PATCH 3/6] Remove broken import from test. --- integration-tests/credentials.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-tests/credentials.test.ts b/integration-tests/credentials.test.ts index c4f77ba7..741b5afa 100644 --- a/integration-tests/credentials.test.ts +++ b/integration-tests/credentials.test.ts @@ -23,7 +23,6 @@ import {IWallet} from '@docknetwork/wallet-sdk-core/src/types'; import {WalletEvents} from '@docknetwork/wallet-sdk-wasm/src/modules/wallet'; import {API_MOCK_DISABLED} from '@docknetwork/wallet-sdk-wasm/src/services/test-utils'; import axios from 'axios'; -import {test} from '@docknetwork/wallet-sdk-wasm/src/services/blockchain/revocation'; import {CheqdRevocationCredential} from './data/credentials/cheqd-credentials'; const allCredentials = [ From df36992c3828f3a8018b19308978573b69034c92 Mon Sep 17 00:00:00 2001 From: Robert Vintila <33718597+gasher@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:17:37 +0200 Subject: [PATCH 4/6] Fix lint and test. --- packages/wasm/src/services/credential/service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wasm/src/services/credential/service.ts b/packages/wasm/src/services/credential/service.ts index 982aecf3..ec8ec756 100644 --- a/packages/wasm/src/services/credential/service.ts +++ b/packages/wasm/src/services/credential/service.ts @@ -176,7 +176,7 @@ export async function createPresentationSDK( vcsArray[credIdx] = credentials[0]; // overwrite in vcsarray to preserve order } - presentation.addCredentials(vcsArray); + vcsArray.forEach((vc) => vc && presentation.addCredential(vc)); // Shouldnt sign with anonymous credentials if (anoncredsVCs.length === 0) { From 577b6118b430357dd6b2c2da0f10092ca48e1bbe Mon Sep 17 00:00:00 2001 From: Robert Vintila <33718597+gasher@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:27:19 +0300 Subject: [PATCH 5/6] Fix pexForBounds being always used. --- packages/core/src/verification-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/verification-controller.ts b/packages/core/src/verification-controller.ts index bc3a714d..0dbd8cf3 100644 --- a/packages/core/src/verification-controller.ts +++ b/packages/core/src/verification-controller.ts @@ -195,7 +195,7 @@ export function createVerificationController({ ? keyDoc.id : `${keyDoc.controller}#keys-1`, domain: 'dock.io', - pexForBounds: templateJSON, + ...(isRangeProofTemplate(templateJSON) ? { pexForBounds: templateJSON } : {}), }); return presentation; From c239319ba32afff8535b973e68fdb0fe539bf81d Mon Sep 17 00:00:00 2001 From: Robert Vintila <33718597+gasher@users.noreply.github.com> Date: Wed, 9 Apr 2025 16:55:52 +0300 Subject: [PATCH 6/6] Fix pex required attributes filter. --- packages/core/src/verification-controller.ts | 2 +- packages/wasm/src/services/credential/pex-helpers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/verification-controller.ts b/packages/core/src/verification-controller.ts index 0dbd8cf3..bc3a714d 100644 --- a/packages/core/src/verification-controller.ts +++ b/packages/core/src/verification-controller.ts @@ -195,7 +195,7 @@ export function createVerificationController({ ? keyDoc.id : `${keyDoc.controller}#keys-1`, domain: 'dock.io', - ...(isRangeProofTemplate(templateJSON) ? { pexForBounds: templateJSON } : {}), + pexForBounds: templateJSON, }); return presentation; diff --git a/packages/wasm/src/services/credential/pex-helpers.js b/packages/wasm/src/services/credential/pex-helpers.js index 74422dd2..72d83dd8 100644 --- a/packages/wasm/src/services/credential/pex-helpers.js +++ b/packages/wasm/src/services/credential/pex-helpers.js @@ -268,7 +268,7 @@ export function getPexRequiredAttributes(pexRequest, selectedCredentials = []) { .map((inputDescriptor, index) => { return inputDescriptor.constraints.fields .filter(field => { - if (field.filter) { + if (field.filter || field.optional) { return false; }