Skip to content

Commit 0c60649

Browse files
authored
Merge pull request #394 from docknetwork/chore/bbs-revocation-unit-test
fix/bbs+ revocation with deriveCredential
2 parents c4cd811 + ea0ebdd commit 0c60649

19 files changed

+280
-79
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@docknetwork/sdk",
3-
"version": "7.3.1",
3+
"version": "7.3.2",
44
"main": "index.js",
55
"license": "MIT",
66
"repository": {

src/modules/accumulator.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,10 +374,11 @@ export default class AccumulatorModule extends WithParamsAndPublicKeys {
374374
* Field `nonce` is the last accepted nonce by the chain, the next write to the accumulator should increment the nonce by 1.
375375
* Field `accumulated` contains the current accumulated value.
376376
* @param id
377-
* @param withKeyAndParams
377+
* @param withKeyAndParams - Fetch both keys and params.
378+
* @param withKeyOnly - Fetch key only. This is useful when default params are used.
378379
* @returns {Promise<{created: *, lastModified: *}|null>}
379380
*/
380-
async getAccumulator(id, withKeyAndParams = false) {
381+
async getAccumulator(id, withKeyAndParams = false, withKeyOnly = false) {
381382
const resp = await this.api.query[this.moduleName].accumulators(
382383
id,
383384
);
@@ -400,8 +401,8 @@ export default class AccumulatorModule extends WithParamsAndPublicKeys {
400401
const keyId = common.keyRef[1].toNumber();
401402
accumulatorObj.keyRef = [typedHexDIDFromSubstrate(this.api, owner), keyId];
402403

403-
if (withKeyAndParams) {
404-
const pk = await this.getPublicKeyByHexDid(owner, keyId, true);
404+
if (withKeyAndParams || withKeyOnly) {
405+
const pk = await this.getPublicKeyByHexDid(owner, keyId, withKeyAndParams);
405406
if (pk !== null) {
406407
accumulatorObj.publicKey = pk;
407408
}

src/presentation.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { stringToU8a } from '@polkadot/util';
1111
import { ensureArray } from './utils/type-helpers';
1212

1313
import Bls12381BBSSignatureDock2022 from './utils/vc/crypto/Bls12381BBSSignatureDock2022';
14+
import { DOCK_ANON_CREDENTIAL_ID } from './utils/vc/crypto/common/DockCryptoSignatureProof';
1415
import {
1516
Bls12381BBSSigDockSigName,
1617
Bls12381PSSigDockSigName,
@@ -165,8 +166,10 @@ export default class Presentation {
165166
);
166167
}
167168

168-
return {
169+
const w3cFormattedCredential = {
169170
...credential.revealedAttributes,
171+
// ID required for W£C formatted credentials, if not revealed used a static URI
172+
id: credential.revealedAttributes.id || DOCK_ANON_CREDENTIAL_ID,
170173
'@context': JSON.parse(credential.revealedAttributes['@context']),
171174
type: JSON.parse(credential.revealedAttributes.type),
172175
credentialSchema: JSON.parse(credential.schema),
@@ -192,6 +195,12 @@ export default class Presentation {
192195
bounds: credential.bounds,
193196
},
194197
};
198+
199+
if (credential.status) {
200+
w3cFormattedCredential.credentialStatus = credential.status;
201+
}
202+
203+
return w3cFormattedCredential;
195204
});
196205
}
197206
}

src/utils/revocation.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export const RevRegIdByteSize = 32;
1717
// Each entry in revocation registry has byte size `RevEntryByteSize`
1818
export const RevEntryByteSize = 32;
1919

20+
const LD_SEC_TERM = 'https://ld.dock.io/security#';
21+
2022
/**
2123
* Return `credentialStatus` according to W3C spec when the revocation status is checked on Dock
2224
* @param registryId - Revocation registry id
@@ -76,14 +78,14 @@ export function getCredentialStatus(expanded) {
7678
* @param status
7779
* @returns {boolean}
7880
*/
79-
export const isRegistryRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(RevRegType) || type.includes(`/${RevRegType}`);
81+
export const isRegistryRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(RevRegType) || type.includes(`${LD_SEC_TERM}${RevRegType}`) || type.includes(`/${RevRegType}`);
8082

8183
/**
8284
* Returns `true` if supplied status is a accumulator revocation status.
8385
* @param status
8486
* @returns {boolean}
8587
*/
86-
export const isAccumulatorRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(VB_ACCUMULATOR_22) || type.includes(`/${VB_ACCUMULATOR_22}`);
88+
export const isAccumulatorRevocationStatus = ({ [credentialTypeField]: type }) => type.includes(VB_ACCUMULATOR_22) || type.includes(`${LD_SEC_TERM}${VB_ACCUMULATOR_22}`) || type.includes(`/${VB_ACCUMULATOR_22}`);
8789

8890
/**
8991
* Checks if a credential status has a registry revocation.

src/utils/vc/contexts/dock-bbs-v1.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@
3434
"@type": "@id",
3535
"@container": "@graph"
3636
},
37+
"DockVBAccumulator2022": {
38+
"@id": "https://ld.dock.io/security#DockVBAccumulator2022",
39+
"@context": {
40+
"@version": 1.1,
41+
"@protected": true,
42+
"id": "@id",
43+
"type": "@type",
44+
"accumulated": "https://ld.dock.io/security#accumulated",
45+
"revocationCheck": "https://ld.dock.io/security#revocationCheck",
46+
"revocationId": "https://ld.dock.io/security#revocationId",
47+
"extra": {
48+
"@id": "https://ld.dock.io/security#extra",
49+
"@context": {"@vocab": "https://ld.dock.io/security/extra"}
50+
}
51+
}
52+
},
3753
"Bls12381BBS+SignatureProofDock2022": {
3854
"@id": "https://ld.dock.io/security#Bls12381BBS+SignatureDock2022",
3955
"@context": {

src/utils/vc/contexts/dock-bbs23-v1.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@
3434
"@type": "@id",
3535
"@container": "@graph"
3636
},
37+
"DockVBAccumulator2022": {
38+
"@id": "https://ld.dock.io/security#DockVBAccumulator2022",
39+
"@context": {
40+
"@version": 1.1,
41+
"@protected": true,
42+
"id": "@id",
43+
"type": "@type",
44+
"accumulated": "https://ld.dock.io/security#accumulated",
45+
"revocationCheck": "https://ld.dock.io/security#revocationCheck",
46+
"revocationId": "https://ld.dock.io/security#revocationId",
47+
"extra": {
48+
"@id": "https://ld.dock.io/security#extra",
49+
"@context": {"@vocab": "https://ld.dock.io/security/extra"}
50+
}
51+
}
52+
},
3753
"Bls12381BBSSignatureProofDock2023": {
3854
"@id": "https://ld.dock.io/security#Bls12381BBSSignatureDock2023",
3955
"@context": {
@@ -57,6 +73,7 @@
5773
"unboundedPseudonyms": "https://ld.dock.io/security#unboundedPseudonyms",
5874
"attributeCiphertexts": "https://ld.dock.io/security#attributeCiphertexts",
5975
"attributeEqualities": "https://ld.dock.io/security#attributeEqualities",
76+
"accumulated": "https://ld.dock.io/security#accumulated",
6077
"proofValue": "https://ld.dock.io/security#proofValue",
6178
"nonce": "https://ld.dock.io/security#nonce",
6279
"proofPurpose": {

src/utils/vc/contexts/dock-ps-v1.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@
3434
"@type": "@id",
3535
"@container": "@graph"
3636
},
37+
"DockVBAccumulator2022": {
38+
"@id": "https://ld.dock.io/security#DockVBAccumulator2022",
39+
"@context": {
40+
"@version": 1.1,
41+
"@protected": true,
42+
"id": "@id",
43+
"type": "@type",
44+
"accumulated": "https://ld.dock.io/security#accumulated",
45+
"revocationCheck": "https://ld.dock.io/security#revocationCheck",
46+
"revocationId": "https://ld.dock.io/security#revocationId",
47+
"extra": {
48+
"@id": "https://ld.dock.io/security#extra",
49+
"@context": {"@vocab": "https://ld.dock.io/security/extra"}
50+
}
51+
}
52+
},
3753
"Bls12381PSSignatureProofDock2023": {
3854
"@id": "https://ld.dock.io/security#Bls12381PSSignatureDock2023",
3955
"@context": {
@@ -57,6 +73,7 @@
5773
"unboundedPseudonyms": "https://ld.dock.io/security#unboundedPseudonyms",
5874
"attributeCiphertexts": "https://ld.dock.io/security#attributeCiphertexts",
5975
"attributeEqualities": "https://ld.dock.io/security#attributeEqualities",
76+
"accumulated": "https://ld.dock.io/security#accumulated",
6077
"proofValue": "https://ld.dock.io/security#proofValue",
6178
"nonce": "https://ld.dock.io/security#nonce",
6279
"proofPurpose": {

src/utils/vc/crypto/common/DockCryptoSignatureProof.js

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import CustomLinkedDataSignature from './CustomLinkedDataSignature';
66

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

9+
export const DOCK_ANON_CREDENTIAL_ID = 'dock:anonymous:credential';
10+
911
/**
1012
* Defines commons for the `@docknetwork/crypto-wasm-ts` signature proofs.
1113
*/
@@ -63,7 +65,7 @@ export default withExtendedStaticProperties(
6365
expansionMap,
6466
});
6567

66-
const presentationJSON = this.constructor.convertToPresentation({
68+
const presentationJSON = this.constructor.derivedToAnoncredsPresentation({
6769
...document,
6870
proof,
6971
});
@@ -79,8 +81,9 @@ export default withExtendedStaticProperties(
7981
circomOutputs, blindedAttributesCircomOutputs,
8082
} = this;
8183

82-
if (!recreatedPres.verify(pks, accumulatorPublicKeys, predicateParams, circomOutputs, blindedAttributesCircomOutputs)) {
83-
throw new Error('Invalid signature');
84+
const res = recreatedPres.verify(pks, accumulatorPublicKeys, predicateParams, circomOutputs, blindedAttributesCircomOutputs);
85+
if (!res.verified) {
86+
throw new Error(`Invalid anoncreds presentation due to error: ${res.error}`);
8487
}
8588

8689
return { verified: true, verificationMethod };
@@ -93,38 +96,49 @@ export default withExtendedStaticProperties(
9396
* Converts a derived proof credential to the native presentation format
9497
* @param document
9598
*/
96-
static convertToPresentation(document) {
99+
static derivedToAnoncredsPresentation(document) {
97100
const {
98101
'@context': context,
99102
type,
100103
credentialSchema,
104+
credentialStatus,
101105
issuer: _issuer,
102106
issuanceDate: _issuanceDate,
103107
proof,
104108
...revealedAttributes
105109
} = document;
106110

111+
// ID wasnt revealed but placeholder was used to conform to W3C spec, trim it
112+
if (revealedAttributes.id === DOCK_ANON_CREDENTIAL_ID) {
113+
delete revealedAttributes.id;
114+
}
115+
116+
// TODO: This is wrong. This won't work with presentation from 2 or more credentials
117+
const c = {
118+
sigType: proof.sigType,
119+
version: proof.version,
120+
bounds: proof.bounds,
121+
schema: JSON.stringify(credentialSchema),
122+
revealedAttributes: {
123+
proof: {
124+
type: this.sigName,
125+
verificationMethod: proof.verificationMethod,
126+
},
127+
'@context': JSON.stringify(context),
128+
type: JSON.stringify(type),
129+
...revealedAttributes,
130+
},
131+
};
132+
if (credentialStatus !== undefined) {
133+
c.status = credentialStatus;
134+
}
107135
return {
108136
version: proof.version,
109137
nonce: proof.nonce,
110138
context: proof.context,
111139
spec: {
112140
credentials: [
113-
{
114-
sigType: proof.sigType,
115-
version: proof.version,
116-
bounds: proof.bounds,
117-
schema: JSON.stringify(credentialSchema),
118-
revealedAttributes: {
119-
proof: {
120-
type: this.sigName,
121-
verificationMethod: proof.verificationMethod,
122-
},
123-
'@context': JSON.stringify(context),
124-
type: JSON.stringify(type),
125-
...revealedAttributes,
126-
},
127-
},
141+
c,
128142
],
129143
attributeEqualities: proof.attributeEqualities,
130144
boundedPseudonyms: proof.boundedPseudonyms,

src/utils/vc/presentations.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export async function signPresentation(
240240
});
241241

242242
const documentLoader = defaultDocumentLoader(resolver);
243-
return jsigs.sign(presentation, {
243+
const signed = await jsigs.sign(presentation, {
244244
purpose,
245245
documentLoader,
246246
domain,
@@ -249,6 +249,16 @@ export async function signPresentation(
249249
suite,
250250
addSuiteContext,
251251
});
252+
253+
// Sometimes jsigs returns proof like [null, { proof }]
254+
// check for that case here and if there's only 1 proof store object instead
255+
if (Array.isArray(signed.proof)) {
256+
const validProofs = signed.proof.filter((p) => !!p);
257+
if (validProofs.length === 1) {
258+
signed.proof = validProofs.pop();
259+
}
260+
}
261+
return signed;
252262
}
253263

254264
export function isAnoncreds(presentation) {

src/verifiable-presentation.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@ class VerifiablePresentation {
155155
return this;
156156
}
157157

158+
/**
159+
* Add multiple Verifiable Credentials to this Presentation. Duplicates will be ignored.
160+
* @param {Array<object>} credentials - Verifiable Credential for the presentation
161+
* @returns {VerifiablePresentation}
162+
*/
163+
addCredentials(credentials) {
164+
credentials.forEach(this.addCredential.bind(this));
165+
}
166+
158167
/**
159168
* Define the JSON representation of a Verifiable Presentation.
160169
* @returns {object}
@@ -186,8 +195,8 @@ class VerifiablePresentation {
186195
resolver,
187196
compactProof,
188197
);
189-
this.proof = signedVP.proof.pop();
190198
this.context = signedVP['@context'];
199+
this.proof = signedVP.proof;
191200
return this;
192201
}
193202

0 commit comments

Comments
 (0)