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 = [ 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/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; } diff --git a/packages/wasm/src/services/credential/service.ts b/packages/wasm/src/services/credential/service.ts index dc45fbba..ec8ec756 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,144 @@ 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 revealAttribsArray = Array.isArray(revealAttribs[0]) ? revealAttribs : [revealAttribs]; + + const presentation = new VerifiablePresentation(presentationId); + + let pexRequiredAttributes = []; + if (pexForBounds?.request) { + pexRequiredAttributes = getPexRequiredAttributes( + pexForBounds.request, + vcsArray, + ); + } + + const { provingKey, provingKeyId } = pexForBounds && hasProvingKey(pexForBounds) ? await fetchProvingKey(pexForBounds) : {}; + + for (let i = 0; i < anoncredsVCs.length; i++) { + const vc = anoncredsVCs[i]; + 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)) { + 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); + vcsArray[credIdx] = credentials[0]; // overwrite in vcsarray to preserve order + } + + vcsArray.forEach((vc) => vc && presentation.addCredential(vc)); + + // 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 +244,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) {