From b8194f90e534fcff25fc35956df424aa464c9df6 Mon Sep 17 00:00:00 2001 From: Samuel Hellawell Date: Tue, 4 Apr 2023 04:14:21 +0100 Subject: [PATCH] Fix empty objects in credential throws error Signed-off-by: Samuel Hellawell --- package.json | 2 +- src/anonymous-credentials/schema.ts | 30 ++++++++++++------- src/anonymous-credentials/util.ts | 4 ++- src/util.ts | 7 ++++- .../anonymous-credentials/credential.spec.ts | 13 ++++++++ 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index d21bcbfa..774ac106 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@docknetwork/crypto-wasm-ts", - "version": "0.31.2", + "version": "0.31.3", "description": "Typescript abstractions over Dock's Rust crypto library's WASM wrapper", "homepage": "https://github.com/docknetwork/crypto-wasm-ts", "main": "lib/index.js", diff --git a/src/anonymous-credentials/schema.ts b/src/anonymous-credentials/schema.ts index cdbebe5f..7b675831 100644 --- a/src/anonymous-credentials/schema.ts +++ b/src/anonymous-credentials/schema.ts @@ -544,6 +544,7 @@ export class CredentialSchema extends Versioned { static validateGeneric(schema: object, ignoreKeys: Set = new Set()) { const [names, values] = this.flattenSchemaObj(schema); + for (let i = 0; i < names.length; i++) { if (ignoreKeys.has(names[i])) { continue; @@ -644,16 +645,16 @@ export class CredentialSchema extends Versioned { properties: { id: { type: 'string' - }, - }, + } + } }, proof: { type: 'object', properties: { type: { type: 'string' - }, - }, + } + } } } }; @@ -1101,13 +1102,20 @@ export class CredentialSchema extends Versioned { } else if (schemaProps[key]['type'] == 'object' && typ == 'object') { const schemaKeys = new Set([...Object.keys(schemaProps[key]['properties'])]); const valKeys = new Set([...Object.keys(value)]); - for (const vk of valKeys) { - CredentialSchema.generateFromCredential(value, schemaProps[key]['properties']); - } - // Delete extra keys not in cred - for (const sk of schemaKeys) { - if (value[sk] === undefined) { - delete schemaKeys[sk]; + + // If empty object, skip it here otherwise causes problems downstream + if (schemaKeys.size === 0) { + delete schemaProps[key]; + } else { + for (const vk of valKeys) { + // TODO: why this loop? seems useless? + CredentialSchema.generateFromCredential(value, schemaProps[key]['properties']); + } + // Delete extra keys not in cred + for (const sk of schemaKeys) { + if (value[sk] === undefined) { + delete schemaKeys[sk]; + } } } } else { diff --git a/src/anonymous-credentials/util.ts b/src/anonymous-credentials/util.ts index a4ae2b27..1d3c1e48 100644 --- a/src/anonymous-credentials/util.ts +++ b/src/anonymous-credentials/util.ts @@ -32,6 +32,7 @@ import { ValueType, ValueTypes } from './schema'; import { Encoder } from '../bbs-plus'; import { SetupParam, Statement, WitnessEqualityMetaStatement } from '../composite-proof'; import { SetupParamsTracker } from './setup-params-tracker'; +import { isEmptyObject } from '../util'; export function dockAccumulatorParams(): AccumulatorParams { return Accumulator.generateParams(ACCUMULATOR_PARAMS_LABEL_BYTES); @@ -56,7 +57,8 @@ export function dockSaverEncryptionGensUncompressed(): SaverEncryptionGensUncomp export function flattenTill2ndLastKey(obj: object): [string[], object[]] { const flattened = {}; const temp = flatten(obj) as object; - for (const k of Object.keys(temp)) { + const tempKeys = Object.keys(temp).filter((key) => typeof temp[key] !== 'object' || !isEmptyObject(temp[key])); + for (const k of tempKeys) { // taken from https://stackoverflow.com/a/5555607 const pos = k.lastIndexOf('.'); const name = k.substring(0, pos); diff --git a/src/util.ts b/src/util.ts index 1a5d6c3f..474d2f5a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -69,7 +69,12 @@ export function randomFieldElement(seed?: Uint8Array): Uint8Array { */ export function flattenObjectToKeyValuesList(obj: object, flattenOptions = undefined): [string[], unknown[]] { const flattened = flatten(obj, flattenOptions) as object; - const keys = Object.keys(flattened).sort(); + + // Sort and filter keys to remove empty objects + // this is done because schema generation removes empty objects + nothing to encode + const keys = Object.keys(flattened) + .sort() + .filter((k) => typeof flattened[k] !== 'object' || !isEmptyObject(flattened[k])); // @ts-ignore const values = keys.map((k) => flattened[k]); return [keys, values]; diff --git a/tests/anonymous-credentials/credential.spec.ts b/tests/anonymous-credentials/credential.spec.ts index 5ee25394..4e763f79 100644 --- a/tests/anonymous-credentials/credential.spec.ts +++ b/tests/anonymous-credentials/credential.spec.ts @@ -871,4 +871,17 @@ describe('Credential signing and verification', () => { checkResult(recreatedCred.verify(pk)); } }); + + it('for credential with auto-generated schema and empty objects', () => { + const builder = new CredentialBuilder(); + builder.schema = new CredentialSchema(CredentialSchema.essential()); + builder.subject = { + fname: 'John', + emptyObject: {}, + lname: 'Smith', + }; + + const cred = builder.sign(sk, undefined, {requireSameFieldsAsSchema: false}); + checkResult(cred.verify(pk)); + }); });