Skip to content

Commit

Permalink
Merge pull request #367 from docknetwork/fix/anoncreds-derived-bounds…
Browse files Browse the repository at this point in the history
…-check

Anoncreds derived bounds check
  • Loading branch information
cykoder authored Sep 29, 2023
2 parents 331749e + 099603b commit 8c705cd
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 13 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": "6.5.0",
"version": "6.5.1",
"main": "index.js",
"license": "MIT",
"repository": {
Expand Down
1 change: 1 addition & 0 deletions src/presentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export default class Presentation {
unboundedPseudonyms: presentation.spec.unboundedPseudonyms,
version: credential.version,
sigType: credential.sigType,
bounds: credential.bounds,
},
};
});
Expand Down
4 changes: 4 additions & 0 deletions src/utils/vc/contexts/dock-bbs-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
},
"domain": "https://ld.dock.io/security#domain",
"context": "https://ld.dock.io/security#context",
"bounds": {
"@id": "https://ld.dock.io/security#bounds",
"@context": {"@vocab": "https://ld.dock.io/security/bounds"}
},
"sigType": "https://ld.dock.io/security#sigType",
"boundedPseudonyms": "https://ld.dock.io/security#boundedPseudonyms",
"unboundedPseudonyms": "https://ld.dock.io/security#unboundedPseudonyms",
Expand Down
4 changes: 4 additions & 0 deletions src/utils/vc/contexts/dock-bbs23-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
},
"domain": "https://ld.dock.io/security#domain",
"context": "https://ld.dock.io/security#context",
"bounds": {
"@id": "https://ld.dock.io/security#bounds",
"@context": {"@vocab": "https://ld.dock.io/security/bounds"}
},
"sigType": "https://ld.dock.io/security#sigType",
"boundedPseudonyms": "https://ld.dock.io/security#boundedPseudonyms",
"unboundedPseudonyms": "https://ld.dock.io/security#unboundedPseudonyms",
Expand Down
4 changes: 4 additions & 0 deletions src/utils/vc/contexts/dock-ps-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
},
"domain": "https://ld.dock.io/security#domain",
"context": "https://ld.dock.io/security#context",
"bounds": {
"@id": "https://ld.dock.io/security#bounds",
"@context": {"@vocab": "https://ld.dock.io/security/bounds"}
},
"sigType": "https://ld.dock.io/security#sigType",
"boundedPseudonyms": "https://ld.dock.io/security#boundedPseudonyms",
"unboundedPseudonyms": "https://ld.dock.io/security#unboundedPseudonyms",
Expand Down
22 changes: 16 additions & 6 deletions src/utils/vc/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,12 @@ export async function verifyCredential(
controller = null,
suite = [],
verifyDates = true,

// Anoncreds params
predicateParams = null,
accumulatorPublicKeys = null,
circomOutputs = null,
blindedAttributesCircomOutputs = null,
} = {},
) {
if (documentLoader && resolver) {
Expand Down Expand Up @@ -321,17 +327,21 @@ export async function verifyCredential(
return { verified };
}

// Specify certain parameters for anoncreds
const anoncredsParams = {
accumulatorPublicKeys, predicateParams, circomOutputs, blindedAttributesCircomOutputs,
};
const fullSuite = [
new Ed25519Signature2018(),
new EcdsaSepc256k1Signature2019(),
new Sr25519Signature2020(),
new Bls12381BBSSignatureDock2022(),
new Bls12381BBSSignatureProofDock2022(),
new Bls12381BBSSignatureDock2023(),
new Bls12381BBSSignatureProofDock2023(),
new Bls12381PSSignatureDock2023(),
new Bls12381PSSignatureProofDock2023(),
new JsonWebSignature2020(),
new Bls12381BBSSignatureDock2022(anoncredsParams),
new Bls12381BBSSignatureProofDock2022(anoncredsParams),
new Bls12381BBSSignatureDock2023(anoncredsParams),
new Bls12381BBSSignatureProofDock2023(anoncredsParams),
new Bls12381PSSignatureDock2023(anoncredsParams),
new Bls12381PSSignatureProofDock2023(anoncredsParams),
...suite,
];

Expand Down
14 changes: 12 additions & 2 deletions src/utils/vc/crypto/common/DockCryptoSignatureProof.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export default withExtendedStaticProperties(
};

this.verificationMethod = verificationMethod;
this.accumulatorPublicKeys = options.accumulatorPublicKeys;
this.predicateParams = options.predicateParams;
this.circomOutputs = options.circomOutputs;
this.blindedAttributesCircomOutputs = options.blindedAttributesCircomOutputs;
}

async verifyProof({
Expand All @@ -70,7 +74,12 @@ export default withExtendedStaticProperties(
return new this.constructor.Signature.KeyPair.PublicKey(pkRaw);
});

if (!recreatedPres.verify(pks)) {
const {
accumulatorPublicKeys, predicateParams,
circomOutputs, blindedAttributesCircomOutputs,
} = this;

if (!recreatedPres.verify(pks, accumulatorPublicKeys, predicateParams, circomOutputs, blindedAttributesCircomOutputs)) {
throw new Error('Invalid signature');
}

Expand All @@ -96,14 +105,15 @@ export default withExtendedStaticProperties(
} = document;

return {
version: '0.1.0',
version: '0.1.0', // TODO: should this be retrieved from the doc somehow?
nonce: proof.nonce,
context: proof.context,
spec: {
credentials: [
{
sigType: proof.sigType,
version: proof.version,
bounds: proof.bounds,
schema: JSON.stringify(credentialSchema),
revealedAttributes: {
proof: {
Expand Down
6 changes: 5 additions & 1 deletion src/utils/vc/presentations.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ export function isAnoncreds(presentation) {

export async function verifyAnoncreds(presentation, options = {}) {
const documentLoader = options.documentLoader || defaultDocumentLoader(options.resolver);
const {
predicateParams, accumulatorPublicKeys,
circomOutputs, blindedAttributesCircomOutputs,
} = options;

const keyDocuments = await Promise.all(
presentation.spec.credentials.map((c, idx) => {
Expand Down Expand Up @@ -315,5 +319,5 @@ export async function verifyAnoncreds(presentation, options = {}) {
}
});

return recreatedPres.verify(pks);
return recreatedPres.verify(pks, accumulatorPublicKeys, predicateParams, circomOutputs, blindedAttributesCircomOutputs);
}
115 changes: 113 additions & 2 deletions tests/integration/anoncreds/derived-credentials.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { randomAsHex } from '@polkadot/util-crypto';
import { stringToU8a, u8aToHex } from '@polkadot/util';
import { initializeWasm } from '@docknetwork/crypto-wasm-ts';
import { initializeWasm, BoundCheckSnarkSetup } from '@docknetwork/crypto-wasm-ts';
import { DockAPI } from '../../../src';
import {
FullNodeEndpoint,
Expand Down Expand Up @@ -156,7 +156,7 @@ for (const {
);
}, 30000);

async function createAndVerifyPresentation(credentials) {
async function createAndVerifyPresentation(credentials, verifyOptions = {}) {
const holderKey = getKeyDoc(
holder3DID,
dock.keyring.addFromUri(holder3KeySeed, null, 'sr25519'),
Expand Down Expand Up @@ -202,6 +202,7 @@ for (const {
challenge: chal,
domain,
resolver,
...verifyOptions,
});

expect(result.verified).toBe(true);
Expand Down Expand Up @@ -305,6 +306,116 @@ for (const {
await createAndVerifyPresentation(credentials);
}, 30000);

test('Holder creates a derived verifiable credential from a credential with range proofs', async () => {
const provingKeyId = 'provingKeyId';
const pk = BoundCheckSnarkSetup();
const provingKey = pk.decompress();

const issuerKey = getKeyDoc(did1, keypair, keypair.type, keypair.id);
const unsignedCred = {
...credentialJSON,
issuer: did1,
};

const presentationOptions = {
nonce: stringToU8a('noncetest'),
context: 'my context',
};

// Create W3C credential
const credential = await issueCredential(issuerKey, unsignedCred);

// Begin to derive a credential from the above issued one
const presentationInstance = new Presentation();
const idx = await presentationInstance.addCredentialToPresent(
credential,
{ resolver },
);

// NOTE: revealing subject type because of JSON-LD processing for this certain credential
// you may not always need to do this depending on your JSON-LD contexts
await presentationInstance.addAttributeToReveal(idx, [
'credentialSubject.type.0',
]);
await presentationInstance.addAttributeToReveal(idx, [
'credentialSubject.type.1',
]);

// Enforce LPR number to be between values aswell as revealed
// NOTE: unlike other tests, we cannot "reveal" this value and enforce bounds at the same time!
presentationInstance.presBuilder.enforceBounds(
idx,
'credentialSubject.lprNumber',
1233,
1235,
provingKeyId,
provingKey,
);

// Enforce issuance date to be between values
// NOTE: we dont need to set proving key value here (must still set ID though!) as its done above, should pass undefined
presentationInstance.presBuilder.enforceBounds(
idx,
'issuanceDate',
new Date('2019-10-01'),
new Date('2020-01-01'),
provingKeyId,
undefined,
);

// Derive a W3C Verifiable Credential JSON from the above presentation
const credentials = await presentationInstance.deriveCredentials(
presentationOptions,
);
expect(credentials.length).toEqual(1);
expect(credentials[0].proof).toBeDefined();
expect(credentials[0].proof.bounds).toBeDefined();
expect(credentials[0].proof.bounds).toEqual({
issuanceDate: {
min: 1569888000000,
max: 1577836800000,
paramId: 'provingKeyId',
protocol: 'LegoGroth16',
},
credentialSubject: {
lprNumber: {
min: 1233,
max: 1235,
paramId: 'provingKeyId',
protocol: 'LegoGroth16',
},
},
});
expect(credentials[0]).toHaveProperty('credentialSubject');
expect(credentials[0].credentialSubject).toMatchObject(
expect.objectContaining({
type: unsignedCred.credentialSubject.type,
}),
);

const reconstructedPres = convertToPresentation(credentials[0]);
expect(reconstructedPres.proof).toBeDefined();
expect(reconstructedPres.spec.credentials[0].bounds).toEqual(credentials[0].proof.bounds);

// Setup predicate params with the verifying key for range proofs
const predicateParams = new Map();
predicateParams.set(provingKeyId, pk.getVerifyingKey());

// Try to verify the derived credential alone
const credentialResult = await verifyCredential(credentials[0], {
resolver,
predicateParams,
});
if (credentialResult.error) {
console.log('credentialResult.error', JSON.stringify(credentialResult.error, null, 2));
}
expect(credentialResult.error).toBe(undefined);
expect(credentialResult.verified).toBe(true);

// Create a VP and verify it from this credential
await createAndVerifyPresentation(credentials, { predicateParams });
}, 60000);

afterAll(async () => {
await dock.disconnect();
}, 10000);
Expand Down
76 changes: 75 additions & 1 deletion tests/integration/anoncreds/presentation.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { randomAsHex } from '@polkadot/util-crypto';
import { u8aToHex, stringToU8a } from '@polkadot/util';
import b58 from 'bs58';
import { initializeWasm } from '@docknetwork/crypto-wasm-ts';
import { BoundCheckSnarkSetup, initializeWasm } from '@docknetwork/crypto-wasm-ts';
import { DockAPI } from '../../../src';
import {
FullNodeEndpoint,
Expand Down Expand Up @@ -132,6 +132,80 @@ for (const {
keypair.id = publicKey[1].id;
}, 30000);

test('expect to range proofs', async () => {
const provingKeyId = 'provingKeyId';
const pk = BoundCheckSnarkSetup();
const provingKey = pk.decompress();
const presentationInstance = new Presentation();
const issuerKey = getKeyDoc(did1, keypair, keypair.type, keypair.id);
const unsignedCred = {
...credentialJSON,
issuer: did1,
};

const credential = await issueCredential(issuerKey, unsignedCred);

const idx = await presentationInstance.addCredentialToPresent(
credential,
{ resolver },
);

await presentationInstance.addAttributeToReveal(idx, [
'credentialSubject.lprNumber',
]);

// Enforce issuance date to be between values
presentationInstance.presBuilder.enforceBounds(
idx,
'issuanceDate',
new Date('2019-10-01'),
new Date('2020-01-01'),
provingKeyId,
provingKey,
);

const presentation = await presentationInstance.createPresentation();

expect(presentation.spec.credentials[0].bounds).toBeDefined();
expect(presentation.spec.credentials[0].bounds).toEqual({
issuanceDate: {
min: 1569888000000,
max: 1577836800000,
paramId: 'provingKeyId',
protocol: 'LegoGroth16',
},
});

expect(
presentation.spec.credentials[0].revealedAttributes,
).toHaveProperty('credentialSubject');
expect(
presentation.spec.credentials[0].revealedAttributes.credentialSubject,
).toHaveProperty('lprNumber', 1234);

// Ensure verificationMethod & type is revealed always
expect(
presentation.spec.credentials[0].revealedAttributes.proof,
).toBeDefined();
expect(
presentation.spec.credentials[0].revealedAttributes.proof,
).toHaveProperty(
'verificationMethod',
credential.proof.verificationMethod,
);
expect(
presentation.spec.credentials[0].revealedAttributes.proof,
).toHaveProperty('type', credential.proof.type);

// Setup predicate params with the verifying key for range proofs
const predicateParams = new Map();
predicateParams.set(provingKeyId, pk.getVerifyingKey());

// Verify the presentation, note that we must pass predicateParams with the verification key
const { verified } = await verifyPresentation(presentation, { resolver, predicateParams });
expect(verified).toEqual(true);
}, 60000);

test('expect to reveal specified attributes', async () => {
const presentationInstance = new Presentation();
const issuerKey = getKeyDoc(did1, keypair, keypair.type, keypair.id);
Expand Down

0 comments on commit 8c705cd

Please sign in to comment.