Skip to content
Merged
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
6 changes: 1 addition & 5 deletions integration-tests/verification-flow/range-proofs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ describe('Range proofs verification', () => {
template: proofRequest.qr,
});

// We can keep the attributes to reveal empty
// Range proof attributes will be handled automatically during presentation creation
// and will not be revealed in the presentation
// even if we include them in the attributesToReveal, they will be ignored
const attributesToReveal = [];

controller.selectedCredentials.set(credential.id, {
Expand Down Expand Up @@ -149,7 +145,7 @@ describe('Range proofs verification', () => {
const presentation = await controller.createPresentation();

// Presentation issuanceDate should not be equal to the credential issuanceDate
// The credential SDK will genreate a presentation timestamp instead
// The credential SDK will generate a presentation timestamp instead
expect(presentation.verifiableCredential[0].issuanceDate).not.toBe(
credential.issuanceDate,
);
Expand Down
162 changes: 89 additions & 73 deletions packages/core/src/verification-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ export enum VerificationStatus {
SelectingCredentials = 'SelectingCredentials',
}

function isRangeProofTemplate(templateJSON) {
return templateJSON.proving_key;
}

type CredentialId = string;
type CredentialSelection = {
credential: any;
Expand Down Expand Up @@ -52,7 +48,6 @@ export function createVerificationController({
let filteredCredentials = [];
let selectedCredentials: CredentialSelectionMap = new Map();
let selectedDID = null;
let provingKey = null;

if (!credentialProvider) {
credentialProvider = createCredentialProvider({wallet});
Expand All @@ -62,23 +57,6 @@ export function createVerificationController({
didProvider = createDIDProvider({wallet});
}

async function fetchProvingKey(templateJSON: any) {
if (templateJSON.proving_key) {
setState(VerificationStatus.FetchingProvingKey);
try {
provingKey = await axios
.get(templateJSON.proving_key)
.then(res => res.data);
} catch (err) {
setState(VerificationStatus.Error, {
message: 'failed_to_fetch_proving_key',
});

throw err;
}
}
}

async function start({template}: {template: string | any}) {
setState(VerificationStatus.LoadingTemplate);

Expand All @@ -96,7 +74,6 @@ export function createVerificationController({
selectedDID = dids[0].didDocument.id;
templateJSON = await getJSON(template);

await fetchProvingKey(templateJSON);
await loadCredentials();

setState(VerificationStatus.SelectingCredentials);
Expand Down Expand Up @@ -154,71 +131,110 @@ export function createVerificationController({
return credentialServiceRPC.isKvacCredential({credential});
}

async function deriveNonBbsCredentials(sdJwtSelections, regularSelections) {
const credentials = [];

for (const sel of sdJwtSelections) {
const derived = await credentialServiceRPC.createSDJWTPresentation({
attributesToReveal: sel.attributesToReveal,
credential: sel.credential._sd_jwt.encoded,
});
credentials.push(derived);
}

for (const sel of regularSelections) {
credentials.push(sel.credential);
}

return credentials;
}

function getKeyId(keyDoc) {
return keyDoc.controller.startsWith('did:key:')
? keyDoc.id
: `${keyDoc.controller}#keys-1`;
}

async function assembleSignedPresentation(credentials, keyDoc) {
return credentialServiceRPC.createPresentation({
credentials,
challenge: templateJSON.nonce,
keyDoc,
id: getKeyId(keyDoc),
domain: 'dock.io',
});
}

async function createPresentation() {
assert(!!selectedDID, 'No DID selected');
assert(!!selectedCredentials.size, 'No credentials selected');

if (isRangeProofTemplate(templateJSON)) {
// TODO: Implement proving key usage for range-proofs
assert(!!provingKey, 'No proving key found');
}
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 sdJwtSelections = [];
const bbsKvacSelections = [];
const regularSelections = [];

for (const credentialSelection of selectedCredentials.values()) {
const isBBS = await isBBSPlusCredential(credentialSelection.credential);
const isKVAC = await isKvacCredential(credentialSelection.credential);

if (credentialSelection.credential._sd_jwt) {
const derivedCredential =
await credentialServiceRPC.createSDJWTPresentation({
attributesToReveal: credentialSelection.attributesToReveal,
credential: credentialSelection.credential._sd_jwt.encoded,
});

credentials.push(derivedCredential);
} else 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]);
sdJwtSelections.push(credentialSelection);
} else {
credentials.push(credentialSelection.credential);
const isBBS = await isBBSPlusCredential(credentialSelection.credential);
const isKVAC = await isKvacCredential(credentialSelection.credential);
if (isBBS || isKVAC) {
bbsKvacSelections.push(credentialSelection);
} else {
regularSelections.push(credentialSelection);
}
}
}

const didKeyPairList = await didProvider.getDIDKeyPairs();
const keyDoc = didKeyPairList.find(doc => doc.controller === selectedDID);
if (bbsKvacSelections.length > 0) {
const credentialsWithWitness = await Promise.all(
bbsKvacSelections.map(async sel => ({
credential: sel.credential,
witness: await credentialProvider.getMembershipWitness(sel.credential.id),
attributesToReveal: sel.attributesToReveal || [],
})),
);

assert(keyDoc, `No key pair found for the selected DID ${selectedDID}`);
// Pure BBS+/KVAC: use generatePresentationFromPex end-to-end
if (sdJwtSelections.length === 0 && regularSelections.length === 0) {
return credentialServiceRPC.generatePresentationFromPex({
credentials: credentialsWithWitness,
pexRequest: templateJSON.request,
holderKeyDoc: keyDoc,
holderDid: selectedDID,
challenge: templateJSON.nonce,
domain: 'dock.io',
boundCheckSnarkKey: templateJSON.boundCheckSnarkKey,
skipSigning: true,
});
}

const presentation = await credentialServiceRPC.createPresentation({
credentials,
challenge: templateJSON.nonce,
keyDoc,
id: keyDoc.controller.startsWith('did:key:')
? keyDoc.id
: `${keyDoc.controller}#keys-1`,
domain: 'dock.io',
});
// 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 || []), 'id'],
})),
});

const nonBbsCredentials = await deriveNonBbsCredentials(sdJwtSelections, regularSelections);
return assembleSignedPresentation(
[...derivedCredentials, ...nonBbsCredentials],
keyDoc,
);
}

return presentation;
// No BBS+/KVAC: handle SD-JWT and regular only
const credentials = await deriveNonBbsCredentials(sdJwtSelections, regularSelections);
return assembleSignedPresentation(credentials, keyDoc);
}

/**
Expand Down
7 changes: 7 additions & 0 deletions packages/wasm/src/services/credential/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ export const validation = {
'publicKeyBase58 is not present',
);
},
generatePresentationFromPex: params => {
const {credentials, pexRequest, challenge} = params;
assert(Array.isArray(credentials), 'invalid credentials');
assert(credentials.length > 0, 'no credential found');
assert(pexRequest, 'pexRequest is required');
assert(challenge, 'challenge is required');
},
createPresentation: params => {
const {credentials, keyDoc, challenge, id} = params;
assert(typeof id === 'string', 'invalid id');
Expand Down
Loading
Loading