Skip to content

Commit

Permalink
Merge pull request #96 from 0xPolygonID/fix/selective-disclosure
Browse files Browse the repository at this point in the history
Fix selective disclosure for nested props
  • Loading branch information
vmidyllic authored Jul 12, 2023
2 parents 5ea8bf1 + 139ed85 commit cffb27f
Show file tree
Hide file tree
Showing 12 changed files with 150 additions and 53 deletions.
7 changes: 5 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@0xpolygonid/js-sdk",
"version": "1.0.0-beta.15",
"version": "1.0.0-beta.16",
"description": "SDK to work with Polygon ID",
"main": "dist/cjs/index.js",
"module": "dist/esm_esbuild/index.js",
Expand Down Expand Up @@ -103,6 +103,6 @@
]
},
"engines": {
"node": ">=18.16.1"
"node": ">=18.16.0"
}
}
2 changes: 1 addition & 1 deletion src/credentials/credential-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface CredentialRequest {
/**
* Credential subject, usually contains claims and identifier
*/
credentialSubject: { [key: string]: string | object | number };
credentialSubject: { [key: string]: string | object | number | boolean };
/**
* expiration time
*/
Expand Down
39 changes: 14 additions & 25 deletions src/storage/filters/jsonQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,32 +52,16 @@ const comparatorOptions: { [v in FilterOperatorMethod]: FilterOperatorFunction }
* @param {string} path - given path
* @param {*} [defaultValue=null]
*/
export const resolvePath = (object: object, path: string, defaultValue = null) =>
path.split('.').reduce((o, p) => (o ? o[p] : defaultValue), object);

/**
* filter creation factory
*
* @param {string} path - given query path
* @param {*} operatorFunc - filter operation
* @param {*} value - filter value
* @param {*} [isReverseParams=false] - reversed params
*/
export const createFilter = (path: string, operatorFunc, value, isReverseParams = false) => {
if (!operatorFunc) {
throw new Error(SearchError.NotDefinedComparator);
}
return (credential: W3CCredential): boolean => {
const credentialPathValue = resolvePath(credential, path);
if (!credentialPathValue) {
return false;
// throw new Error(`Not found path - ${path} to credential`);
export const resolvePath = (object: object, path: string, defaultValue = null) => {
const pathParts = path.split('.');
let o = object;
for (const part of pathParts) {
if (o === null || o === undefined) {
return defaultValue;
}
if (isReverseParams) {
return operatorFunc(value, credentialPathValue);
}
return operatorFunc(credentialPathValue, value);
};
o = o[part];
}
return o;
};

/**
Expand Down Expand Up @@ -154,6 +138,11 @@ export const StandardJSONCredentialsQueryFilter = (query: ProofQuery): FilterQue
case 'credentialSubject': {
const reqFilters = Object.keys(queryValue).reduce((acc: FilterQuery[], fieldKey) => {
const fieldParams = queryValue[fieldKey];
if (typeof fieldParams === 'object' && Object.keys(fieldParams).length === 0) {
return acc.concat([
new FilterQuery(`credentialSubject.${fieldKey}`, comparatorOptions.$noop, null)
]);
}
const res = Object.keys(fieldParams).map((comparator) => {
const value = fieldParams[comparator];
const path = `credentialSubject.${fieldKey}`;
Expand Down
2 changes: 1 addition & 1 deletion src/storage/indexed-db/merkletree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class MerkleTreeIndexedDBStorage implements IMerkleTreeStorage {
*/
constructor(private readonly _mtDepth: number) {
this._merkleTreeMetaStore = createStore(
`${ MerkleTreeIndexedDBStorage.storageKeyMeta}-db`,
`${MerkleTreeIndexedDBStorage.storageKeyMeta}-db`,
MerkleTreeIndexedDBStorage.storageKeyMeta
);
this._bindingStore = createStore(
Expand Down
2 changes: 1 addition & 1 deletion src/verifiable/credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class W3CCredential {
type: string[];
expirationDate?: string;
issuanceDate?: string;
credentialSubject: { [key: string]: object | string | number };
credentialSubject: { [key: string]: object | string | number | boolean };
credentialStatus: CredentialStatus;
issuer: string;
credentialSchema: CredentialSchema;
Expand Down
7 changes: 5 additions & 2 deletions src/verifiable/presentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const buildQueryPath = async (
export const createVerifiablePresentation = (
context: string,
tp: string,
field: string,
path: string,
value: unknown
): object => {
const baseContext = [VerifiableConstants.JSONLD_SCHEMA.W3C_CREDENTIAL_2018];
Expand All @@ -53,6 +53,9 @@ export const createVerifiablePresentation = (
vcTypes.push(tp);
}

const [first, ...rest] = path.split('.');
const obj = rest.reduceRight((acc, key) => ({ [key]: acc }), value);

return {
'@context': baseContext,
'@type': VerifiableConstants.CREDENTIAL_TYPE.W3C_VERIFIABLE_PRESENTATION,
Expand All @@ -61,7 +64,7 @@ export const createVerifiablePresentation = (
'@type': vcTypes,
credentialSubject: {
'@type': tp,
[field]: value
[first]: obj
}
}
};
Expand Down
18 changes: 18 additions & 0 deletions tests/credentials/credential-wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,24 @@ const credentialFlow = async (storage: IDataStorage) => {
}
},
expected: []
},
{
query: {
allowedIssuers: ['*'],
credentialSubject: {
countOfFines: {}
}
},
expected: [cred4]
},
{
query: {
allowedIssuers: ['*'],
credentialSubject: {
'country.name': { $eq: 'Spain' }
}
},
expected: [cred4]
}
];

Expand Down
6 changes: 5 additions & 1 deletion tests/credentials/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ export const cred4 = createTestCredential({
credentialStatus: {},
issuer: 'issuer4',
credentialSubject: {
countOfFines: 0
countOfFines: 0,
country: {
name: 'Spain',
code: 'ES'
}
},
expirationDate: '2023-11-11',
issuanceDate: '2022-11-11'
Expand Down
1 change: 0 additions & 1 deletion tests/handlers/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ describe('auth', () => {
const msgBytes = byteEncoder.encode(JSON.stringify(authReq));
const authRes = await authHandler.handleAuthorizationRequestForGenesisDID(userDID, msgBytes);


const tokenStr = authRes.token;
console.log(tokenStr);
expect(tokenStr).to.be.a('string');
Expand Down
9 changes: 5 additions & 4 deletions tests/proofs/common.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { expect } from 'chai';
import { CircuitId, ZeroKnowledgeProofRequest } from '../../src';
import { CircuitId, IProofService, W3CCredential, ZeroKnowledgeProofRequest } from '../../src';
import { DID } from '@iden3/js-iden3-core';

export async function checkVerifiablePresentation(
type: string,
userDID,
cred,
proofService,
userDID: DID,
cred: W3CCredential,
proofService: IProofService,
circuitId: CircuitId
) {
const vpProofReq: ZeroKnowledgeProofRequest = {
Expand Down
106 changes: 93 additions & 13 deletions tests/proofs/sig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ describe('sig proofs', () => {
let dataStorage: IDataStorage;
let proofService: ProofService;
const rhsUrl = process.env.RHS_URL as string;
const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed');
const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser');

const mockStateStorage: IStateStorage = {
getLatestStateById: async () => {
Expand Down Expand Up @@ -124,9 +126,6 @@ describe('sig proofs', () => {
});

it('sigv2-non-merklized', async () => {
const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed');
const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser');

const { did: userDID, credential: cred } = await idWallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
Expand Down Expand Up @@ -205,9 +204,6 @@ describe('sig proofs', () => {
});

it('sigv2-merklized', async () => {
const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed');
const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser');

const { did: userDID } = await idWallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
Expand Down Expand Up @@ -286,9 +282,6 @@ describe('sig proofs', () => {
});

it('sigv2-merklized-query-array', async () => {
const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed');
const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser');

const { did: userDID } = await idWallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
Expand Down Expand Up @@ -366,7 +359,6 @@ describe('sig proofs', () => {
);
});


it('sigv2-ipfs-string-eq', async () => {
const req = {
id: '0d8e91e5-5686-49b5-85e3-2b35538c6a03',
Expand Down Expand Up @@ -396,9 +388,6 @@ describe('sig proofs', () => {
from: 'did:polygonid:polygon:mumbai:2qLPqvayNQz9TA2r5VPxUugoF18teGU583zJ859wfy'
};

const seedPhraseIssuer: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseedseed');
const seedPhrase: Uint8Array = byteEncoder.encode('seedseedseedseedseedseedseeduser');

const { did: userDID } = await idWallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
Expand Down Expand Up @@ -454,4 +443,95 @@ describe('sig proofs', () => {

expect(vp).to.be.undefined;
});

it('sigv2 vp-credential', async () => {
const query = {
allowedIssuers: ['*'],
context: 'ipfs://QmQXQ5gBNfJuc9QXy5pGbaVfLxzFjCDAvPs4Fa43BaU1U4',
credentialSubject: {
'postalProviderInformation.name': {}
},
type: 'DeliveryAddress'
};

const { did: issuerDID } = await idWallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
networkId: NetworkId.Mumbai,
seed: seedPhraseIssuer,
revocationOpts: {
type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
id: rhsUrl
}
});

const { did: userDID } = await idWallet.createIdentity({
method: DidMethod.Iden3,
blockchain: Blockchain.Polygon,
networkId: NetworkId.Mumbai,
seed: seedPhrase,
revocationOpts: {
type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
id: rhsUrl
}
});

const claimReq: CredentialRequest = {
credentialSchema: 'ipfs://QmbLQKw9Mzc9fVHowatJbvZjWNSUZchxYQX5Wtt8Ff9rGx',
type: 'DeliveryAddress',
credentialSubject: {
id: userDID.toString(),
price: 10,
deliveryTime: '2023-07-11T16:05:51.140Z',
postalProviderInformation: {
name: 'ukr posta',
officeNo: 1
},
homeAddress: {
line2: 'line 2',
line1: 'line 1'
},
isPostalProvider: true
},
expiration: 1693526400,
revocationOpts: {
type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
id: rhsUrl
}
};
const issuerCred = await idWallet.issueCredential(issuerDID, claimReq, {
ipfsGatewayURL: 'https://ipfs.io'
});

await credWallet.save(issuerCred);

const creds = await credWallet.findByQuery(query);
expect(creds.length).to.not.equal(0);

const credsForMyUserDID = await credWallet.filterByCredentialSubject(creds, userDID);
expect(credsForMyUserDID.length).to.equal(1);
const vpReq = {
id: 1,
circuitId: 'credentialAtomicQuerySigV2',
query
};
const { proof, vp } = await proofService.generateProof(vpReq, userDID, credsForMyUserDID[0]);
expect(proof).not.to.be.undefined;

expect(vp).to.deep.equal({
'@context': ['https://www.w3.org/2018/credentials/v1'],
'@type': 'VerifiablePresentation',
verifiableCredential: {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'ipfs://QmQXQ5gBNfJuc9QXy5pGbaVfLxzFjCDAvPs4Fa43BaU1U4'
],
'@type': ['VerifiableCredential', 'DeliveryAddress'],
credentialSubject: {
'@type': 'DeliveryAddress',
postalProviderInformation: { name: 'ukr posta' }
}
}
});
});
});

0 comments on commit cffb27f

Please sign in to comment.