Skip to content

Commit

Permalink
Merge pull request #394 from docknetwork/chore/bbs-revocation-unit-test
Browse files Browse the repository at this point in the history
fix/bbs+ revocation with deriveCredential
  • Loading branch information
cykoder authored Feb 16, 2024
2 parents c4cd811 + ea0ebdd commit 0c60649
Show file tree
Hide file tree
Showing 19 changed files with 280 additions and 79 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docknetwork/sdk",
"version": "7.3.1",
"version": "7.3.2",
"main": "index.js",
"license": "MIT",
"repository": {
Expand Down
9 changes: 5 additions & 4 deletions src/modules/accumulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,10 +374,11 @@ export default class AccumulatorModule extends WithParamsAndPublicKeys {
* Field `nonce` is the last accepted nonce by the chain, the next write to the accumulator should increment the nonce by 1.
* Field `accumulated` contains the current accumulated value.
* @param id
* @param withKeyAndParams
* @param withKeyAndParams - Fetch both keys and params.
* @param withKeyOnly - Fetch key only. This is useful when default params are used.
* @returns {Promise<{created: *, lastModified: *}|null>}
*/
async getAccumulator(id, withKeyAndParams = false) {
async getAccumulator(id, withKeyAndParams = false, withKeyOnly = false) {
const resp = await this.api.query[this.moduleName].accumulators(
id,
);
Expand All @@ -400,8 +401,8 @@ export default class AccumulatorModule extends WithParamsAndPublicKeys {
const keyId = common.keyRef[1].toNumber();
accumulatorObj.keyRef = [typedHexDIDFromSubstrate(this.api, owner), keyId];

if (withKeyAndParams) {
const pk = await this.getPublicKeyByHexDid(owner, keyId, true);
if (withKeyAndParams || withKeyOnly) {
const pk = await this.getPublicKeyByHexDid(owner, keyId, withKeyAndParams);
if (pk !== null) {
accumulatorObj.publicKey = pk;
}
Expand Down
11 changes: 10 additions & 1 deletion src/presentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { stringToU8a } from '@polkadot/util';
import { ensureArray } from './utils/type-helpers';

import Bls12381BBSSignatureDock2022 from './utils/vc/crypto/Bls12381BBSSignatureDock2022';
import { DOCK_ANON_CREDENTIAL_ID } from './utils/vc/crypto/common/DockCryptoSignatureProof';
import {
Bls12381BBSSigDockSigName,
Bls12381PSSigDockSigName,
Expand Down Expand Up @@ -165,8 +166,10 @@ export default class Presentation {
);
}

return {
const w3cFormattedCredential = {
...credential.revealedAttributes,
// ID required for W£C formatted credentials, if not revealed used a static URI
id: credential.revealedAttributes.id || DOCK_ANON_CREDENTIAL_ID,
'@context': JSON.parse(credential.revealedAttributes['@context']),
type: JSON.parse(credential.revealedAttributes.type),
credentialSchema: JSON.parse(credential.schema),
Expand All @@ -192,6 +195,12 @@ export default class Presentation {
bounds: credential.bounds,
},
};

if (credential.status) {
w3cFormattedCredential.credentialStatus = credential.status;
}

return w3cFormattedCredential;
});
}
}
6 changes: 4 additions & 2 deletions src/utils/revocation.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const RevRegIdByteSize = 32;
// Each entry in revocation registry has byte size `RevEntryByteSize`
export const RevEntryByteSize = 32;

const LD_SEC_TERM = 'https://ld.dock.io/security#';

/**
* Return `credentialStatus` according to W3C spec when the revocation status is checked on Dock
* @param registryId - Revocation registry id
Expand Down Expand Up @@ -76,14 +78,14 @@ export function getCredentialStatus(expanded) {
* @param status
* @returns {boolean}
*/
export const isRegistryRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(RevRegType) || type.includes(`/${RevRegType}`);
export const isRegistryRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(RevRegType) || type.includes(`${LD_SEC_TERM}${RevRegType}`) || type.includes(`/${RevRegType}`);

/**
* Returns `true` if supplied status is a accumulator revocation status.
* @param status
* @returns {boolean}
*/
export const isAccumulatorRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(VB_ACCUMULATOR_22) || type.includes(`/${VB_ACCUMULATOR_22}`);
export const isAccumulatorRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(VB_ACCUMULATOR_22) || type.includes(`${LD_SEC_TERM}${VB_ACCUMULATOR_22}`) || type.includes(`/${VB_ACCUMULATOR_22}`);

/**
* Checks if a credential status has a registry revocation.
Expand Down
16 changes: 16 additions & 0 deletions src/utils/vc/contexts/dock-bbs-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@
"@type": "@id",
"@container": "@graph"
},
"DockVBAccumulator2022": {
"@id": "https://ld.dock.io/security#DockVBAccumulator2022",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"accumulated": "https://ld.dock.io/security#accumulated",
"revocationCheck": "https://ld.dock.io/security#revocationCheck",
"revocationId": "https://ld.dock.io/security#revocationId",
"extra": {
"@id": "https://ld.dock.io/security#extra",
"@context": {"@vocab": "https://ld.dock.io/security/extra"}
}
}
},
"Bls12381BBS+SignatureProofDock2022": {
"@id": "https://ld.dock.io/security#Bls12381BBS+SignatureDock2022",
"@context": {
Expand Down
17 changes: 17 additions & 0 deletions src/utils/vc/contexts/dock-bbs23-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@
"@type": "@id",
"@container": "@graph"
},
"DockVBAccumulator2022": {
"@id": "https://ld.dock.io/security#DockVBAccumulator2022",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"accumulated": "https://ld.dock.io/security#accumulated",
"revocationCheck": "https://ld.dock.io/security#revocationCheck",
"revocationId": "https://ld.dock.io/security#revocationId",
"extra": {
"@id": "https://ld.dock.io/security#extra",
"@context": {"@vocab": "https://ld.dock.io/security/extra"}
}
}
},
"Bls12381BBSSignatureProofDock2023": {
"@id": "https://ld.dock.io/security#Bls12381BBSSignatureDock2023",
"@context": {
Expand All @@ -57,6 +73,7 @@
"unboundedPseudonyms": "https://ld.dock.io/security#unboundedPseudonyms",
"attributeCiphertexts": "https://ld.dock.io/security#attributeCiphertexts",
"attributeEqualities": "https://ld.dock.io/security#attributeEqualities",
"accumulated": "https://ld.dock.io/security#accumulated",
"proofValue": "https://ld.dock.io/security#proofValue",
"nonce": "https://ld.dock.io/security#nonce",
"proofPurpose": {
Expand Down
17 changes: 17 additions & 0 deletions src/utils/vc/contexts/dock-ps-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@
"@type": "@id",
"@container": "@graph"
},
"DockVBAccumulator2022": {
"@id": "https://ld.dock.io/security#DockVBAccumulator2022",
"@context": {
"@version": 1.1,
"@protected": true,
"id": "@id",
"type": "@type",
"accumulated": "https://ld.dock.io/security#accumulated",
"revocationCheck": "https://ld.dock.io/security#revocationCheck",
"revocationId": "https://ld.dock.io/security#revocationId",
"extra": {
"@id": "https://ld.dock.io/security#extra",
"@context": {"@vocab": "https://ld.dock.io/security/extra"}
}
}
},
"Bls12381PSSignatureProofDock2023": {
"@id": "https://ld.dock.io/security#Bls12381PSSignatureDock2023",
"@context": {
Expand All @@ -57,6 +73,7 @@
"unboundedPseudonyms": "https://ld.dock.io/security#unboundedPseudonyms",
"attributeCiphertexts": "https://ld.dock.io/security#attributeCiphertexts",
"attributeEqualities": "https://ld.dock.io/security#attributeEqualities",
"accumulated": "https://ld.dock.io/security#accumulated",
"proofValue": "https://ld.dock.io/security#proofValue",
"nonce": "https://ld.dock.io/security#nonce",
"proofPurpose": {
Expand Down
52 changes: 33 additions & 19 deletions src/utils/vc/crypto/common/DockCryptoSignatureProof.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import CustomLinkedDataSignature from './CustomLinkedDataSignature';

const SUITE_CONTEXT_URL = 'https://www.w3.org/2018/credentials/v1';

export const DOCK_ANON_CREDENTIAL_ID = 'dock:anonymous:credential';

/**
* Defines commons for the `@docknetwork/crypto-wasm-ts` signature proofs.
*/
Expand Down Expand Up @@ -63,7 +65,7 @@ export default withExtendedStaticProperties(
expansionMap,
});

const presentationJSON = this.constructor.convertToPresentation({
const presentationJSON = this.constructor.derivedToAnoncredsPresentation({
...document,
proof,
});
Expand All @@ -79,8 +81,9 @@ export default withExtendedStaticProperties(
circomOutputs, blindedAttributesCircomOutputs,
} = this;

if (!recreatedPres.verify(pks, accumulatorPublicKeys, predicateParams, circomOutputs, blindedAttributesCircomOutputs)) {
throw new Error('Invalid signature');
const res = recreatedPres.verify(pks, accumulatorPublicKeys, predicateParams, circomOutputs, blindedAttributesCircomOutputs);
if (!res.verified) {
throw new Error(`Invalid anoncreds presentation due to error: ${res.error}`);
}

return { verified: true, verificationMethod };
Expand All @@ -93,38 +96,49 @@ export default withExtendedStaticProperties(
* Converts a derived proof credential to the native presentation format
* @param document
*/
static convertToPresentation(document) {
static derivedToAnoncredsPresentation(document) {
const {
'@context': context,
type,
credentialSchema,
credentialStatus,
issuer: _issuer,
issuanceDate: _issuanceDate,
proof,
...revealedAttributes
} = document;

// ID wasnt revealed but placeholder was used to conform to W3C spec, trim it
if (revealedAttributes.id === DOCK_ANON_CREDENTIAL_ID) {
delete revealedAttributes.id;
}

// TODO: This is wrong. This won't work with presentation from 2 or more credentials
const c = {
sigType: proof.sigType,
version: proof.version,
bounds: proof.bounds,
schema: JSON.stringify(credentialSchema),
revealedAttributes: {
proof: {
type: this.sigName,
verificationMethod: proof.verificationMethod,
},
'@context': JSON.stringify(context),
type: JSON.stringify(type),
...revealedAttributes,
},
};
if (credentialStatus !== undefined) {
c.status = credentialStatus;
}
return {
version: proof.version,
nonce: proof.nonce,
context: proof.context,
spec: {
credentials: [
{
sigType: proof.sigType,
version: proof.version,
bounds: proof.bounds,
schema: JSON.stringify(credentialSchema),
revealedAttributes: {
proof: {
type: this.sigName,
verificationMethod: proof.verificationMethod,
},
'@context': JSON.stringify(context),
type: JSON.stringify(type),
...revealedAttributes,
},
},
c,
],
attributeEqualities: proof.attributeEqualities,
boundedPseudonyms: proof.boundedPseudonyms,
Expand Down
12 changes: 11 additions & 1 deletion src/utils/vc/presentations.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export async function signPresentation(
});

const documentLoader = defaultDocumentLoader(resolver);
return jsigs.sign(presentation, {
const signed = await jsigs.sign(presentation, {
purpose,
documentLoader,
domain,
Expand All @@ -249,6 +249,16 @@ export async function signPresentation(
suite,
addSuiteContext,
});

// Sometimes jsigs returns proof like [null, { proof }]
// check for that case here and if there's only 1 proof store object instead
if (Array.isArray(signed.proof)) {
const validProofs = signed.proof.filter((p) => !!p);
if (validProofs.length === 1) {
signed.proof = validProofs.pop();
}
}
return signed;
}

export function isAnoncreds(presentation) {
Expand Down
11 changes: 10 additions & 1 deletion src/verifiable-presentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ class VerifiablePresentation {
return this;
}

/**
* Add multiple Verifiable Credentials to this Presentation. Duplicates will be ignored.
* @param {Array<object>} credentials - Verifiable Credential for the presentation
* @returns {VerifiablePresentation}
*/
addCredentials(credentials) {
credentials.forEach(this.addCredential.bind(this));
}

/**
* Define the JSON representation of a Verifiable Presentation.
* @returns {object}
Expand Down Expand Up @@ -186,8 +195,8 @@ class VerifiablePresentation {
resolver,
compactProof,
);
this.proof = signedVP.proof.pop();
this.context = signedVP['@context'];
this.proof = signedVP.proof;
return this;
}

Expand Down
27 changes: 9 additions & 18 deletions tests/create-presentation.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {
DEFAULT_CONTEXT_V1_URL,
} from '../src/utils/vc/constants';
import VerifiablePresentation from '../src/verifiable-presentation';

/**
* Create an unsigned Verifiable Presentation
Expand All @@ -9,22 +7,15 @@ import {
* @param {string} [holder] - optional presentation holder url
* @return {object} verifiable presentation.
*/
export function createPresentation(verifiableCredential, id, holder = null) {
const presentation = {
'@context': [DEFAULT_CONTEXT_V1_URL],
type: ['VerifiablePresentation'],
};

if (verifiableCredential) {
const credentials = [].concat(verifiableCredential);
presentation.verifiableCredential = credentials;
}
if (id) {
presentation.id = id;
export function createPresentation(verifiableCredential, id = 'http://example.edu/presentation/2803', holder = null) {
const presentation = new VerifiablePresentation(id);
if (Array.isArray(verifiableCredential)) {
presentation.addCredentials(verifiableCredential);
} else {
presentation.addCredential(verifiableCredential);
}
if (holder) {
presentation.holder = holder;
presentation.setHolder(holder);
}

return presentation;
return presentation.toJSON();
}
Loading

0 comments on commit 0c60649

Please sign in to comment.