diff --git a/package.json b/package.json index 158d128f..d625f2c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@docknetwork/crypto-wasm-ts", - "version": "0.49.0", + "version": "0.50.0", "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/blinded-credential-request-builder.ts b/src/anonymous-credentials/blinded-credential-request-builder.ts index 4644ec39..c53544ca 100644 --- a/src/anonymous-credentials/blinded-credential-request-builder.ts +++ b/src/anonymous-credentials/blinded-credential-request-builder.ts @@ -11,13 +11,13 @@ import { BBS_PLUS_SIGNATURE_PARAMS_LABEL_BYTES, BBS_SIGNATURE_PARAMS_LABEL_BYTES, BlindedAttributeEquality, - BlindSignatureTypes, + BlindSignatureType, BoundCheckParamType, BoundType, PublicKey, SignatureParams, SUBJECT_STR, - VerifiableEncryptionProtocols + VerifiableEncryptionProtocol } from './types-and-consts'; import { AccumulatorPublicKey, AccumulatorWitness } from '../accumulator'; import { LegoProvingKey, LegoProvingKeyUncompressed } from '../legosnark'; @@ -36,7 +36,7 @@ import { BBSSignatureParams } from '../bbs'; import { BBSPlusSignatureParamsG1 } from '../bbs-plus'; import { BytearrayWrapper } from '../bytearray-wrapper'; import { - IPresentedAttributeBounds, + IPresentedAttributeBound, IPresentedAttributeInequality, IPresentedAttributeVE } from './presentation-specification'; @@ -72,7 +72,7 @@ export abstract class BlindedCredentialRequestBuilder extends Version attributeInequalities: Map; // Bounds on blinded attributes - bounds: Map; + bounds: Map; // Encryption of blinded attributes verifEnc: Map; @@ -127,7 +127,7 @@ export abstract class BlindedCredentialRequestBuilder extends Version abstract getBlinding(): Uint8Array | undefined; - static getSigType(): BlindSignatureTypes { + static getSigType(): BlindSignatureType { throw new Error('This method should be implemented by extending class'); } @@ -222,6 +222,28 @@ export abstract class BlindedCredentialRequestBuilder extends Version ); } + enforceCircomPredicateAcrossMultipleCredentials( + // For each circuit private variable name, give its corresponding credential index and attribute name + circuitPrivateVars: [string, [number, string] | [number, string][]][], + // For each circuit public variable name, give its corresponding values + circuitPublicVars: [string, Uint8Array | Uint8Array[]][], + circuitId: string, + provingKeyId: string, + r1cs?: R1CS | ParsedR1CSFile, + wasmBytes?: Uint8Array, + provingKey?: LegoProvingKey | LegoProvingKeyUncompressed + ) { + this.presentationBuilder.enforceCircomPredicateAcrossMultipleCredentials( + circuitPrivateVars, + circuitPublicVars, + circuitId, + provingKeyId, + r1cs, + wasmBytes, + provingKey + ); + } + addUnboundedPseudonym(baseForSecretKey: Uint8Array, secretKey: Uint8Array): number { return this.presentationBuilder.addUnboundedPseudonym(baseForSecretKey, secretKey); } @@ -440,8 +462,8 @@ export class BBSBlindedCredentialRequestBuilder extends BlindedCredentialRequest return undefined; } - static getSigType(): BlindSignatureTypes { - return BlindSignatureTypes.Bbs; + static getSigType(): BlindSignatureType { + return BlindSignatureType.Bbs; } } @@ -482,8 +504,8 @@ export class BBSPlusBlindedCredentialRequestBuilder extends BlindedCredentialReq return commitment; } - static getSigType(): BlindSignatureTypes { - return BlindSignatureTypes.BbsPlus; + static getSigType(): BlindSignatureType { + return BlindSignatureType.BbsPlus; } } diff --git a/src/anonymous-credentials/credential-builder-common.ts b/src/anonymous-credentials/credential-builder-common.ts index df03a337..3049474d 100644 --- a/src/anonymous-credentials/credential-builder-common.ts +++ b/src/anonymous-credentials/credential-builder-common.ts @@ -5,7 +5,7 @@ import { ID_STR, MEM_CHECK_STR, NON_MEM_CHECK_STR, - RevocationStatusProtocols, + RevocationStatusProtocol, REV_CHECK_STR, REV_ID_STR, SCHEMA_STR, @@ -64,7 +64,7 @@ export abstract class CredentialBuilderCommon extends Versioned { throw new Error(`Revocation check should be either ${MEM_CHECK_STR} or ${NON_MEM_CHECK_STR} but was ${revCheck}`); } this._credStatus = { - [TYPE_STR]: RevocationStatusProtocols.Vb22, + [TYPE_STR]: RevocationStatusProtocol.Vb22, [ID_STR]: registryId, [REV_CHECK_STR]: revCheck, [REV_ID_STR]: memberValue diff --git a/src/anonymous-credentials/credential.ts b/src/anonymous-credentials/credential.ts index d0c16875..40646c32 100644 --- a/src/anonymous-credentials/credential.ts +++ b/src/anonymous-credentials/credential.ts @@ -5,7 +5,7 @@ import { BBS_PLUS_SIGNATURE_PARAMS_LABEL_BYTES, BBS_SIGNATURE_PARAMS_LABEL_BYTES, CRYPTO_VERSION_STR, - SignatureTypes, + SignatureType, PROOF_STR, PS_CRED_PROOF_TYPE, PS_SIGNATURE_PARAMS_LABEL_BYTES, @@ -71,7 +71,7 @@ export abstract class Credential extends } } - static getSigType(): SignatureTypes { + static getSigType(): SignatureType { throw new Error('This method should be implemented by extending class'); } } @@ -116,8 +116,8 @@ export class BBSCredential extends Credential>; + bounds: Map>; // Verifiable encryption of attributes. The key of the map is the credential index and for the inner map is the attribute and value of map // denotes the setup parameters for the protocol and the protocol name. An attribute can have many verifiable encryptions. verifEnc: Map>; - // Predicates expressed as Circom programs. For each credential, store a public, private variables, circuit id (used to fetch R1CS, WASM bytes) and attributes used in circuit + // Predicates expressed as Circom programs over attributes of a single credential. For each credential, store a public, private variables, circuit id (used to fetch R1CS, WASM bytes) and attributes used in circuit circomPredicates: Map; + // Predicates expressed as Circom programs over attributes of multiple credentials. + circomPredicatesMultiCred: IProverCircomPredicateMultiCred[]; + // Parameters for predicates like snark proving key for bound check, verifiable encryption, Circom program predicateParams: Map; @@ -171,7 +174,7 @@ export class PresentationBuilder extends Versioned { blinding?: Uint8Array; // The 2nd item, i.e. Uint8Array in the pair is the encoded value of the public value with which inequality is proved attributeInequalities: Map; - bounds: Map; + bounds: Map; verifEnc: Map; circPred: IProverCircomPredicate[]; pseudonyms: IProverBoundedPseudonymInBlindedCredReq[]; @@ -190,10 +193,17 @@ export class PresentationBuilder extends Versioned { this.verifEnc = new Map(); this.predicateParams = new Map(); this.circomPredicates = new Map(); + this.circomPredicatesMultiCred = []; this.spec = new PresentationSpecification(); } + /** + * Add a credential to this presentation. This will result in a proof of possession of this credential being created + * @param credential + * @param pk + */ addCredential(credential: Credential, pk: PublicKey): number { + // TODO: Accept reference to public keys in case of same key for many credentials this.credentials.push([credential, pk]); return this.credentials.length - 1; } @@ -345,6 +355,18 @@ export class PresentationBuilder extends Versioned { this.verifEnc.set(credIdx, v); } + /** + * Enforce a predicate written as a Circom program over a credential's attributes + * @param credIdx - The credential index whose attributes are used as witness in the Circom program + * @param circuitPrivateVars - Mapping of private variables from Circom program to attribute names. A variable can be a single value + * or an array and thus can correspond to a single attribute or array of attributes + * @param circuitPublicVars + * @param circuitId + * @param provingKeyId + * @param r1cs + * @param wasmBytes + * @param provingKey + */ enforceCircomPredicate( credIdx: number, // For each circuit private variable name, give its corresponding attribute names @@ -375,6 +397,40 @@ export class PresentationBuilder extends Versioned { this.circomPredicates.set(credIdx, predicates); } + /** + * Enforce a predicate written as a Circom program over a many credentials' attributes + * @param circuitPrivateVars - Mapping of private variables from Circom program to pairs where each pair corresponds to a credential attribute. + * The 1st item of the pair is the credential index and 2nd item is the attribute name in that credential + * @param circuitPublicVars + * @param circuitId + * @param provingKeyId + * @param r1cs + * @param wasmBytes + * @param provingKey + */ + enforceCircomPredicateAcrossMultipleCredentials( + // For each circuit private variable name, give its corresponding credential index and attribute name + circuitPrivateVars: [string, [number, string] | [number, string][]][], + // For each circuit public variable name, give its corresponding values + circuitPublicVars: [string, Uint8Array | Uint8Array[]][], + circuitId: string, + provingKeyId: string, + r1cs?: R1CS | ParsedR1CSFile, + wasmBytes?: Uint8Array, + provingKey?: LegoProvingKey | LegoProvingKeyUncompressed + ) { + if (circuitPrivateVars.length === 0) { + throw new Error('Provide at least one private variable mapping'); + } + this.updatePredicateParams(provingKeyId, provingKey); + this.updatePredicateParams( + PresentationBuilder.r1csParamId(circuitId), + r1cs !== undefined ? getR1CS(r1cs) : undefined + ); + this.updatePredicateParams(PresentationBuilder.wasmParamId(circuitId), wasmBytes); + this.circomPredicatesMultiCred.push({ privateVars: circuitPrivateVars, publicVars: circuitPublicVars, circuitId, provingKeyId }) + } + addBoundedPseudonym( basesForAttribute: Uint8Array[], // Attributes from each credential: keys are credential indexes, values are attribute names @@ -441,13 +497,13 @@ export class PresentationBuilder extends Versioned { // Create statements and witnesses for proving possession of each credential, i.e. proof of knowledge of the sigs. // Also collect encoded attributes used in any predicate - for (let i = 0; i < numCreds; i++) { - const cred = this.credentials[i][0]; + for (let credIndex = 0; credIndex < numCreds; credIndex++) { + const cred = this.credentials[credIndex][0]; const schema = cred.schema; const flattenedSchema = schema.flatten(); const numAttribs = flattenedSchema[0].length; - let revealedNames = this.revealedAttributes.get(i); + let revealedNames = this.revealedAttributes.get(credIndex); if (revealedNames === undefined) { revealedNames = new Set(); } @@ -467,7 +523,7 @@ export class PresentationBuilder extends Versioned { (cred.credentialStatus[REV_CHECK_STR] !== MEM_CHECK_STR && cred.credentialStatus[REV_CHECK_STR] !== NON_MEM_CHECK_STR) ) { - throw new Error(`Credential for ${i} has invalid status ${cred.credentialStatus}`); + throw new Error(`Credential for ${credIndex} has invalid status ${cred.credentialStatus}`); } revealedNames.add(`${STATUS_STR}.${ID_STR}`); revealedNames.add(`${STATUS_STR}.${REV_CHECK_STR}`); @@ -481,7 +537,7 @@ export class PresentationBuilder extends Versioned { const statement = buildSignatureStatementFromParamsRef( setupParamsTrk, sigParams, - this.credentials[i][1], + this.credentials[credIndex][1], numAttribs, revealedAttrsEncoded ); @@ -491,19 +547,19 @@ export class PresentationBuilder extends Versioned { let presentedStatus: IPresentedStatus | undefined; if (cred.credentialStatus !== undefined) { - const s = this.credStatuses.get(i); + const s = this.credStatuses.get(credIndex); if (s === undefined) { - throw new Error(`No status details found for credential index ${i}`); + throw new Error(`No status details found for credential index ${credIndex}`); } presentedStatus = { [ID_STR]: cred.credentialStatus[ID_STR], - [TYPE_STR]: RevocationStatusProtocols.Vb22, + [TYPE_STR]: RevocationStatusProtocol.Vb22, [REV_CHECK_STR]: cred.credentialStatus[REV_CHECK_STR], accumulated: s[1], extra: s[3] }; credStatusAux.push([ - i, + credIndex, cred.credentialStatus[REV_CHECK_STR], schema.encoder.encodeMessage(`${STATUS_STR}.${REV_ID_STR}`, cred.credentialStatus[REV_ID_STR]) ]); @@ -523,11 +579,11 @@ export class PresentationBuilder extends Versioned { } // Get encoded attributes which are used in inequality check - const ineqs = this.attributeInequalities.get(i); + const ineqs = this.attributeInequalities.get(credIndex); let attributeIneqs: { [key: string]: string | IPresentedAttributeInequality[] } | undefined; if (ineqs !== undefined && ineqs.size > 0) { attributeIneqs = {}; - const encodedAttrs = unrevealedMsgsEncoded.get(i) || new Map(); + const encodedAttrs = unrevealedMsgsEncoded.get(credIndex) || new Map(); for (const [name, ineq] of ineqs.entries()) { attributeIneqs[name] = []; ineq.forEach((ineq_j) => { @@ -539,79 +595,98 @@ export class PresentationBuilder extends Versioned { updateEncodedAttrs(name, encodedAttrs); } attributeIneqs = unflatten(attributeIneqs); - unrevealedMsgsEncoded.set(i, encodedAttrs); + unrevealedMsgsEncoded.set(credIndex, encodedAttrs); } // Get encoded attributes which are used in bound check - const bounds = this.bounds.get(i); - let attributeBounds: { [key: string]: string | IPresentedAttributeBounds[] } | undefined; + const bounds = this.bounds.get(credIndex); + let attributeBounds: { [key: string]: string | IPresentedAttributeBound[] } | undefined; if (bounds !== undefined && bounds.size > 0) { attributeBounds = {}; - const encodedAttrs = unrevealedMsgsEncoded.get(i) || new Map(); + const encodedAttrs = unrevealedMsgsEncoded.get(credIndex) || new Map(); for (const [name, b] of bounds.entries()) { attributeBounds[name] = b; updateEncodedAttrs(name, encodedAttrs); } attributeBounds = unflatten(attributeBounds); - unrevealedMsgsEncoded.set(i, encodedAttrs); + unrevealedMsgsEncoded.set(credIndex, encodedAttrs); } // Get encoded attributes which are used in verifiable encryption let attributeEncs: { [key: string]: string | IPresentedAttributeVE[] } | undefined; - const encs = this.verifEnc.get(i); + const encs = this.verifEnc.get(credIndex); if (encs !== undefined && encs.size > 0) { attributeEncs = {}; - const encodedAttrs = unrevealedMsgsEncoded.get(i) || new Map(); + const encodedAttrs = unrevealedMsgsEncoded.get(credIndex) || new Map(); for (const [name, ve] of encs.entries()) { const valTyp = schema.typeOfName(name, flattenedSchema); if (valTyp.type !== ValueType.RevStr) { throw new Error( - `Attribute name ${name} of credential index ${i} should be a reversible string type but was ${valTyp}` + `Attribute name ${name} of credential index ${credIndex} should be a reversible string type but was ${valTyp}` ); } attributeEncs[name] = ve; updateEncodedAttrs(name, encodedAttrs); } attributeEncs = unflatten(attributeEncs); - unrevealedMsgsEncoded.set(i, encodedAttrs); + unrevealedMsgsEncoded.set(credIndex, encodedAttrs); } // Get encoded attributes used in predicates expressed as Circom programs - const predicates = this.circomPredicates.get(i); + const predicates = this.circomPredicates.get(credIndex); const [encodedAttrs, predicatesForSpec] = this.encodeCircomAttrsAndFormatPredicatesForSpec( predicates, () => { - return unrevealedMsgsEncoded.get(i) || new Map(); + return unrevealedMsgsEncoded.get(credIndex) || new Map(); }, (a: string, m: Map) => { return updateEncodedAttrs(a, m); } ); if (encodedAttrs !== undefined) { - unrevealedMsgsEncoded.set(i, encodedAttrs); + unrevealedMsgsEncoded.set(credIndex, encodedAttrs); } + const encodedAttrsMultiCred: Map = unrevealedMsgsEncoded.get(credIndex) || new Map(); + this.circomPredicatesMultiCred.forEach((pred) => { + pred.privateVars.forEach(([name, val]) => { + if (Array.isArray(val)) { + // @ts-ignore + val.forEach(([i, s]) => { + if (i == credIndex) { + updateEncodedAttrs(s, encodedAttrsMultiCred); + } + }) + } else { + if (val[0] == credIndex) { + updateEncodedAttrs(val[1], encodedAttrsMultiCred); + } + } + }) + }); + unrevealedMsgsEncoded.set(credIndex, encodedAttrsMultiCred); + function updateUnrevealedMsgsEncoded(attributeNames?: string[]) { if (attributeNames !== undefined) { // this bounded pseudonym does not use any attributes from credential indexed `i` - const encodedAttrs = unrevealedMsgsEncoded.get(i) || new Map(); + const encodedAttrs = unrevealedMsgsEncoded.get(credIndex) || new Map(); for (const attributeName of attributeNames) { updateEncodedAttrs(attributeName, encodedAttrs); } - unrevealedMsgsEncoded.set(i, encodedAttrs); + unrevealedMsgsEncoded.set(credIndex, encodedAttrs); } } // Get encoded attributes which are used in bounded pseudonyms for (let j = 0; j < this.boundedPseudonyms.length; j++) { - const attributeNames = this.boundedPseudonyms[j].attributeNames.get(i); + const attributeNames = this.boundedPseudonyms[j].attributeNames.get(credIndex); updateUnrevealedMsgsEncoded(attributeNames); } // Get encoded attributes which are used in bounded pseudonyms for the blinded credential request if (this.blindCredReq !== undefined && this.blindCredReq.pseudonyms.length > 0) { for (let j = 0; j < this.blindCredReq.pseudonyms.length; j++) { - const attributeNames = this.blindCredReq.pseudonyms[j].credentialAttributes.get(i); + const attributeNames = this.blindCredReq.pseudonyms[j].credentialAttributes.get(credIndex); updateUnrevealedMsgsEncoded(attributeNames); } } @@ -847,6 +922,75 @@ export class PresentationBuilder extends Versioned { ); } + let circomPredMultiCred: ICircomPredicate[] | undefined; + if (this.circomPredicatesMultiCred.length > 0) { + circomPredMultiCred = []; + this.circomPredicatesMultiCred.forEach(({ privateVars, publicVars, circuitId, provingKeyId: snarkKeyId }) => { + const privateVarsForSpec: ICircuitPrivateVarMultiCred[] = []; + const statement = this.createCircomStatement(circuitId, snarkKeyId, setupParamsTrk); + const sIdx = statements.add(statement); + + function addWitnessEqualityAndReturnEncodedAttr(cId: number, name: string): Uint8Array { + const nameIdx = flattenedSchemas[cId][0].indexOf(name); + const witnessEq = new WitnessEqualityMetaStatement(); + witnessEq.addWitnessRef(cId, nameIdx); + witnessEq.addWitnessRef(sIdx, predicateWitnessIdx++); + metaStatements.addWitnessEquality(witnessEq); + return unrevealedMsgsEncoded.get(cId)?.get(nameIdx) as Uint8Array; + } + + let predicateWitnessIdx = 0; + const circuitInputs = new CircomInputs(); + // For each private input, set its value as the corresponding attribute and set the witness equality + privateVars.forEach(([varName, attrRef]) => { + if (Array.isArray(attrRef)) { + circuitInputs.setPrivateArrayInput( + varName, + attrRef.map((n) => { + return addWitnessEqualityAndReturnEncodedAttr(n[0], n[1]); + }) + ); + privateVarsForSpec.push({ + varName, + // @ts-ignore + attributeRef: attrRef.map(([i, n]) => [i, unflatten({ [n]: null })]) + }); + } else { + circuitInputs.setPrivateInput(varName, addWitnessEqualityAndReturnEncodedAttr(attrRef[0], attrRef[1])); + privateVarsForSpec.push({ + varName, + // @ts-ignore + attributeRef: [attrRef[0], unflatten({ [attrRef[1]]: null })] + }); + } + }); + + publicVars.forEach(([varName, value]) => { + if (Array.isArray(value)) { + circuitInputs.setPublicArrayInput(varName, value); + } else { + circuitInputs.setPublicInput(varName, value); + } + }); + witnesses.add(Witness.r1csCircomWitness(circuitInputs)); + + // @ts-ignore + circomPredMultiCred.push({ + privateVars: privateVarsForSpec, + publicVars: publicVars.map(([n, v]) => { + return { + varName: n, + value: v + }; + }), + circuitId, + snarkKeyId, + protocol: CircomProtocol.Legogroth16 + }); + }); + this.spec.circomPredicatesMultiCred = circomPredMultiCred; + } + // For blinded credential request let blindAttrToSId = new Map(); if (this.blindCredReq !== undefined) { @@ -1118,7 +1262,7 @@ export class PresentationBuilder extends Versioned { paramId: string, param: PredicateParamType | undefined, setupParamsTrk: SetupParamsTracker, - statementIdx: number + statementIdx?: number ) { if (param instanceof LegoProvingKey) { if (!setupParamsTrk.isTrackingParam(paramId)) { @@ -1129,9 +1273,13 @@ export class PresentationBuilder extends Versioned { setupParamsTrk.addForParamId(paramId, SetupParam.legosnarkProvingKeyUncompressed(param)); } } else { - throw new Error( - `Predicate param id ${paramId} (for statement index ${statementIdx}) was expected to be a Legosnark proving key but was ${param}` - ); + let errorMsg: string; + if (statementIdx !== undefined) { + errorMsg = `Predicate param id ${paramId} (for statement index ${statementIdx}) was expected to be a Legosnark proving key but was ${param}`; + } else { + errorMsg = `Predicate param id ${paramId} was expected to be a Legosnark proving key but was ${param}`; + } + throw new Error(errorMsg); } } @@ -1197,7 +1345,7 @@ export class PresentationBuilder extends Versioned { private processBoundChecks( credIdx: number, witnessIndexGetter: (string) => number, - bounds: Map, + bounds: Map, flattenedSchema: FlattenedSchema, encodedAttrGetter: (number) => Uint8Array, statements: Statements, @@ -1205,7 +1353,7 @@ export class PresentationBuilder extends Versioned { metaStatements: MetaStatements, setupParamsTrk: SetupParamsTracker ) { - const dataSortedByNameIdx: [number, string, IPresentedAttributeBounds[]][] = []; + const dataSortedByNameIdx: [number, string, IPresentedAttributeBound[]][] = []; for (const [name, b] of bounds.entries()) { const nameIdx = witnessIndexGetter(name); dataSortedByNameIdx.push([nameIdx, name, b]); @@ -1222,7 +1370,7 @@ export class PresentationBuilder extends Versioned { const encodedAttrVal = encodedAttrGetter(nameIdx); if (paramId === undefined) { // paramId is undefined means no setup param was passed and thus the default setup of Bulletproofs++ can be used. - if (protocol !== BoundCheckProtocols.Bpp) { + if (protocol !== BoundCheckProtocol.Bpp) { throw new Error( `paramId was undefined but protocol was not Bulletproofs++ but ${protocol}. This shouldn't have happened and is a bug in the code.` ); @@ -1241,7 +1389,7 @@ export class PresentationBuilder extends Versioned { const param = this.predicateParams.get(paramId); switch (protocol) { - case BoundCheckProtocols.Legogroth16: + case BoundCheckProtocol.Legogroth16: PresentationBuilder.addLegoProvingKeyToTracker(paramId, param, setupParamsTrk, credIdx); statement = Statement.boundCheckLegoProverFromSetupParamRefs( transformedMin, @@ -1250,7 +1398,7 @@ export class PresentationBuilder extends Versioned { ); witness = Witness.boundCheckLegoGroth16(encodedAttrVal); break; - case BoundCheckProtocols.Bpp: + case BoundCheckProtocol.Bpp: Presentation.addBppSetupParamsToTracker(paramId, param, setupParamsTrk, credIdx); statement = Statement.boundCheckBppFromSetupParamRefs( transformedMin, @@ -1259,7 +1407,7 @@ export class PresentationBuilder extends Versioned { ); witness = Witness.boundCheckBpp(encodedAttrVal); break; - case BoundCheckProtocols.Smc: + case BoundCheckProtocol.Smc: Presentation.addSmcSetupParamsToTracker(paramId, param, setupParamsTrk, credIdx); statement = Statement.boundCheckSmcFromSetupParamRefs( transformedMin, @@ -1268,7 +1416,7 @@ export class PresentationBuilder extends Versioned { ); witness = Witness.boundCheckSmc(encodedAttrVal); break; - case BoundCheckProtocols.SmcKV: + case BoundCheckProtocol.SmcKV: PresentationBuilder.addSmcKVProverParamsToTracker(paramId, param, setupParamsTrk, credIdx); statement = Statement.boundCheckSmcWithKVProverFromSetupParamRefs( transformedMin, @@ -1369,27 +1517,7 @@ export class PresentationBuilder extends Versioned { setupParamsTrk: SetupParamsTracker ) { predicates.forEach(({ privateVars, publicVars, circuitId, provingKeyId: snarkKeyId }) => { - const snarkKey = this.predicateParams.get(snarkKeyId); - const r1csId = PresentationBuilder.r1csParamId(circuitId); - const r1cs = this.predicateParams.get(r1csId); - const wasmId = PresentationBuilder.wasmParamId(circuitId); - const wasm = this.predicateParams.get(wasmId); - PresentationBuilder.addLegoProvingKeyToTracker(snarkKeyId, snarkKey, setupParamsTrk, statementIdx); - if (r1cs === undefined || wasm === undefined) { - throw new Error('Both WASM and R1CS should be present'); - } - if (!setupParamsTrk.isTrackingParam(r1csId)) { - setupParamsTrk.addForParamId(r1csId, SetupParam.r1cs(r1cs as R1CS)); - } - if (!setupParamsTrk.isTrackingParam(wasmId)) { - setupParamsTrk.addForParamId(wasmId, SetupParam.bytes(wasm as Uint8Array)); - } - - const statement = Statement.r1csCircomProverFromSetupParamRefs( - setupParamsTrk.indexForParam(r1csId), - setupParamsTrk.indexForParam(wasmId), - setupParamsTrk.indexForParam(snarkKeyId) - ); + const statement = this.createCircomStatement(circuitId, snarkKeyId, setupParamsTrk, statementIdx); const sIdx = statements.add(statement); function addWitnessEqualityAndReturnEncodedAttr(name: string): Uint8Array { @@ -1458,12 +1586,12 @@ export class PresentationBuilder extends Versioned { return m; } - private encodeCircomAttrsAndFormatPredicatesForSpec( + private encodeCircomAttrsAndFormatPredicatesForSpec( predicates?: IProverCircomPredicate[], encodedAttrsGetter?: () => Map, encodedAttrsUpdater?: (string, Map) => void - ): [Map | undefined, ICircomPredicate[] | undefined] { - let predicatesForSpec: ICircomPredicate[] | undefined; + ): [Map | undefined, ICircomPredicate[] | undefined] { + let predicatesForSpec: ICircomPredicate[] | undefined; let encodedAttrs: Map | undefined; if (predicates !== undefined && predicates.length > 0) { predicatesForSpec = []; @@ -1472,9 +1600,10 @@ export class PresentationBuilder extends Versioned { } predicates.forEach((predicate) => { const privateVars = predicate.privateVars; - const privateVarsForSpec: ICircuitPrivateVars[] = []; + const privateVarsForSpec: ICircuitPrivateVar[] = []; privateVars.forEach(([varName, attrName]) => { if (Array.isArray(attrName)) { + // The circuit variable is an array so will correspond to an array of attributes const attributeName = []; attrName.forEach((a) => { if (encodedAttrsUpdater !== undefined) { @@ -1487,6 +1616,7 @@ export class PresentationBuilder extends Versioned { attributeName }); } else { + // The circuit variable is a single value so will correspond to a single attribute if (encodedAttrsUpdater !== undefined) { encodedAttrsUpdater(attrName, encodedAttrs); } @@ -1507,13 +1637,37 @@ export class PresentationBuilder extends Versioned { }), circuitId: predicate.circuitId, snarkKeyId: predicate.provingKeyId, - protocol: CircomProtocols.Legogroth16 + protocol: CircomProtocol.Legogroth16 }); }); } return [encodedAttrs, predicatesForSpec]; } + private createCircomStatement(circuitId: string, snarkKeyId: string, setupParamsTrk: SetupParamsTracker, statementIdx?: number): Uint8Array { + const snarkKey = this.predicateParams.get(snarkKeyId); + const r1csId = PresentationBuilder.r1csParamId(circuitId); + const r1cs = this.predicateParams.get(r1csId); + const wasmId = PresentationBuilder.wasmParamId(circuitId); + const wasm = this.predicateParams.get(wasmId); + PresentationBuilder.addLegoProvingKeyToTracker(snarkKeyId, snarkKey, setupParamsTrk, statementIdx); + if (r1cs === undefined || wasm === undefined) { + throw new Error('Both WASM and R1CS should be present'); + } + if (!setupParamsTrk.isTrackingParam(r1csId)) { + setupParamsTrk.addForParamId(r1csId, SetupParam.r1cs(r1cs as R1CS)); + } + if (!setupParamsTrk.isTrackingParam(wasmId)) { + setupParamsTrk.addForParamId(wasmId, SetupParam.bytes(wasm as Uint8Array)); + } + + return Statement.r1csCircomProverFromSetupParamRefs( + setupParamsTrk.indexForParam(r1csId), + setupParamsTrk.indexForParam(wasmId), + setupParamsTrk.indexForParam(snarkKeyId) + ); + } + static enforceAttributeInequalities( self, ineqs: Map, @@ -1528,7 +1682,7 @@ export class PresentationBuilder extends Versioned { ineqs.set(attributeName, attrIneq); } // setting the encoded value (Uint8Array) as a dummy for now, this is later set to the correct value - attrIneq?.push([{ inEqualTo, paramId, protocol: InequalityProtocols.Uprove }, new Uint8Array()]); + attrIneq?.push([{ inEqualTo, paramId, protocol: InequalityProtocol.Uprove }, new Uint8Array()]); if (paramId !== undefined) { self.updatePredicateParams(paramId, param); } @@ -1546,7 +1700,7 @@ export class PresentationBuilder extends Versioned { */ static processBounds( self, - boundsMap: Map, + boundsMap: Map, attributeName: string, vmin: BoundType, vmax: BoundType, @@ -1561,26 +1715,26 @@ export class PresentationBuilder extends Versioned { if (min >= max) { throw new Error(`Invalid bounds min=${min}, max=${max}`); } - let protocol: BoundCheckProtocols; + let protocol: BoundCheckProtocol; if (paramId !== undefined) { self.updatePredicateParams(paramId, param); let par = self.predicateParams.get(paramId); if (par instanceof LegoProvingKey || par instanceof LegoProvingKeyUncompressed) { - protocol = BoundCheckProtocols.Legogroth16; + protocol = BoundCheckProtocol.Legogroth16; } else if (par instanceof BoundCheckBppParams || par instanceof BoundCheckBppParamsUncompressed) { - protocol = BoundCheckProtocols.Bpp; + protocol = BoundCheckProtocol.Bpp; } else if (par instanceof BoundCheckSmcParams || par instanceof BoundCheckSmcParamsUncompressed) { - protocol = BoundCheckProtocols.Smc; + protocol = BoundCheckProtocol.Smc; } else if ( par instanceof BoundCheckSmcWithKVProverParams || par instanceof BoundCheckSmcWithKVProverParamsUncompressed ) { - protocol = BoundCheckProtocols.SmcKV; + protocol = BoundCheckProtocol.SmcKV; } else { throw new Error(`Invalid predicate param type ${par} for bound check protocol`); } } else { - protocol = BoundCheckProtocols.Bpp; + protocol = BoundCheckProtocol.Bpp; } const existingBounds = boundsMap.get(attributeName); @@ -1611,7 +1765,7 @@ export class PresentationBuilder extends Versioned { commitmentGensId: commKeyId, encryptionKeyId: encryptionKeyId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }]); } else { existingVE.push({ @@ -1619,7 +1773,7 @@ export class PresentationBuilder extends Versioned { commitmentGensId: commKeyId, encryptionKeyId: encryptionKeyId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }); verEncsMap.set(attributeName, existingVE); } @@ -1636,6 +1790,13 @@ export interface IProverCircomPredicate { provingKeyId: string; } +export interface IProverCircomPredicateMultiCred { + privateVars: [string, [number, string] | [number, string][]][]; + publicVars: [string, Uint8Array | Uint8Array[]][]; + circuitId: string; + provingKeyId: string; +} + export interface IProverBoundedPseudonymInBlindedCredReq { basesForAttributes: Uint8Array[]; baseForSecretKey?: Uint8Array; diff --git a/src/anonymous-credentials/presentation-specification.ts b/src/anonymous-credentials/presentation-specification.ts index c750f6a2..69e46570 100644 --- a/src/anonymous-credentials/presentation-specification.ts +++ b/src/anonymous-credentials/presentation-specification.ts @@ -2,33 +2,33 @@ import { AttributeEquality, BlindedAttributeEquality, ID_STR, - BlindSignatureTypes, - BoundCheckProtocols, - CircomProtocols, - RevocationStatusProtocols, - SignatureTypes, - VerifiableEncryptionProtocols, + BlindSignatureType, + BoundCheckProtocol, + CircomProtocol, + RevocationStatusProtocol, + SignatureType, + VerifiableEncryptionProtocol, REV_CHECK_STR, TYPE_STR, - InequalityProtocols + InequalityProtocol } from './types-and-consts'; import b58 from 'bs58'; import { CredentialSchema } from './schema'; export interface IPresentedStatus { [ID_STR]: string; - [TYPE_STR]: RevocationStatusProtocols; + [TYPE_STR]: RevocationStatusProtocol; [REV_CHECK_STR]: string; accumulated: Uint8Array; extra: object; } -export interface IPresentedAttributeBounds { +export interface IPresentedAttributeBound { min: number; max: number; // paramId will be absent when Bulletproofs++ with default setup is used paramId?: string; - protocol: BoundCheckProtocols; + protocol: BoundCheckProtocol; } export interface IPresentedAttributeVE { @@ -36,22 +36,33 @@ export interface IPresentedAttributeVE { commitmentGensId: string; encryptionKeyId: string; snarkKeyId: string; - protocol: VerifiableEncryptionProtocols; + protocol: VerifiableEncryptionProtocol; } /** * A mapping of one private variable of the Circom circuit to one or more attributes */ -export interface ICircuitPrivateVars { +export interface ICircuitPrivateVar { varName: string; // A circuit variable can be a single value or an array and thus map to one or more attributes attributeName: { [key: string]: null | object } | { [key: string]: null | object }[]; } +/** + * A mapping of one private variable of the Circom circuit to one or more (credential, attribute) pairs + */ +export interface ICircuitPrivateVarMultiCred { + varName: string; + // A circuit variable can be reference to a single attribute or an array and thus map to one + // or more attribute references. Each attribute reference is a pair with 1st item is the credential + // index and 2nd is the attribute name + attributeRef: [number, { [key: string]: null | object }] | [number, { [key: string]: null | object }][]; +} + /** * A mapping of one public variable of the Circom circuit to one or more values */ -export interface ICircuitPublicVars { +export interface ICircuitPublicVar { varName: string; // A circuit variable can be a single value or an array and thus map to one or more values value: Uint8Array | Uint8Array[]; @@ -60,24 +71,24 @@ export interface ICircuitPublicVars { /** * R1CS public inputs, private attribute names involved in circuit. */ -export interface ICircomPredicate { - privateVars: ICircuitPrivateVars[]; - publicVars: ICircuitPublicVars[]; +export interface ICircomPredicate { + privateVars: PV[]; + publicVars: ICircuitPublicVar[]; // Used to identify the circuit and associated R1CS and WASM files circuitId: string; snarkKeyId: string; - protocol: CircomProtocols; + protocol: CircomProtocol; } export interface IPresentedAttributeInequality { inEqualTo: any; // paramId will be absent when default commitment key is used paramId?: string; - protocol: InequalityProtocols; + protocol: InequalityProtocol; } export interface IPresentedCredential { - sigType?: SignatureTypes; + sigType?: SignatureType; version: string; schema: string; // Attributes being revealed to the verifier @@ -85,11 +96,11 @@ export interface IPresentedCredential { // Credential status used for checking revocation status?: IPresentedStatus; // Bounds proved of any attribute(s) - bounds?: { [key: string]: string | IPresentedAttributeBounds | IPresentedAttributeBounds[] }; + bounds?: { [key: string]: string | IPresentedAttributeBound | IPresentedAttributeBound[] }; // Verifiable encryption of any attributes verifiableEncryptions?: { [key: string]: string | IPresentedAttributeVE | IPresentedAttributeVE[] }; // Predicates proved using Circom. Can be over any number of attributes - circomPredicates?: ICircomPredicate[]; + circomPredicates?: ICircomPredicate[]; attributeInequalities?: { [key: string]: string | IPresentedAttributeInequality[] }; } @@ -122,7 +133,7 @@ export interface IPresentedBoundedPseudonymInBlindedCredReq { export interface IBlindCredentialRequest { // Type of the signature requested, like BBS, BBS+ - sigType: BlindSignatureTypes; + sigType: BlindSignatureType; version: string; // The schema of the whole (unblinded credential). This should include all attributes, i.e. blinded and unblinded schema: CredentialSchema; @@ -131,11 +142,11 @@ export interface IBlindCredentialRequest { commitment: Uint8Array; attributeInequalities?: { [key: string]: string | IPresentedAttributeInequality[] }; // Bounds proved of any attribute(s) - bounds?: { [key: string]: string | IPresentedAttributeBounds[] }; + bounds?: { [key: string]: string | IPresentedAttributeBound[] }; // Verifiable encryption of any blinded attributes verifiableEncryptions?: { [key: string]: string | IPresentedAttributeVE[] }; // Predicates proved using Circom. Can be over any number of blinded attributes - circomPredicates?: ICircomPredicate[]; + circomPredicates?: ICircomPredicate[]; // Equalities between the blinded attributes and credential attributes blindedAttributeEqualities?: BlindedAttributeEquality[]; pseudonyms?: { [key: string]: IPresentedBoundedPseudonymInBlindedCredReq }; @@ -155,6 +166,7 @@ export class PresentationSpecification { // key == pseudonym unboundedPseudonyms?: { [key: string]: IPresentedUnboundedPseudonym }; blindCredentialRequest?: IBlindCredentialRequest; + circomPredicatesMultiCred?: ICircomPredicate[] constructor() { this.credentials = []; @@ -168,10 +180,10 @@ export class PresentationSpecification { schema: string, revealedAttributes: object, status?: IPresentedStatus, - bounds?: { [key: string]: string | IPresentedAttributeBounds[] }, + bounds?: { [key: string]: string | IPresentedAttributeBound[] }, verifiableEncryptions?: { [key: string]: string | IPresentedAttributeVE[] }, - circomPredicates?: ICircomPredicate[], - sigType?: SignatureTypes, + circomPredicates?: ICircomPredicate[], + sigType?: SignatureType, attributeInequalities?: { [key: string]: string | IPresentedAttributeInequality[] } ) { const ps = { @@ -219,7 +231,9 @@ export class PresentationSpecification { credentials: [], attributeEqualities: this.attributeEqualities, boundedPseudonyms: this.boundedPseudonyms, - unboundedPseudonyms: this.unboundedPseudonyms + unboundedPseudonyms: this.unboundedPseudonyms, + blindCredentialRequest: this.blindCredentialRequest, + circomPredicatesMultiCred: this.circomPredicatesMultiCred }; for (const pc of this.credentials) { diff --git a/src/anonymous-credentials/presentation.ts b/src/anonymous-credentials/presentation.ts index d95265be..dd75a34c 100644 --- a/src/anonymous-credentials/presentation.ts +++ b/src/anonymous-credentials/presentation.ts @@ -1,8 +1,8 @@ import { Versioned } from './versioned'; import { IBoundedPseudonymCommitKey, - ICircomPredicate, - IPresentedAttributeBounds, + ICircomPredicate, ICircuitPrivateVar, ICircuitPrivateVarMultiCred, + IPresentedAttributeBound, IPresentedAttributeInequality, IPresentedAttributeVE, IPresentedCredential, @@ -29,19 +29,19 @@ import { ID_STR, MEM_CHECK_STR, NON_MEM_CHECK_STR, - BlindSignatureTypes, - RevocationStatusProtocols, - SignatureTypes, + BlindSignatureType, + RevocationStatusProtocol, + SignatureType, PredicateParamType, PublicKey, REV_CHECK_STR, REV_ID_STR, SCHEMA_STR, STATUS_STR, - BoundCheckProtocols, - VerifiableEncryptionProtocols, - CircomProtocols, - InequalityProtocols + BoundCheckProtocol, + VerifiableEncryptionProtocol, + CircomProtocol, + InequalityProtocol } from './types-and-consts'; import { AccumulatorPublicKey } from '../accumulator'; import { @@ -112,7 +112,6 @@ export class Presentation extends Versioned { readonly attributeCiphertexts?: Map; // Similar to above for blinded attributes readonly blindedAttributeCiphertexts?: AttributeCiphertexts[]; - // This can specify the reason why the proof was created, or date of the proof, or self-attested attributes (as JSON string), etc readonly context?: string; // To prevent replay attack readonly nonce?: Uint8Array; @@ -140,21 +139,29 @@ export class Presentation extends Versioned { * @param publicKeys - Array of keys in the order of credentials in the presentation. * @param accumulatorPublicKeys - Mapping credential index -> accumulator public key * @param predicateParams - Setup params for various predicates - * @param circomOutputs - Values for the outputs variables of the Circom programs used for predicates + * @param circomOutputs - Values for the outputs variables of the Circom programs used for predicates. They key of the map + * is the credential index * @param blindedAttributesCircomOutputs - Outputs for Circom predicates on blinded attributes + * @param circomOutputsMultiCred - Values for the outputs variables of the Circom programs spanning over multiple credential attributes */ verify( + // TODO: Accept reference to public keys in case of same key for many credentials publicKeys: PublicKey[], accumulatorPublicKeys?: Map, predicateParams?: Map, circomOutputs?: Map, - blindedAttributesCircomOutputs?: Uint8Array[][] + blindedAttributesCircomOutputs?: Uint8Array[][], + circomOutputsMultiCred?: Uint8Array[][], ): VerifyResult { const numCreds = this.spec.credentials.length; if (publicKeys.length !== numCreds) { throw new Error(`Supply same no of public keys as creds. ${publicKeys.length} != ${numCreds}`); } + // NOTE: The order of processing predicates should match exactly to the order in presentation builder, eg. if circom predicates + // are processed at the end in the builder than they should be processed at the end here as well, if verifiable encryption is + // processed at 2nd last in the builder than they should be processed at 2nd last here as well. + const statements = new Statements(); const metaStatements = new MetaStatements(); @@ -169,35 +176,35 @@ export class Presentation extends Versioned { const ineqsAux: [number, { [key: string]: [IPresentedAttributeInequality, Uint8Array][] }][] = []; // For bound check on credential attributes - const boundsAux: [number, { [key: string]: string | IPresentedAttributeBounds | IPresentedAttributeBounds[] }][] = []; + const boundsAux: [number, { [key: string]: string | IPresentedAttributeBound | IPresentedAttributeBound[] }][] = []; // For verifiable encryption of credential attributes const verEncAux: [number, { [key: string]: string | IPresentedAttributeVE | IPresentedAttributeVE[] }][] = []; // For circom predicates on credential attributes - const circomAux: [number, ICircomPredicate[]][] = []; + const circomAux: [number, ICircomPredicate[]][] = []; const setupParamsTrk = new SetupParamsTracker(); const sigParamsByScheme = new Map(); - for (let i = 0; i < this.spec.credentials.length; i++) { - const presentedCred = this.spec.credentials[i]; + for (let credIndex = 0; credIndex < this.spec.credentials.length; credIndex++) { + const presentedCred = this.spec.credentials[credIndex]; const presentedCredSchema = CredentialSchema.fromJSON(JSON.parse(presentedCred.schema)); const flattenedSchema = presentedCredSchema.flatten(); const numAttribs = flattenedSchema[0].length; - const revealedEncoded = Presentation.encodeRevealed(i, presentedCred, presentedCredSchema, flattenedSchema[0]); + const revealedEncoded = Presentation.encodeRevealed(credIndex, presentedCred, presentedCredSchema, flattenedSchema[0]); - const paramsClass = paramsClassByPublicKey(publicKeys[i]); + const paramsClass = paramsClassByPublicKey(publicKeys[credIndex]); if (paramsClass === null) { - throw new Error(`Invalid public key: ${publicKeys[i]}`); + throw new Error(`Invalid public key: ${publicKeys[credIndex]}`); } const sigParams = getSignatureParamsForMsgCount(sigParamsByScheme, paramsClass, numAttribs); const statement = buildSignatureStatementFromParamsRef( setupParamsTrk, sigParams, - publicKeys[i], + publicKeys[credIndex], numAttribs, revealedEncoded ); @@ -206,7 +213,7 @@ export class Presentation extends Versioned { if (presentedCred.status !== undefined) { // The input validation and security checks for these have been done as part of encoding revealed attributes - credStatusAux.push([i, presentedCred.status[REV_CHECK_STR], presentedCred.status.accumulated]); + credStatusAux.push([credIndex, presentedCred.status[REV_CHECK_STR], presentedCred.status.accumulated]); } if (presentedCred.attributeInequalities !== undefined) { @@ -219,17 +226,17 @@ export class Presentation extends Versioned { presentedCredSchema.encoder.encodeMessage(names[j], ineqs_j.inEqualTo) ]); } - ineqsAux.push([i, obj]); + ineqsAux.push([credIndex, obj]); } if (presentedCred.bounds !== undefined) { - boundsAux.push([i, presentedCred.bounds]); + boundsAux.push([credIndex, presentedCred.bounds]); } if (presentedCred.verifiableEncryptions !== undefined) { - verEncAux.push([i, presentedCred.verifiableEncryptions]); + verEncAux.push([credIndex, presentedCred.verifiableEncryptions]); } if (presentedCred.circomPredicates !== undefined) { - circomAux.push([i, presentedCred.circomPredicates]); + circomAux.push([credIndex, presentedCred.circomPredicates]); } } @@ -313,6 +320,33 @@ export class Presentation extends Versioned { ); }); + if (this.spec.circomPredicatesMultiCred !== undefined) { + this.spec.circomPredicatesMultiCred.forEach((pred, j) => { + const statement = Presentation.createCircomStatement(pred, j, setupParamsTrk, predicateParams, circomOutputsMultiCred); + const sIdx = statements.add(statement); + + function addWitnessEquality(cId: number, attributeName: object) { + const attr = flattenObjectToKeyValuesList(attributeName) as object; + const nameIdx = flattenedSchemas[cId][0].indexOf(attr[0][0]); + const witnessEq = new WitnessEqualityMetaStatement(); + witnessEq.addWitnessRef(cId, nameIdx); + witnessEq.addWitnessRef(sIdx, predicateWitnessIdx++); + metaStatements.addWitnessEquality(witnessEq); + } + + let predicateWitnessIdx = 0; + pred.privateVars.forEach((privateVars) => { + if (Array.isArray(privateVars.attributeRef)) { + privateVars.attributeRef.forEach((attrRef) => { + addWitnessEquality(attrRef[0], attrRef[1]); + }); + } else { + addWitnessEquality(privateVars.attributeRef[0], privateVars.attributeRef[1]); + } + }); + }); + } + function createPseudonymStatement(pseudonym: string, commitKey: IBoundedPseudonymCommitKey): number { const basesForAttributes = PseudonymBases.encodeBasesForAttributes(commitKey.basesForAttributes); const decodedBaseForSecretKey = commitKey.baseForSecretKey; @@ -605,7 +639,7 @@ export class Presentation extends Versioned { private processBoundChecks( statementIdx: number, witnessIndexGetter: (string) => number, - b: { [key: string]: string | IPresentedAttributeBounds | IPresentedAttributeBounds[] }, + b: { [key: string]: string | IPresentedAttributeBound | IPresentedAttributeBound[] }, flattenedSchema: FlattenedSchema, statements: Statements, metaStatements: MetaStatements, @@ -636,12 +670,12 @@ export class Presentation extends Versioned { // Older versions of presentation did not have protocol name specified if (semver.lt(this.version, '0.2.0')) { - protocol = BoundCheckProtocols.Legogroth16; + protocol = BoundCheckProtocol.Legogroth16; } if (paramId === undefined) { // paramId is undefined means no setup param was passed and thus the default setup of Bulletproofs++ can be used. - if (protocol !== BoundCheckProtocols.Bpp) { + if (protocol !== BoundCheckProtocol.Bpp) { throw new Error( `Hardcoded setup for bound check is only available for Bulletproofs++ but found protocol ${protocol}` ); @@ -657,7 +691,7 @@ export class Presentation extends Versioned { } } else { switch (protocol) { - case BoundCheckProtocols.Legogroth16: + case BoundCheckProtocol.Legogroth16: Presentation.addLegoVerifyingKeyToTracker(paramId, param, setupParamsTrk, statementIdx); statement = Statement.boundCheckLegoVerifierFromSetupParamRefs( transformedMin, @@ -665,7 +699,7 @@ export class Presentation extends Versioned { setupParamsTrk.indexForParam(paramId) ); break; - case BoundCheckProtocols.Bpp: + case BoundCheckProtocol.Bpp: Presentation.addBppSetupParamsToTracker(paramId, param, setupParamsTrk, statementIdx); statement = Statement.boundCheckBppFromSetupParamRefs( transformedMin, @@ -673,7 +707,7 @@ export class Presentation extends Versioned { setupParamsTrk.indexForParam(paramId) ); break; - case BoundCheckProtocols.Smc: + case BoundCheckProtocol.Smc: Presentation.addSmcSetupParamsToTracker(paramId, param, setupParamsTrk, statementIdx); statement = Statement.boundCheckSmcFromSetupParamRefs( transformedMin, @@ -681,7 +715,7 @@ export class Presentation extends Versioned { setupParamsTrk.indexForParam(paramId) ); break; - case BoundCheckProtocols.SmcKV: + case BoundCheckProtocol.SmcKV: Presentation.addSmcKVVerifierParamsToTracker(paramId, param, setupParamsTrk, statementIdx); statement = Statement.boundCheckSmcWithKVVerifierFromSetupParamRefs( transformedMin, @@ -780,7 +814,7 @@ export class Presentation extends Versioned { private processCircomPredicates( statementIdx: number, witnessIndexGetter: (string) => number, - predicates: ICircomPredicate[], + predicates: ICircomPredicate[], statements: Statements, metaStatements: MetaStatements, setupParamsTrk: SetupParamsTracker, @@ -788,22 +822,7 @@ export class Presentation extends Versioned { outputs?: Uint8Array[][] ) { predicates.forEach((pred, j) => { - const param = predicateParams?.get(pred.snarkKeyId); - Presentation.addLegoVerifyingKeyToTracker(pred.snarkKeyId, param, setupParamsTrk, statementIdx); - - let publicInputs = pred.publicVars.flatMap((pv) => { - return pv.value; - }); - if (outputs !== undefined && outputs.length > j) { - publicInputs = outputs[j].concat(publicInputs); - } - const unqId = `circom-outputs-${statementIdx}__${j}`; - setupParamsTrk.addForParamId(unqId, SetupParam.fieldElementVec(publicInputs)); - - const statement = Statement.r1csCircomVerifierFromSetupParamRefs( - setupParamsTrk.indexForParam(unqId), - setupParamsTrk.indexForParam(pred.snarkKeyId) - ); + const statement = Presentation.createCircomStatement(pred, j, setupParamsTrk, predicateParams, outputs, statementIdx); const sIdx = statements.add(statement); function addWitnessEquality(attributeName: object) { @@ -828,6 +847,25 @@ export class Presentation extends Versioned { }); } + private static createCircomStatement(pred: ICircomPredicate, predIdx: number, setupParamsTrk: SetupParamsTracker, predicateParams?: Map, outputs?: Uint8Array[][], statementIdx?: number): Uint8Array { + const param = predicateParams?.get(pred.snarkKeyId); + Presentation.addLegoVerifyingKeyToTracker(pred.snarkKeyId, param, setupParamsTrk); + + let publicInputs = pred.publicVars.flatMap((pv) => { + return pv.value; + }); + if (outputs !== undefined && outputs.length > predIdx) { + publicInputs = outputs[predIdx].concat(publicInputs); + } + const unqId = `circom-outputs-${statementIdx !== undefined ? statementIdx : null}__${predIdx}`; + setupParamsTrk.addForParamId(unqId, SetupParam.fieldElementVec(publicInputs)); + + return Statement.r1csCircomVerifierFromSetupParamRefs( + setupParamsTrk.indexForParam(unqId), + setupParamsTrk.indexForParam(pred.snarkKeyId) + ); + } + toJSON(): object { let attributeCiphertexts; if (this.attributeCiphertexts !== undefined) { @@ -838,7 +876,7 @@ export class Presentation extends Versioned { } } - function formatCircomPreds(circomPredicates: ICircomPredicate[]): object { + function formatCircomPreds(circomPredicates: ICircomPredicate[]): object { return circomPredicates.map((v) => { const r = deepClone(v) as object; // @ts-ignore @@ -898,6 +936,9 @@ export class Presentation extends Versioned { if (blindCredentialRequest !== undefined) { spec['blindCredentialRequest'] = blindCredentialRequest; } + if (this.spec.circomPredicatesMultiCred !== undefined) { + spec['circomPredicatesMultiCred'] = formatCircomPreds(this.spec.circomPredicatesMultiCred); + } const p = { version: this.version, @@ -971,7 +1012,7 @@ export class Presentation extends Versioned { paramId: string, param: PredicateParamType | undefined, setupParamsTrk: SetupParamsTracker, - statementIdx: number + statementIdx?: number ) { if (param instanceof LegoVerifyingKey) { if (!setupParamsTrk.isTrackingParam(paramId)) { @@ -982,9 +1023,13 @@ export class Presentation extends Versioned { setupParamsTrk.addForParamId(paramId, SetupParam.legosnarkVerifyingKeyUncompressed(param)); } } else { - throw new Error( - `Predicate param id ${paramId} (for statement index ${statementIdx}) was expected to be a Legosnark verifying key but was ${param}` - ); + let errorMsg: string; + if (statementIdx !== undefined) { + errorMsg = `Predicate param id ${paramId} (for statement index ${statementIdx}) was expected to be a Legosnark verifying key but was ${param}`; + } else { + errorMsg = `Predicate param id ${paramId} was expected to be a Legosnark verifying key but was ${param}`; + } + throw new Error(errorMsg); } } @@ -1098,10 +1143,10 @@ export class Presentation extends Versioned { const { version, context, nonce, spec, attributeCiphertexts, blindedAttributeCiphertexts, proof } = j; const nnc = nonce ? b58.decode(nonce) : undefined; - function formatCircomPreds(pred: object): ICircomPredicate[] { + function formatCircomPreds(pred: object): ICircomPredicate[] { const circomPredicates = deepClone(pred) as object[]; circomPredicates.forEach((cp) => { - if (cp['protocol'] !== undefined && !Object.values(CircomProtocols).includes(cp['protocol'])) { + if (cp['protocol'] !== undefined && !Object.values(CircomProtocol).includes(cp['protocol'])) { throw new Error(`Unrecognized protocol ${cp['protocol']} for Circom`); } cp['publicVars'] = cp['publicVars'].map((pv) => { @@ -1122,7 +1167,7 @@ export class Presentation extends Versioned { for (let i = 0; i < ineqs[0].length; i++) { // @ts-ignore ineqs[1][i].forEach((ineq) => { - if (!Object.values(InequalityProtocols).includes(ineq['protocol'])) { + if (!Object.values(InequalityProtocol).includes(ineq['protocol'])) { throw new Error( `Unrecognized protocol ${ineq['protocol']} for public inequality for attribute ${ineqs[0][i]} with value ${ineq['inEqualTo']}` ); @@ -1136,7 +1181,7 @@ export class Presentation extends Versioned { for (let i = 0; i < bounds[0].length; i++) { if ( bounds[1][i]['protocol'] !== undefined && - !Object.values(BoundCheckProtocols).includes(bounds[1][i]['protocol']) + !Object.values(BoundCheckProtocol).includes(bounds[1][i]['protocol']) ) { throw new Error( `Unrecognized protocol ${bounds[1][i]['protocol']} for bound check for attribute ${bounds[0][i]}` @@ -1150,7 +1195,7 @@ export class Presentation extends Versioned { for (let i = 0; i < vencs[0].length; i++) { if ( vencs[1][i]['protocol'] !== undefined && - !Object.values(VerifiableEncryptionProtocols).includes(vencs[1][i]['protocol']) + !Object.values(VerifiableEncryptionProtocol).includes(vencs[1][i]['protocol']) ) { throw new Error( `Unrecognized protocol ${vencs[1][i]['protocol']} for verifiable encryption for attribute ${vencs[0][i]}` @@ -1161,12 +1206,12 @@ export class Presentation extends Versioned { let status, circomPredicates, sigType; if (cred['status'] !== undefined) { - if (Object.values(RevocationStatusProtocols).includes(cred['status']['type'])) { + if (Object.values(RevocationStatusProtocol).includes(cred['status']['type'])) { status = deepClone(cred['status']) as object; status['accumulated'] = b58.decode(cred['status']['accumulated']); } else { throw new Error( - `status type should be one of ${RevocationStatusProtocols} but was ${cred['status']['type']}` + `status type should be one of ${RevocationStatusProtocol} but was ${cred['status']['type']}` ); } } @@ -1174,10 +1219,10 @@ export class Presentation extends Versioned { circomPredicates = formatCircomPreds(cred['circomPredicates']); } if (cred['sigType'] !== undefined) { - if (Object.values(SignatureTypes).includes(cred['sigType'])) { + if (Object.values(SignatureType).includes(cred['sigType'])) { sigType = cred['sigType']; } else { - throw new Error(`sigType should be one of ${SignatureTypes} but was ${cred['sigType']}`); + throw new Error(`sigType should be one of ${SignatureType} but was ${cred['sigType']}`); } } presSpec.addPresentedCredential( @@ -1193,6 +1238,10 @@ export class Presentation extends Versioned { ); } + if (spec['circomPredicatesMultiCred'] !== undefined) { + presSpec.circomPredicatesMultiCred = formatCircomPreds(spec['circomPredicatesMultiCred']) as ICircomPredicate[]; + } + presSpec.attributeEqualities = spec['attributeEqualities']; presSpec.boundedPseudonyms = spec['boundedPseudonyms']; presSpec.unboundedPseudonyms = spec['unboundedPseudonyms']; @@ -1211,8 +1260,8 @@ export class Presentation extends Versioned { let bac; if (spec['blindCredentialRequest'] !== undefined) { const req = deepClone(spec['blindCredentialRequest']) as object; - if (!Object.values(BlindSignatureTypes).includes(req['sigType'])) { - throw new Error(`sigType should be one of ${BlindSignatureTypes} but was ${req['sigType']}`); + if (!Object.values(BlindSignatureType).includes(req['sigType'])) { + throw new Error(`sigType should be one of ${BlindSignatureType} but was ${req['sigType']}`); } req['schema'] = CredentialSchema.fromJSON(JSON.parse(req['schema'])); req['commitment'] = b58.decode(req['commitment']); diff --git a/src/anonymous-credentials/types-and-consts.ts b/src/anonymous-credentials/types-and-consts.ts index 0051ed8b..bab722dd 100644 --- a/src/anonymous-credentials/types-and-consts.ts +++ b/src/anonymous-credentials/types-and-consts.ts @@ -181,36 +181,36 @@ export function dockInequalityCommKeyUncompressed(): PederCommKeyUncompressed { return new PederCommKey(INEQUALITY_COMM_KEY_LABEL_BYTES).decompress(); } -export enum SignatureTypes { +export enum SignatureType { Bbs = BBS_CRED_PROOF_TYPE, BbsPlus = BBS_PLUS_CRED_PROOF_TYPE, Ps = PS_CRED_PROOF_TYPE } -export enum BlindSignatureTypes { +export enum BlindSignatureType { Bbs = BBS_BLINDED_CRED_PROOF_TYPE, BbsPlus = BBS_PLUS_BLINDED_CRED_PROOF_TYPE } -export enum RevocationStatusProtocols { +export enum RevocationStatusProtocol { Vb22 = VB_ACCUMULATOR_22 } -export enum BoundCheckProtocols { +export enum BoundCheckProtocol { Legogroth16 = LEGOGROTH16, Bpp = BPP, Smc = SMC, SmcKV = SMC_KV } -export enum VerifiableEncryptionProtocols { +export enum VerifiableEncryptionProtocol { Saver = SAVER } -export enum CircomProtocols { +export enum CircomProtocol { Legogroth16 = LEGOGROTH16 } -export enum InequalityProtocols { +export enum InequalityProtocol { Uprove = UPROVE } diff --git a/tests/anonymous-credentials/blind-issuance.spec.ts b/tests/anonymous-credentials/blind-issuance.spec.ts index fa1f7de2..6fdec380 100644 --- a/tests/anonymous-credentials/blind-issuance.spec.ts +++ b/tests/anonymous-credentials/blind-issuance.spec.ts @@ -33,8 +33,8 @@ import { VB_ACCUMULATOR_22, SUBJECT_STR, TYPE_STR, - BoundCheckProtocols, - VerifiableEncryptionProtocols, + BoundCheckProtocol, + VerifiableEncryptionProtocol, BoundCheckBppParams, BoundCheckSmcParams, BoundCheckBppParamsUncompressed, @@ -44,7 +44,7 @@ import { BoundCheckSmcWithKVSetup, DefaultSchemaParsingOpts, META_SCHEMA_STR, - InequalityProtocols + InequalityProtocol } from '../../src'; import { SignatureParams, @@ -322,10 +322,10 @@ skipIfPS.each([true, false])(`${Scheme} Blind issuance of credentials with withS credentialSubject: { sensitive: { email: [ - { inEqualTo: inEqualEmail, protocol: InequalityProtocols.Uprove }, - { inEqualTo: inEqualEmail2, protocol: InequalityProtocols.Uprove } + { inEqualTo: inEqualEmail, protocol: InequalityProtocol.Uprove }, + { inEqualTo: inEqualEmail2, protocol: InequalityProtocol.Uprove } ], - SSN: [{ inEqualTo: inEqualSsn, protocol: InequalityProtocols.Uprove }] + SSN: [{ inEqualTo: inEqualSsn, protocol: InequalityProtocol.Uprove }] } } }); @@ -631,12 +631,12 @@ skipIfPS.each([true, false])(`${Scheme} Blind issuance of credentials with withS credentialSubject: { education: { transcript: { - CGPA: [{ min: 2.5, max: 3.5, paramId: boundCheckSnarkId, protocol: BoundCheckProtocols.Legogroth16 }], - rank: [{ min: 50, max: 200, paramId: boundCheckBppId, protocol: BoundCheckProtocols.Bpp }], + CGPA: [{ min: 2.5, max: 3.5, paramId: boundCheckSnarkId, protocol: BoundCheckProtocol.Legogroth16 }], + rank: [{ min: 50, max: 200, paramId: boundCheckBppId, protocol: BoundCheckProtocol.Bpp }], scores: { - english: [{ min: 20, max: 100, paramId: boundCheckSmcId, protocol: BoundCheckProtocols.Smc }], - science: [{ min: 30, max: 100, paramId: boundCheckSmcKVId, protocol: BoundCheckProtocols.SmcKV }], - history: [{ min: 20, max: 60, paramId: undefined, protocol: BoundCheckProtocols.Bpp }] + english: [{ min: 20, max: 100, paramId: boundCheckSmcId, protocol: BoundCheckProtocol.Smc }], + science: [{ min: 30, max: 100, paramId: boundCheckSmcKVId, protocol: BoundCheckProtocol.SmcKV }], + history: [{ min: 20, max: 60, paramId: undefined, protocol: BoundCheckProtocol.Bpp }] } } } @@ -651,7 +651,7 @@ skipIfPS.each([true, false])(`${Scheme} Blind issuance of credentials with withS commitmentGensId: commKeyId1, encryptionKeyId: ekId1, snarkKeyId: snarkPkId1, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver } ] } @@ -665,14 +665,14 @@ skipIfPS.each([true, false])(`${Scheme} Blind issuance of credentials with withS commitmentGensId: commKeyId1, encryptionKeyId: ekId1, snarkKeyId: snarkPkId1, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }, { chunkBitSize, commitmentGensId: commKeyId2, encryptionKeyId: ekId2, snarkKeyId: snarkPkId2, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver } ] } @@ -835,7 +835,7 @@ skipIfPS.each([true, false])(`${Scheme} Blind issuance of credentials with withS min: 1662010849610, max: 1662010849620, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 } ] } @@ -849,7 +849,7 @@ skipIfPS.each([true, false])(`${Scheme} Blind issuance of credentials with withS commitmentGensId: commKeyId, encryptionKeyId: ekId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver } ] } @@ -865,7 +865,7 @@ skipIfPS.each([true, false])(`${Scheme} Blind issuance of credentials with withS commitmentGensId: commKeyId, encryptionKeyId: ekId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver } ] } diff --git a/tests/anonymous-credentials/presentation-circom.spec.ts b/tests/anonymous-credentials/presentation-circom.spec.ts index 6a36e4a6..da288762 100644 --- a/tests/anonymous-credentials/presentation-circom.spec.ts +++ b/tests/anonymous-credentials/presentation-circom.spec.ts @@ -5,7 +5,8 @@ import { R1CSSnarkSetup, getR1CS, META_SCHEMA_STR, - DefaultSchemaParsingOpts + DefaultSchemaParsingOpts, + SUBJECT_STR } from '../../src'; import { SignatureParams, @@ -22,310 +23,667 @@ import { import { checkPresentationJson, getExampleSchema } from './utils'; import { checkResult, getWasmBytes, parseR1CSFile, stringToBytes } from '../utils'; -describe.each([true, false])(`${Scheme} Presentation creation and verification with Circom predicates with withSchemaRef=%s`, (withSchemaRef) => { - let sk: SecretKey, pk: PublicKey; - - let credential1: Credential; - let credential2: Credential; - - const requiredGrades = ['A+', 'A', 'B+', 'B', 'C']; - - // R1CS, WASM and keys for circuit set_membership_5_public - let r1csGrade: ParsedR1CSFile; - let wasmGrade: Uint8Array; - let provingKeyGrade; - let verifyingKeyGrade; - - // R1CS, WASM and keys for circuit less_than_public_64 - let r1csLtPub: ParsedR1CSFile; - let wasmLtPub: Uint8Array; - let provingKeyLtPub; - let verifyingKeyLtPub; - - const nonEmbeddedSchema = { - $id: 'https://example.com?hash=abc123ff', - [META_SCHEMA_STR]: 'http://json-schema.org/draft-07/schema#', - type: 'object', - }; - - beforeAll(async () => { - await initializeWasm(); - const params = SignatureParams.generate(100, SignatureLabelBytes); - const keypair = KeyPair.generate(params, stringToBytes('seed1')); - sk = keypair.sk; - pk = keypair.pk; - - let credSchema1: CredentialSchema, credSchema2: CredentialSchema; - const schema1 = getExampleSchema(12); - if (withSchemaRef) { - credSchema1 = new CredentialSchema(nonEmbeddedSchema, DefaultSchemaParsingOpts, true, undefined, schema1) - } else { - credSchema1 = new CredentialSchema(schema1); - } - - const builder1 = new CredentialBuilder(); - builder1.schema = credSchema1; - builder1.subject = { - fname: 'John', - lname: 'Smith', - education: { - score1: 55, - score2: 60, - score3: 45, - grade: 'B+' - } +describe.each([true, false])( + `${Scheme} Presentation creation and verification with Circom predicates with withSchemaRef=%s`, + (withSchemaRef) => { + let sk: SecretKey, pk: PublicKey; + + let credential1: Credential; + let credential2: Credential; + + const requiredGrades = ['A+', 'A', 'B+', 'B', 'C']; + + // R1CS, WASM and keys for circuit set_membership_5_public + let r1csGrade: ParsedR1CSFile; + let wasmGrade: Uint8Array; + let provingKeyGrade; + let verifyingKeyGrade; + + // R1CS, WASM and keys for circuit less_than_public_64 + let r1csLtPub: ParsedR1CSFile; + let wasmLtPub: Uint8Array; + let provingKeyLtPub; + let verifyingKeyLtPub; + + const nonEmbeddedSchema = { + $id: 'https://example.com?hash=abc123ff', + [META_SCHEMA_STR]: 'http://json-schema.org/draft-07/schema#', + type: 'object' }; - credential1 = builder1.sign(sk); - - const builder2 = new CredentialBuilder(); - builder2.schema = credSchema1; - builder2.subject = { - fname: 'Bob', - lname: 'Smith', - education: { - score1: 35, - score2: 20, - score3: 25, - grade: 'E' + + beforeAll(async () => { + await initializeWasm(); + const params = SignatureParams.generate(100, SignatureLabelBytes); + const keypair = KeyPair.generate(params, stringToBytes('seed1')); + sk = keypair.sk; + pk = keypair.pk; + + let credSchema1: CredentialSchema, credSchema2: CredentialSchema; + const schema1 = getExampleSchema(12); + if (withSchemaRef) { + credSchema1 = new CredentialSchema(nonEmbeddedSchema, DefaultSchemaParsingOpts, true, undefined, schema1); + } else { + credSchema1 = new CredentialSchema(schema1); } - }; - credential2 = builder2.sign(sk); - - r1csGrade = await parseR1CSFile('set_membership_5_public.r1cs'); - wasmGrade = getWasmBytes('set_membership_5_public.wasm'); - let prk = R1CSSnarkSetup.fromParsedR1CSFile(r1csGrade, 1); - provingKeyGrade = prk.decompress(); - verifyingKeyGrade = prk.getVerifyingKeyUncompressed(); - - r1csLtPub = await parseR1CSFile('less_than_public_64.r1cs'); - wasmLtPub = getWasmBytes('less_than_public_64.wasm'); - prk = R1CSSnarkSetup.fromParsedR1CSFile(r1csLtPub, 1); - provingKeyLtPub = prk.decompress(); - verifyingKeyLtPub = prk.getVerifyingKeyUncompressed(); - }); - - it('with predicate checking that grade is or is not from a required set', () => { - const pkId = 'random1'; - const circuitId = 'random2'; - - const encodedGrades = requiredGrades.map((g: string) => - credential1.schema.encoder.encodeMessage('credentialSubject.education.grade', g) - ); - - // Test that the `grade` attribute in credential does belong to the set `requiredGrades` - const builder1 = new PresentationBuilder(); - builder1.addCredential(credential1, pk); - builder1.markAttributesRevealed(0, new Set(['credentialSubject.fname'])); - builder1.enforceCircomPredicate( - 0, - [['x', 'credentialSubject.education.grade']], - [['set', encodedGrades]], - circuitId, - pkId, - r1csGrade, - wasmGrade, - provingKeyGrade - ); - - const pres1 = builder1.finalize(); - - // Verifier should check that the spec has the required predicates and also check the variable names are mapped - // to the correct attributes - expect(pres1.spec.credentials[0].circomPredicates?.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].privateVars.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].privateVars[0]).toEqual({ - varName: 'x', - attributeName: { credentialSubject: { education: { grade: null } } } + + const builder1 = new CredentialBuilder(); + builder1.schema = credSchema1; + builder1.subject = { + fname: 'John', + lname: 'Smith', + education: { + score1: 55, + score2: 60, + score3: 45, + grade: 'B+' + } + }; + credential1 = builder1.sign(sk); + + const builder2 = new CredentialBuilder(); + builder2.schema = credSchema1; + builder2.subject = { + fname: 'Bob', + lname: 'Smith', + education: { + score1: 35, + score2: 20, + score3: 25, + grade: 'E' + } + }; + credential2 = builder2.sign(sk); + + r1csGrade = await parseR1CSFile('set_membership_5_public.r1cs'); + wasmGrade = getWasmBytes('set_membership_5_public.wasm'); + let prk = R1CSSnarkSetup.fromParsedR1CSFile(r1csGrade, 1); + provingKeyGrade = prk.decompress(); + verifyingKeyGrade = prk.getVerifyingKeyUncompressed(); + + r1csLtPub = await parseR1CSFile('less_than_public_64.r1cs'); + wasmLtPub = getWasmBytes('less_than_public_64.wasm'); + prk = R1CSSnarkSetup.fromParsedR1CSFile(r1csLtPub, 1); + provingKeyLtPub = prk.decompress(); + verifyingKeyLtPub = prk.getVerifyingKeyUncompressed(); }); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].publicVars.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].publicVars[0].varName).toEqual('set'); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].publicVars[0].value).toEqual(encodedGrades); - - const pp = new Map(); - pp.set(pkId, verifyingKeyGrade); - pp.set(PresentationBuilder.r1csParamId(circuitId), getR1CS(r1csGrade)); - pp.set(PresentationBuilder.wasmParamId(circuitId), wasmGrade); - - // Set output variable for circuit. - // The output is set to "1" because the grade does belong to the required set - const circomOutputs = new Map(); - circomOutputs.set(0, [[generateFieldElementFromNumber(1)]]); - checkResult(pres1.verify([pk], undefined, pp, circomOutputs)); - - checkPresentationJson(pres1, [pk], undefined, pp, circomOutputs); - - // Setting the output variable "0" would fail the proof verification because the grade does belong to the required set - let wrongCircomOutputs = new Map(); - wrongCircomOutputs.set(0, [[generateFieldElementFromNumber(0)]]); - expect(pres1.verify([pk], undefined, pp, wrongCircomOutputs).verified).toBe(false); - - // Test that the `grade` attribute in credential does not belong to the set `requiredGrades` - const builder2 = new PresentationBuilder(); - builder2.addCredential(credential2, pk); - builder2.markAttributesRevealed(0, new Set(['credentialSubject.fname'])); - builder2.enforceCircomPredicate( - 0, - [['x', 'credentialSubject.education.grade']], - [['set', encodedGrades]], - circuitId, - pkId, - r1csGrade, - wasmGrade, - provingKeyGrade - ); - - const pres2 = builder2.finalize(); - - // Verifier should check that the spec has the required predicates and also check the variable names are mapped - // to the correct attributes - expect(pres2.spec.credentials[0].circomPredicates?.length).toEqual(1); - // @ts-ignore - expect(pres2.spec.credentials[0].circomPredicates[0].privateVars.length).toEqual(1); - // @ts-ignore - expect(pres2.spec.credentials[0].circomPredicates[0].privateVars[0]).toEqual({ - varName: 'x', - attributeName: { credentialSubject: { education: { grade: null } } } + + it('with predicate checking that grade is or is not from a required set', () => { + const pkId = 'random1'; + const circuitId = 'random2'; + + const encodedGrades = requiredGrades.map((g: string) => + credential1.schema.encoder.encodeMessage('credentialSubject.education.grade', g) + ); + + // Test that the `grade` attribute in credential does belong to the set `requiredGrades` + const builder1 = new PresentationBuilder(); + builder1.addCredential(credential1, pk); + builder1.markAttributesRevealed(0, new Set(['credentialSubject.fname'])); + builder1.enforceCircomPredicate( + 0, + [['x', 'credentialSubject.education.grade']], + [['set', encodedGrades]], + circuitId, + pkId, + r1csGrade, + wasmGrade, + provingKeyGrade + ); + + const pres1 = builder1.finalize(); + + // Verifier should check that the spec has the required predicates and also check the variable names are mapped + // to the correct attributes + expect(pres1.spec.credentials[0].circomPredicates?.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].privateVars.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].privateVars[0]).toEqual({ + varName: 'x', + attributeName: { credentialSubject: { education: { grade: null } } } + }); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].publicVars.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].publicVars[0].varName).toEqual('set'); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].publicVars[0].value).toEqual(encodedGrades); + + const pp = new Map(); + pp.set(pkId, verifyingKeyGrade); + pp.set(PresentationBuilder.r1csParamId(circuitId), getR1CS(r1csGrade)); + pp.set(PresentationBuilder.wasmParamId(circuitId), wasmGrade); + + // Set output variable for circuit. + // The output is set to "1" because the grade does belong to the required set + const circomOutputs = new Map(); + circomOutputs.set(0, [[generateFieldElementFromNumber(1)]]); + checkResult(pres1.verify([pk], undefined, pp, circomOutputs)); + + checkPresentationJson(pres1, [pk], undefined, pp, circomOutputs); + + // Setting the output variable "0" would fail the proof verification because the grade does belong to the required set + let wrongCircomOutputs = new Map(); + wrongCircomOutputs.set(0, [[generateFieldElementFromNumber(0)]]); + expect(pres1.verify([pk], undefined, pp, wrongCircomOutputs).verified).toBe(false); + + // Test that the `grade` attribute in credential does not belong to the set `requiredGrades` + const builder2 = new PresentationBuilder(); + builder2.addCredential(credential2, pk); + builder2.markAttributesRevealed(0, new Set(['credentialSubject.fname'])); + builder2.enforceCircomPredicate( + 0, + [['x', 'credentialSubject.education.grade']], + [['set', encodedGrades]], + circuitId, + pkId, + r1csGrade, + wasmGrade, + provingKeyGrade + ); + + const pres2 = builder2.finalize(); + + // Verifier should check that the spec has the required predicates and also check the variable names are mapped + // to the correct attributes + expect(pres2.spec.credentials[0].circomPredicates?.length).toEqual(1); + // @ts-ignore + expect(pres2.spec.credentials[0].circomPredicates[0].privateVars.length).toEqual(1); + // @ts-ignore + expect(pres2.spec.credentials[0].circomPredicates[0].privateVars[0]).toEqual({ + varName: 'x', + attributeName: { credentialSubject: { education: { grade: null } } } + }); + // @ts-ignore + expect(pres2.spec.credentials[0].circomPredicates[0].publicVars.length).toEqual(1); + // @ts-ignore + expect(pres2.spec.credentials[0].circomPredicates[0].publicVars[0].varName).toEqual('set'); + // @ts-ignore + expect(pres2.spec.credentials[0].circomPredicates[0].publicVars[0].value).toEqual(encodedGrades); + + const pp1 = new Map(); + pp1.set(pkId, verifyingKeyGrade); + pp1.set(PresentationBuilder.r1csParamId(circuitId), getR1CS(r1csGrade)); + pp1.set(PresentationBuilder.wasmParamId(circuitId), wasmGrade); + + // Set output variable for circuit. + // The output is set to "0" because the grade does belong to the required set + const circomOutputs1 = new Map(); + circomOutputs1.set(0, [[generateFieldElementFromNumber(0)]]); + checkResult(pres2.verify([pk], undefined, pp1, circomOutputs1)); + + checkPresentationJson(pres2, [pk], undefined, pp1, circomOutputs1); + + // Setting the output variable "1" would fail the proof verification because the grade does not belong to the required set + wrongCircomOutputs = new Map(); + wrongCircomOutputs.set(0, [[generateFieldElementFromNumber(1)]]); + expect(pres2.verify([pk], undefined, pp1, wrongCircomOutputs).verified).toBe(false); }); - // @ts-ignore - expect(pres2.spec.credentials[0].circomPredicates[0].publicVars.length).toEqual(1); - // @ts-ignore - expect(pres2.spec.credentials[0].circomPredicates[0].publicVars[0].varName).toEqual('set'); - // @ts-ignore - expect(pres2.spec.credentials[0].circomPredicates[0].publicVars[0].value).toEqual(encodedGrades); - - const pp1 = new Map(); - pp1.set(pkId, verifyingKeyGrade); - pp1.set(PresentationBuilder.r1csParamId(circuitId), getR1CS(r1csGrade)); - pp1.set(PresentationBuilder.wasmParamId(circuitId), wasmGrade); - - // Set output variable for circuit. - // The output is set to "0" because the grade does belong to the required set - const circomOutputs1 = new Map(); - circomOutputs1.set(0, [[generateFieldElementFromNumber(0)]]); - checkResult(pres2.verify([pk], undefined, pp1, circomOutputs1)); - - checkPresentationJson(pres2, [pk], undefined, pp1, circomOutputs1); - - // Setting the output variable "1" would fail the proof verification because the grade does not belong to the required set - wrongCircomOutputs = new Map(); - wrongCircomOutputs.set(0, [[generateFieldElementFromNumber(1)]]); - expect(pres2.verify([pk], undefined, pp1, wrongCircomOutputs).verified).toBe(false); - }); - - it('with predicate checking that grade is from a required set and certain scores are higher than required', () => { - const pkId1 = 'random1'; - const circuitId1 = 'random2'; - const pkId2 = 'random3'; - const circuitId2 = 'random4'; - - const encodedGrades = requiredGrades.map((g: string) => - credential1.schema.encoder.encodeMessage('credentialSubject.education.grade', g) - ); - - // Test that the `grade` attribute in credential does belong to the set `requiredGrades` and both `score1` and `score2` are >= 50 - const encoded50 = generateFieldElementFromNumber(50); - - const builder1 = new PresentationBuilder(); - builder1.addCredential(credential1, pk); - builder1.markAttributesRevealed(0, new Set(['credentialSubject.fname'])); - builder1.enforceCircomPredicate( - 0, - [['x', 'credentialSubject.education.grade']], - [['set', encodedGrades]], - circuitId1, - pkId1, - r1csGrade, - wasmGrade, - provingKeyGrade - ); - builder1.enforceCircomPredicate( - 0, - [['a', 'credentialSubject.education.score1']], - [['b', encoded50]], - circuitId2, - pkId2, - r1csLtPub, - wasmLtPub, - provingKeyLtPub - ); - builder1.enforceCircomPredicate( - 0, - [['a', 'credentialSubject.education.score2']], - [['b', encoded50]], - circuitId2, - pkId2 - ); - - const pres1 = builder1.finalize(); - - // Verifier should check that the spec has the required predicates and also check the variable names are mapped - // to the correct attributes - expect(pres1.spec.credentials[0].circomPredicates?.length).toEqual(3); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].privateVars.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].privateVars[0]).toEqual({ - varName: 'x', - attributeName: { credentialSubject: { education: { grade: null } } } + + it('with predicate checking that grade is from a required set and certain scores are higher than required', () => { + const pkId1 = 'random1'; + const circuitId1 = 'random2'; + const pkId2 = 'random3'; + const circuitId2 = 'random4'; + + const encodedGrades = requiredGrades.map((g: string) => + credential1.schema.encoder.encodeMessage('credentialSubject.education.grade', g) + ); + + // Test that the `grade` attribute in credential does belong to the set `requiredGrades` and both `score1` and `score2` are >= 50 + const encoded50 = generateFieldElementFromNumber(50); + + const builder1 = new PresentationBuilder(); + builder1.addCredential(credential1, pk); + builder1.markAttributesRevealed(0, new Set(['credentialSubject.fname'])); + builder1.enforceCircomPredicate( + 0, + [['x', 'credentialSubject.education.grade']], + [['set', encodedGrades]], + circuitId1, + pkId1, + r1csGrade, + wasmGrade, + provingKeyGrade + ); + builder1.enforceCircomPredicate( + 0, + [['a', 'credentialSubject.education.score1']], + [['b', encoded50]], + circuitId2, + pkId2, + r1csLtPub, + wasmLtPub, + provingKeyLtPub + ); + builder1.enforceCircomPredicate( + 0, + [['a', 'credentialSubject.education.score2']], + [['b', encoded50]], + circuitId2, + pkId2 + ); + + const pres1 = builder1.finalize(); + + // Verifier should check that the spec has the required predicates and also check the variable names are mapped + // to the correct attributes + expect(pres1.spec.credentials[0].circomPredicates?.length).toEqual(3); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].privateVars.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].privateVars[0]).toEqual({ + varName: 'x', + attributeName: { credentialSubject: { education: { grade: null } } } + }); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].publicVars.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].publicVars[0].varName).toEqual('set'); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[0].publicVars[0].value).toEqual(encodedGrades); + + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[1].privateVars.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[1].privateVars[0]).toEqual({ + varName: 'a', + attributeName: { credentialSubject: { education: { score1: null } } } + }); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[1].publicVars.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[1].publicVars[0].varName).toEqual('b'); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[1].publicVars[0].value).toEqual(encoded50); + + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[2].privateVars.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[2].privateVars[0]).toEqual({ + varName: 'a', + attributeName: { credentialSubject: { education: { score2: null } } } + }); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[2].publicVars.length).toEqual(1); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[2].publicVars[0].varName).toEqual('b'); + // @ts-ignore + expect(pres1.spec.credentials[0].circomPredicates[2].publicVars[0].value).toEqual(encoded50); + + const pp = new Map(); + pp.set(pkId1, verifyingKeyGrade); + pp.set(PresentationBuilder.r1csParamId(circuitId1), getR1CS(r1csGrade)); + pp.set(PresentationBuilder.wasmParamId(circuitId1), wasmGrade); + pp.set(pkId2, verifyingKeyLtPub); + pp.set(PresentationBuilder.r1csParamId(circuitId2), getR1CS(r1csLtPub)); + pp.set(PresentationBuilder.wasmParamId(circuitId2), wasmLtPub); + + const circomOutputs = new Map(); + // Setting last 2 outputs to 0 as the circuit will output 1 when the private input (`score` attribute) is less than the public input (50 here) else 0. + // Here the prover is proving that the private input is greater than or equal to 50 + circomOutputs.set(0, [ + [generateFieldElementFromNumber(1)], + [generateFieldElementFromNumber(0)], + [generateFieldElementFromNumber(0)] + ]); + checkResult(pres1.verify([pk], undefined, pp, circomOutputs)); + + checkPresentationJson(pres1, [pk], undefined, pp, circomOutputs); }); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].publicVars.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].publicVars[0].varName).toEqual('set'); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[0].publicVars[0].value).toEqual(encodedGrades); - - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[1].privateVars.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[1].privateVars[0]).toEqual({ - varName: 'a', - attributeName: { credentialSubject: { education: { score1: null } } } + + it('with predicate checking that all receipts are unique and amount is less than 1000', async () => { + // Test for a scenario where a user wants to prove that he has 10 receipts where all are unique because they have + // different ids and all have amounts less than 1000 + + // If this is changed then the circuit should be changed as well + const numReceipts = 10; + + const pkId1 = 'random1'; + const circuitId1 = 'random2'; + const pkId2 = 'random3'; + const circuitId2 = 'random4'; + + const r1csForUnique = await parseR1CSFile('all_different_10.r1cs'); + const wasmForUnique = getWasmBytes('all_different_10.wasm'); + const snarkSetup = R1CSSnarkSetup.fromParsedR1CSFile(r1csForUnique, numReceipts); + const provingKeyForUniqueness = snarkSetup.decompress(); + const verifyingKeyForUniqueness = snarkSetup.getVerifyingKeyUncompressed(); + + const schema = CredentialSchema.essential(); + schema.properties[SUBJECT_STR] = { + type: 'object', + properties: { + id: { type: 'string' }, + date: { type: 'string', format: 'date-time' }, + posId: { type: 'string' }, + amount: { type: 'number', minimum: 0.01, multipleOf: 0.01 } + } + }; + const cs = new CredentialSchema(schema); + + const maxAmount = 1000; + const encodedMaxAmount = cs.encoder.encodeMessage('credentialSubject.amount', maxAmount); + + const credentials: Credential[] = []; + for (let i = 0; i < numReceipts; i++) { + const builder = new CredentialBuilder(); + builder.schema = cs; + builder.subject = { + id: 'e-123-987-1-22-' + (i + 1).toString(), // Unique id for each receipt + date: `2023-09-14T1${i}:26:40.488Z`, + posId: '1234567', + amount: maxAmount - Math.ceil(Math.random() * 100) + }; + expect(builder.subject.amount).toBeLessThan(maxAmount); + credentials.push(builder.sign(sk)); + checkResult(credentials[i].verify(pk)); + } + + const builder = new PresentationBuilder(); + + const attrRefs: [number, string][] = []; + for (let i = 0; i < numReceipts; i++) { + builder.addCredential(credentials[i], pk); + builder.markAttributesRevealed(i, new Set(['credentialSubject.posId'])); + + if (i == 0) { + builder.enforceCircomPredicate( + i, + [['a', 'credentialSubject.amount']], + [['b', encodedMaxAmount]], + circuitId2, + pkId2, + r1csLtPub, + wasmLtPub, + provingKeyLtPub + ); + } else { + builder.enforceCircomPredicate( + i, + [['a', 'credentialSubject.amount']], + [['b', encodedMaxAmount]], + circuitId2, + pkId2 + ); + } + attrRefs.push([i, 'credentialSubject.id']); + } + builder.enforceCircomPredicateAcrossMultipleCredentials( + [['in', attrRefs]], + [], + circuitId1, + pkId1, + r1csForUnique, + wasmForUnique, + provingKeyForUniqueness + ); + + const pres = builder.finalize(); + + const pp = new Map(); + pp.set(pkId1, verifyingKeyForUniqueness); + pp.set(PresentationBuilder.r1csParamId(circuitId1), getR1CS(r1csForUnique)); + pp.set(PresentationBuilder.wasmParamId(circuitId1), wasmForUnique); + pp.set(pkId2, verifyingKeyLtPub); + pp.set(PresentationBuilder.r1csParamId(circuitId2), getR1CS(r1csLtPub)); + pp.set(PresentationBuilder.wasmParamId(circuitId2), wasmLtPub); + + const circomOutputs = new Map(); + for (let i = 0; i < numReceipts; i++) { + circomOutputs.set(i, [[generateFieldElementFromNumber(1)]]); + } + + const circomOutputsMultiCred: Uint8Array[][] = []; + circomOutputsMultiCred.push([generateFieldElementFromNumber(1)]); + checkResult( + pres.verify(Array(numReceipts).fill(pk), undefined, pp, circomOutputs, undefined, circomOutputsMultiCred) + ); + checkPresentationJson(pres, Array(numReceipts).fill(pk), undefined, pp, circomOutputs, circomOutputsMultiCred); + }); + + it('with predicate checking that yearly income is less than 25000', async () => { + // Test for a scenario where a user wants to prove that his yearly income is less than 25000 where his income comprises + // of 12 payslip credentials, 1 for each month's. + + // If this is changed then the circuit should be changed as well + const numPayslips = 12; + + const pkId = 'random1'; + const circuitId = 'random2'; + const r1cs = await parseR1CSFile('sum_12_less_than_public.r1cs'); + const wasm = getWasmBytes('sum_12_less_than_public.wasm'); + const snarkPk = R1CSSnarkSetup.fromParsedR1CSFile(r1cs, 12); + const provingKey = snarkPk.decompress(); + const verifyingKey = snarkPk.getVerifyingKeyUncompressed(); + + const schema = CredentialSchema.essential(); + schema.properties[SUBJECT_STR] = { + type: 'object', + properties: { + fname: { type: 'string' }, + lname: { type: 'string' }, + employer: { type: 'string' }, + empId: { type: 'string' }, + salary: { + type: 'object', + properties: { + paySlipId: { type: 'string' }, + year: { type: 'integer', minimum: 0 }, + month: { type: 'integer', minimum: 0 }, + amount: { type: 'number', minimum: 0.01, multipleOf: 0.01 } + } + } + } + }; + const cs = new CredentialSchema(schema); + + const maxSalary = 25000; + const encodedMaxSalary = cs.encoder.encodeMessage('credentialSubject.salary.amount', maxSalary); + + const credentials: Credential[] = []; + for (let i = 0; i < numPayslips; i++) { + const builder = new CredentialBuilder(); + builder.schema = cs; + builder.subject = { + fname: 'John', + lname: 'Smith', + employer: 'Acme Corp', + empId: 'e-123-987-1', + salary: { + paySlipId: 'e-123-987-1-22-' + (i + 1).toString(), + year: 2022, + month: i + 1, + amount: Math.ceil(maxSalary / 12 - Math.random() * 100) + } + }; + expect(builder.subject.salary.amount).toBeLessThan(maxSalary); + credentials.push(builder.sign(sk)); + checkResult(credentials[i].verify(pk)); + } + + const builder = new PresentationBuilder(); + const attrRefs: [number, string][] = []; + + for (let i = 0; i < numPayslips; i++) { + builder.addCredential(credentials[i], pk); + builder.markAttributesRevealed(i, new Set(['credentialSubject.salary.year'])); + builder.markAttributesRevealed(i, new Set(['credentialSubject.salary.month'])); + attrRefs.push([i, 'credentialSubject.salary.amount']); + } + builder.enforceCircomPredicateAcrossMultipleCredentials( + [['in', attrRefs]], + [['max', encodedMaxSalary]], + circuitId, + pkId, + r1cs, + wasm, + provingKey + ); + + const pres = builder.finalize(); + + const pp = new Map(); + pp.set(pkId, verifyingKey); + pp.set(PresentationBuilder.r1csParamId(circuitId), getR1CS(r1cs)); + pp.set(PresentationBuilder.wasmParamId(circuitId), wasm); + + const circomOutputsMultiCred: Uint8Array[][] = []; + circomOutputsMultiCred.push([generateFieldElementFromNumber(1)]); + checkResult( + pres.verify(Array(numPayslips).fill(pk), undefined, pp, undefined, undefined, circomOutputsMultiCred) + ); + checkPresentationJson(pres, Array(numPayslips).fill(pk), undefined, pp, undefined, circomOutputsMultiCred); }); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[1].publicVars.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[1].publicVars[0].varName).toEqual('b'); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[1].publicVars[0].value).toEqual(encoded50); - - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[2].privateVars.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[2].privateVars[0]).toEqual({ - varName: 'a', - attributeName: { credentialSubject: { education: { score2: null } } } + + it('with predicate checking that difference between total assets and total liabilities is more than 10000', async () => { + // Test for a scenario where a user have 20 assets and liabilities, in different credentials. The user + // proves that the sum of his assets is greater than sum of liabilities by 10000 without revealing actual values of either. + + const numAssetCredentials = 4; // Circuit supports 20 assets, and each asset above has 5 values so 4 credentials (5*4=20) + const numLiabilityCredentials = 5; // Circuit supports 20 liabilities, and each liability above has 4 values so 5 credentials (5*4=20) + + const pkId = 'random1'; + const circuitId = 'random2'; + const r1cs = await parseR1CSFile('difference_of_array_sum_20_20.r1cs'); + const wasm = getWasmBytes('difference_of_array_sum_20_20.wasm'); + const snarkPk = R1CSSnarkSetup.fromParsedR1CSFile(r1cs, 40); + const provingKey = snarkPk.decompress(); + const verifyingKey = snarkPk.getVerifyingKeyUncompressed(); + + const assetSchema = CredentialSchema.essential(); + assetSchema.properties[SUBJECT_STR] = { + type: 'object', + properties: { + fname: { type: 'string' }, + lname: { type: 'string' }, + id: { type: 'string' }, + assets: { + type: 'object', + properties: { + id1: { type: 'integer', minimum: 0 }, + id2: { type: 'integer', minimum: 0 }, + id3: { type: 'integer', minimum: 0 }, + id4: { type: 'integer', minimum: 0 }, + id5: { type: 'integer', minimum: 0 } + } + } + } + }; + const assetCs = new CredentialSchema(assetSchema); + + const liabSchema = CredentialSchema.essential(); + liabSchema.properties[SUBJECT_STR] = { + type: 'object', + properties: { + fname: { type: 'string' }, + lname: { type: 'string' }, + id: { type: 'string' }, + liabilities: { + type: 'object', + properties: { + id1: { type: 'integer', minimum: 0 }, + id2: { type: 'integer', minimum: 0 }, + id3: { type: 'integer', minimum: 0 }, + id4: { type: 'integer', minimum: 0 } + } + } + } + }; + const liabCs = new CredentialSchema(liabSchema); + + const minDiff = 10000; + const encodedMinDiff = assetCs.encoder.encodeMessage('credentialSubject.assets.id1', minDiff); + + const assetCreds: Credential[] = []; + for (let i = 0; i < numAssetCredentials; i++) { + const builder = new CredentialBuilder(); + builder.schema = assetCs; + builder.subject = { + fname: 'John', + lname: 'Smith', + id: `aid-${i}`, + assets: { + id1: (i + 1) * 10000, + id2: (i + 2) * 10000, + id3: (i + 3) * 10000, + id4: (i + 4) * 10000, + id5: (i + 5) * 10000 + } + }; + assetCreds.push(builder.sign(sk)); + checkResult(assetCreds[i].verify(pk)); + } + + const liabCreds: Credential[] = []; + for (let i = 0; i < numLiabilityCredentials; i++) { + const builder = new CredentialBuilder(); + builder.schema = liabCs; + builder.subject = { + fname: 'John', + lname: 'Smith', + id: `lid-${i}`, + liabilities: { + id1: (i + 1) * 100, + id2: (i + 2) * 100, + id3: (i + 3) * 100, + id4: (i + 4) * 100 + } + }; + liabCreds.push(builder.sign(sk)); + checkResult(liabCreds[i].verify(pk)); + } + + const builder = new PresentationBuilder(); + const assetAttrRefs: [number, string][] = []; + const liabAttrRefs: [number, string][] = []; + for (let i = 0; i < numAssetCredentials; i++) { + builder.addCredential(assetCreds[i], pk); + for (let j = 0; j < 5; j++) { + assetAttrRefs.push([i, `credentialSubject.assets.id${j + 1}`]); + } + } + for (let i = 0; i < numLiabilityCredentials; i++) { + builder.addCredential(liabCreds[i], pk); + for (let j = 0; j < 4; j++) { + liabAttrRefs.push([numAssetCredentials + i, `credentialSubject.liabilities.id${j + 1}`]); + } + } + + builder.enforceCircomPredicateAcrossMultipleCredentials( + [ + ['inA', assetAttrRefs], + ['inB', liabAttrRefs] + ], + [['min', encodedMinDiff]], + circuitId, + pkId, + r1cs, + wasm, + provingKey + ); + + const pres = builder.finalize(); + + const pp = new Map(); + pp.set(pkId, verifyingKey); + pp.set(PresentationBuilder.r1csParamId(circuitId), getR1CS(r1cs)); + pp.set(PresentationBuilder.wasmParamId(circuitId), wasm); + + const circomOutputsMultiCred: Uint8Array[][] = []; + circomOutputsMultiCred.push([generateFieldElementFromNumber(1)]); + checkResult( + pres.verify( + Array(numAssetCredentials + numLiabilityCredentials).fill(pk), + undefined, + pp, + undefined, + undefined, + circomOutputsMultiCred + ) + ); + + checkPresentationJson(pres, Array(numAssetCredentials + numLiabilityCredentials).fill(pk), undefined, pp, undefined, circomOutputsMultiCred); }); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[2].publicVars.length).toEqual(1); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[2].publicVars[0].varName).toEqual('b'); - // @ts-ignore - expect(pres1.spec.credentials[0].circomPredicates[2].publicVars[0].value).toEqual(encoded50); - - const pp = new Map(); - pp.set(pkId1, verifyingKeyGrade); - pp.set(PresentationBuilder.r1csParamId(circuitId1), getR1CS(r1csGrade)); - pp.set(PresentationBuilder.wasmParamId(circuitId1), wasmGrade); - pp.set(pkId2, verifyingKeyLtPub); - pp.set(PresentationBuilder.r1csParamId(circuitId2), getR1CS(r1csLtPub)); - pp.set(PresentationBuilder.wasmParamId(circuitId2), wasmLtPub); - - const circomOutputs = new Map(); - // Setting last 2 outputs to 0 as the circuit will output 1 when the private input (`score` attribute) is less than the public input (50 here) else 0. - // Here the prover is proving that the private input is greater than or equal to 50 - circomOutputs.set(0, [ - [generateFieldElementFromNumber(1)], - [generateFieldElementFromNumber(0)], - [generateFieldElementFromNumber(0)] - ]); - checkResult(pres1.verify([pk], undefined, pp, circomOutputs)); - - checkPresentationJson(pres1, [pk], undefined, pp, circomOutputs); - }); -}); + } +); diff --git a/tests/anonymous-credentials/presentation-multiple-bounds-and-ve.spec.ts b/tests/anonymous-credentials/presentation-multiple-bounds-and-ve.spec.ts index b5cafbeb..6fae8826 100644 --- a/tests/anonymous-credentials/presentation-multiple-bounds-and-ve.spec.ts +++ b/tests/anonymous-credentials/presentation-multiple-bounds-and-ve.spec.ts @@ -10,7 +10,7 @@ import { SignatureParams } from '../scheme'; import { - BoundCheckProtocols, + BoundCheckProtocol, CredentialSchema, dockSaverEncryptionGens, LegoProvingKeyUncompressed, @@ -23,7 +23,7 @@ import { SaverSecretKey, SaverVerifyingKeyUncompressed, SUBJECT_STR, - VerifiableEncryptionProtocols + VerifiableEncryptionProtocol } from '../../src'; import { checkResult, getBoundCheckSnarkKeys, readByteArrayFromFile, stringToBytes } from '../utils'; import { initializeWasm } from '@docknetwork/crypto-wasm'; @@ -235,12 +235,12 @@ describe(`${Scheme} Presentation creation and verification`, () => { { min: minLat0, max: maxLat0, - protocol: BoundCheckProtocols.Bpp + protocol: BoundCheckProtocol.Bpp }, { min: minLat1, max: maxLat1, - protocol: BoundCheckProtocols.Bpp + protocol: BoundCheckProtocol.Bpp } ], long: [ @@ -248,18 +248,18 @@ describe(`${Scheme} Presentation creation and verification`, () => { min: minLong0, max: maxLong0, paramId: boundCheckSnarkId1, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }, { min: minLong1, max: maxLong1, paramId: boundCheckSnarkId2, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }, { min: minLong2, max: maxLong2, - protocol: BoundCheckProtocols.Bpp + protocol: BoundCheckProtocol.Bpp } ] } @@ -335,14 +335,14 @@ describe(`${Scheme} Presentation creation and verification`, () => { commitmentGensId: commKeyId1, encryptionKeyId: ekId1, snarkKeyId: snarkPkId1, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }, { chunkBitSize, commitmentGensId: commKeyId2, encryptionKeyId: ekId2, snarkKeyId: snarkPkId2, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver } ] } diff --git a/tests/anonymous-credentials/presentation-multiple-sig-types.spec.ts b/tests/anonymous-credentials/presentation-multiple-sig-types.spec.ts index 481b8257..11b54eda 100644 --- a/tests/anonymous-credentials/presentation-multiple-sig-types.spec.ts +++ b/tests/anonymous-credentials/presentation-multiple-sig-types.spec.ts @@ -16,7 +16,7 @@ import { BBSSignatureParams, CredentialSchema, DefaultSchemaParsingOpts, - InequalityProtocols, + InequalityProtocol, META_SCHEMA_STR, PresentationBuilder, PS_SIGNATURE_PARAMS_LABEL_BYTES, @@ -25,7 +25,7 @@ import { PSKeypair, PSPublicKey, PSSecretKey, - PSSignatureParams, SignatureTypes + PSSignatureParams, SignatureType } from '../../src'; import { checkResult, stringToBytes } from '../utils'; import { checkPresentationJson, getExampleSchema } from './utils'; @@ -145,9 +145,9 @@ describe.each([true, false])( expect(pres.spec.credentials.length).toEqual(3); - expect(pres.spec.credentials[0].sigType).toEqual(SignatureTypes.Bbs); - expect(pres.spec.credentials[1].sigType).toEqual(SignatureTypes.BbsPlus); - expect(pres.spec.credentials[2].sigType).toEqual(SignatureTypes.Ps); + expect(pres.spec.credentials[0].sigType).toEqual(SignatureType.Bbs); + expect(pres.spec.credentials[1].sigType).toEqual(SignatureType.BbsPlus); + expect(pres.spec.credentials[2].sigType).toEqual(SignatureType.Ps); expect(pres.spec.credentials[0].revealedAttributes).toEqual({ credentialSubject: { @@ -170,7 +170,7 @@ describe.each([true, false])( expect(pres.spec.credentials[i].attributeInequalities).toEqual({ credentialSubject: { sensitive: { - email: [{ inEqualTo: inEqualEmail, paramId: commKeyId, protocol: InequalityProtocols.Uprove }] + email: [{ inEqualTo: inEqualEmail, paramId: commKeyId, protocol: InequalityProtocol.Uprove }] } } }); diff --git a/tests/anonymous-credentials/presentation.spec.ts b/tests/anonymous-credentials/presentation.spec.ts index 737cd402..54b0414a 100644 --- a/tests/anonymous-credentials/presentation.spec.ts +++ b/tests/anonymous-credentials/presentation.spec.ts @@ -23,8 +23,8 @@ import { SaverProvingKeyUncompressed, SaverSecretKey, SaverVerifyingKeyUncompressed, - BoundCheckProtocols, - VerifiableEncryptionProtocols, + BoundCheckProtocol, + VerifiableEncryptionProtocol, BoundCheckBppParamsUncompressed, BoundCheckSmcParamsUncompressed, BoundCheckBppParams, @@ -34,7 +34,7 @@ import { BoundCheckSmcWithKVSetup, META_SCHEMA_STR, DefaultSchemaParsingOpts, - InequalityProtocols, BoundCheckParamType + InequalityProtocol, BoundCheckParamType } from '../../src'; import { generateRandomFieldElement, initializeWasm } from '@docknetwork/crypto-wasm'; import { @@ -1091,19 +1091,19 @@ describe.each([true, false])( expect(pres.spec.credentials[0].attributeInequalities).toEqual({ credentialSubject: { email: [ - { inEqualTo: inEqualEmail, protocol: InequalityProtocols.Uprove }, - { inEqualTo: inEqualEmail2, protocol: InequalityProtocols.Uprove } + { inEqualTo: inEqualEmail, protocol: InequalityProtocol.Uprove }, + { inEqualTo: inEqualEmail2, protocol: InequalityProtocol.Uprove } ], timeOfBirth: [ - { inEqualTo: inequalTob, protocol: InequalityProtocols.Uprove }, - { inEqualTo: inequalTob2, protocol: InequalityProtocols.Uprove }, - { inEqualTo: inequalTob3, protocol: InequalityProtocols.Uprove } + { inEqualTo: inequalTob, protocol: InequalityProtocol.Uprove }, + { inEqualTo: inequalTob2, protocol: InequalityProtocol.Uprove }, + { inEqualTo: inequalTob3, protocol: InequalityProtocol.Uprove } ], score: [ - { inEqualTo: score, protocol: InequalityProtocols.Uprove }, - { inEqualTo: score2, protocol: InequalityProtocols.Uprove } + { inEqualTo: score, protocol: InequalityProtocol.Uprove }, + { inEqualTo: score2, protocol: InequalityProtocol.Uprove } ], - SSN: [{ inEqualTo: ssn, protocol: InequalityProtocols.Uprove }] + SSN: [{ inEqualTo: ssn, protocol: InequalityProtocol.Uprove }] } }); @@ -1111,22 +1111,22 @@ describe.each([true, false])( credentialSubject: { sensitive: { email: [ - { inEqualTo: inEqualEmail, protocol: InequalityProtocols.Uprove }, - { inEqualTo: inEqualEmail2, protocol: InequalityProtocols.Uprove } + { inEqualTo: inEqualEmail, protocol: InequalityProtocol.Uprove }, + { inEqualTo: inEqualEmail2, protocol: InequalityProtocol.Uprove } ] }, location: { - city: [{ inEqualTo: city, protocol: InequalityProtocols.Uprove }] + city: [{ inEqualTo: city, protocol: InequalityProtocol.Uprove }] }, - isbool: [{ inEqualTo: false, protocol: InequalityProtocols.Uprove }] + isbool: [{ inEqualTo: false, protocol: InequalityProtocol.Uprove }] } }); expect(pres.spec.credentials[2].attributeInequalities).toEqual({ credentialSubject: { myDate: [ - { inEqualTo: '2023-10-15', protocol: InequalityProtocols.Uprove }, - { inEqualTo: new Date('2023-10-16'), protocol: InequalityProtocols.Uprove } + { inEqualTo: '2023-10-15', protocol: InequalityProtocol.Uprove }, + { inEqualTo: new Date('2023-10-16'), protocol: InequalityProtocol.Uprove } ], } }); @@ -1136,7 +1136,7 @@ describe.each([true, false])( checkPresentationJson(pres, [pk1, pk2, pk1]); }); - function checkBounds(protocol: BoundCheckProtocols, paramId?: string, provingParams?: BoundCheckParamType, verifyingParams?: BoundCheckParamType) { + function checkBounds(protocol: BoundCheckProtocol, paramId?: string, provingParams?: BoundCheckParamType, verifyingParams?: BoundCheckParamType) { // ------------------- Presentation with 1 credential ----------------------------------------- console.time(`Proof generation over 1 credential and 3 bound-check in total using ${protocol}`); const builder7 = new PresentationBuilder(); @@ -1344,7 +1344,7 @@ describe.each([true, false])( checkPresentationJson(pres2, [pk1, pk2, pk3], acc, pp1); } - function checkBoundsOnDates(protocol: BoundCheckProtocols, paramId?: string, provingParams?: BoundCheckParamType, verifyingParams?: BoundCheckParamType) { + function checkBoundsOnDates(protocol: BoundCheckProtocol, paramId?: string, provingParams?: BoundCheckParamType, verifyingParams?: BoundCheckParamType) { console.time(`Proof generation over 1 credential and 2 bound-check in total using ${protocol}`); const builder7 = new PresentationBuilder(); expect(builder7.addCredential(credential7, pk1)).toEqual(0); @@ -1405,27 +1405,27 @@ describe.each([true, false])( it('from credentials and proving bounds using LegoGroth16 on attributes as dates', () => { setupBoundCheckLego(); - checkBoundsOnDates(BoundCheckProtocols.Legogroth16, 'random', boundCheckProvingKey, boundCheckVerifyingKey); + checkBoundsOnDates(BoundCheckProtocol.Legogroth16, 'random', boundCheckProvingKey, boundCheckVerifyingKey); }); it('from credentials and proving bounds using Bulletproofs++ on attributes as dates', () => { setupBoundCheckBpp(); - checkBoundsOnDates(BoundCheckProtocols.Bpp, 'random', boundCheckBppParams, boundCheckBppParams); + checkBoundsOnDates(BoundCheckProtocol.Bpp, 'random', boundCheckBppParams, boundCheckBppParams); }); it('from credentials and proving bounds using Bulletproofs++ on attributes as dates and using default setup', () => { - checkBoundsOnDates(BoundCheckProtocols.Bpp); + checkBoundsOnDates(BoundCheckProtocol.Bpp); }); it('from credentials and proving bounds using set-membership check on attributes as dates', () => { setupBoundCheckSmc(); - checkBoundsOnDates(BoundCheckProtocols.Smc, 'random', boundCheckSmcParams, boundCheckSmcParams); + checkBoundsOnDates(BoundCheckProtocol.Smc, 'random', boundCheckSmcParams, boundCheckSmcParams); }); it('from credentials and proving bounds using set-membership check with keyed verification on attributes as dates', () => { setupBoundCheckSmcWithKV(); checkBoundsOnDates( - BoundCheckProtocols.SmcKV, + BoundCheckProtocol.SmcKV, 'random', boundCheckSmcKVProverParams, boundCheckSmcKVVerifierParams, @@ -1434,26 +1434,26 @@ describe.each([true, false])( it('from credentials and proving bounds on attributes using LegorGroth16', () => { setupBoundCheckLego(); - checkBounds(BoundCheckProtocols.Legogroth16, 'random', boundCheckProvingKey, boundCheckVerifyingKey); + checkBounds(BoundCheckProtocol.Legogroth16, 'random', boundCheckProvingKey, boundCheckVerifyingKey); }); it('from credentials and proving bounds on attributes using Bulletproofs++', () => { setupBoundCheckBpp(); - checkBounds(BoundCheckProtocols.Bpp, 'random', boundCheckBppParams, boundCheckBppParams); + checkBounds(BoundCheckProtocol.Bpp, 'random', boundCheckBppParams, boundCheckBppParams); }); it('from credentials and proving bounds on attributes using Bulletproofs++ and using default setup', () => { - checkBounds(BoundCheckProtocols.Bpp); + checkBounds(BoundCheckProtocol.Bpp); }); it('from credentials and proving bounds on attributes using set-membership check', () => { setupBoundCheckSmc(); - checkBounds(BoundCheckProtocols.Smc, 'random', boundCheckSmcParams, boundCheckSmcParams); + checkBounds(BoundCheckProtocol.Smc, 'random', boundCheckSmcParams, boundCheckSmcParams); }); it('from credentials and proving bounds on attributes using set-membership check and keyed-verification', () => { setupBoundCheckSmcWithKV(); - checkBounds(BoundCheckProtocols.SmcKV, 'random', boundCheckSmcKVProverParams, boundCheckSmcKVVerifierParams); + checkBounds(BoundCheckProtocol.SmcKV, 'random', boundCheckSmcKVProverParams, boundCheckSmcKVVerifierParams); }); it('from credentials and encryption of attributes', () => { @@ -1495,7 +1495,7 @@ describe.each([true, false])( commitmentGensId: commKeyId, encryptionKeyId: ekId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }] } }); @@ -1583,7 +1583,7 @@ describe.each([true, false])( commitmentGensId: commKeyId, encryptionKeyId: ekId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }] } }); @@ -1595,7 +1595,7 @@ describe.each([true, false])( commitmentGensId: commKeyId, encryptionKeyId: ekId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }] } } @@ -1773,19 +1773,19 @@ describe.each([true, false])( min: minTime, max: maxTime, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }], BMI: [{ min: minBMI, max: maxBMI, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }], score: [{ min: minScore, max: maxScore, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }] } }); @@ -1796,7 +1796,7 @@ describe.each([true, false])( commitmentGensId: commKeyId, encryptionKeyId: ekId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }] } }); @@ -1812,13 +1812,13 @@ describe.each([true, false])( min: minLat, max: maxLat, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }], long: [{ min: minLong, max: maxLong, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }] } } @@ -1834,7 +1834,7 @@ describe.each([true, false])( commitmentGensId: commKeyId, encryptionKeyId: ekId, snarkKeyId: snarkPkId, - protocol: VerifiableEncryptionProtocols.Saver + protocol: VerifiableEncryptionProtocol.Saver }] } } @@ -1960,13 +1960,13 @@ describe.each([true, false])( min: minLat1, max: maxLat1, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }], long: [{ min: minLong1, max: maxLong1, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }] } } @@ -1978,13 +1978,13 @@ describe.each([true, false])( min: minLat2, max: maxLat2, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }], long: [{ min: minLong2, max: maxLong2, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }] } } @@ -2104,13 +2104,13 @@ describe.each([true, false])( min: minIssuanceDate, max: maxIssuanceDate, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }], expirationDate: [{ min: minExpDate, max: maxExpDate, paramId: boundCheckSnarkId, - protocol: BoundCheckProtocols.Legogroth16 + protocol: BoundCheckProtocol.Legogroth16 }] }); diff --git a/tests/anonymous-credentials/utils.ts b/tests/anonymous-credentials/utils.ts index fd5f9191..6a7a5e43 100644 --- a/tests/anonymous-credentials/utils.ts +++ b/tests/anonymous-credentials/utils.ts @@ -651,17 +651,20 @@ export function getDecodedBoundedPseudonym( * @param accumulatorPublicKeys * @param predicateParams * @param circomOutputs + * @param circomOutputsMultiCred */ export function checkPresentationJson( pres: Presentation, pks: PublicKey[], accumulatorPublicKeys?: Map, predicateParams?: Map, - circomOutputs?: Map + circomOutputs?: Map, + // TODO: Rename + circomOutputsMultiCred?: Uint8Array[][] ): Presentation { const presJson = pres.toJSON(); const recreatedPres = Presentation.fromJSON(presJson); - checkResult(recreatedPres.verify(pks, accumulatorPublicKeys, predicateParams, circomOutputs)); + checkResult(recreatedPres.verify(pks, accumulatorPublicKeys, predicateParams, circomOutputs, undefined, circomOutputsMultiCred)); expect(presJson).toEqual(recreatedPres.toJSON()); return recreatedPres; } diff --git a/tests/circom/comparators.circom b/tests/circom/comparators.circom new file mode 100644 index 00000000..c70075f0 --- /dev/null +++ b/tests/circom/comparators.circom @@ -0,0 +1,117 @@ +/* + Copyright 2018 0KIMS association. + + This file is part of circom (Zero Knowledge Circuit Compiler). + + circom is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + circom is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with circom. If not, see . +*/ +pragma circom 2.0.0; + +template Num2Bits(n) { + signal input in; + signal output out[n]; + var lc1=0; + + var e2=1; + for (var i = 0; i> i) & 1; + out[i] * (out[i] -1 ) === 0; + lc1 += out[i] * e2; + e2 = e2+e2; + } + + lc1 === in; +} + +// `out` signal will be set to 1 if `a < b` else 0. +template LessThan(n) { + assert(n <= 252); + signal input a; + signal input b; + signal output out; + + component n2b = Num2Bits(n+1); + + n2b.in <== a + (1< out; +} + +// N is the number of bits the input have. +// The MSF is the sign bit. +template GreaterThan(n) { + signal input a; + signal input b; + signal output out; + + component lt = LessThan(n); + + lt.a <== b; + lt.b <== a; + lt.out ==> out; +} + +// N is the number of bits the input have. +// The MSF is the sign bit. +template GreaterEqThan(n) { + signal input a; + signal input b; + signal output out; + + component lt = LessThan(n); + + lt.a <== b; + lt.b <== a+1; + lt.out ==> out; +} + +// `out` signal will be set to 1 if `in == 0` else 0. +template IsZero() { + signal input in; + signal output out; + + signal inv; + + inv <-- in!=0 ? 1/in : 0; + + out <== -in*inv +1; + in*out === 0; +} + +// `out` signal will be set to 1 if `a == b` else 0. +template IsEqual() { + signal input a; + signal input b; + signal output out; + + component isz = IsZero(); + + b - a ==> isz.in; + + isz.out ==> out; +}