From af307e19ab3d0d96e210aabf27bce1ec32cf31ce Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 3 Oct 2024 15:54:43 +0200 Subject: [PATCH 01/14] simplification 1: don't store proofs in separate list --- src/lib/mina/zkapp.ts | 2 +- src/lib/proof-system/zkprogram.ts | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lib/mina/zkapp.ts b/src/lib/mina/zkapp.ts index 7c6402164..0e1283d01 100644 --- a/src/lib/mina/zkapp.ts +++ b/src/lib/mina/zkapp.ts @@ -156,7 +156,7 @@ function method( ZkappClass._maxProofsVerified ??= 0; ZkappClass._maxProofsVerified = Math.max( ZkappClass._maxProofsVerified, - methodEntry.proofs.length + methodEntry.numberOfProofs ) as 0 | 1 | 2; let func = descriptor.value as AsyncFunction; descriptor.value = wrapMethod(func, ZkappClass, internalMethodEntry); diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index dea019650..f35716994 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -486,7 +486,7 @@ function sortMethodArguments( return { type: input, isProof: isProof(input) }; }); - // store proofs separately as well + // extract proofs for sanity checks let proofs: Subclass[] = privateInputs.filter(isProof); // don't allow base classes for proofs @@ -506,7 +506,7 @@ function sortMethodArguments( `Suggestion: You can merge more than two proofs by merging two at a time in a binary tree.` ); } - return { methodName, args, proofs }; + return { methodName, args, numberOfProofs: proofs.length }; } function isProvable(type: unknown): type is ProvableType { @@ -549,7 +549,7 @@ function getPreviousProofsForProver( type MethodInterface = { methodName: string; args: { type: ProvableType; isProof: boolean }[]; - proofs: Subclass[]; + numberOfProofs: number; returnType?: Provable; }; @@ -710,7 +710,7 @@ function picklesRuleFromFunction( publicOutputType: ProvablePure, func: (...args: unknown[]) => unknown, proofSystemTag: { name: string }, - { methodName, args, proofs: proofArgs }: MethodInterface, + { methodName, args }: MethodInterface, gates: Gate[] ): Pickles.Rule { async function main( @@ -793,13 +793,14 @@ function picklesRuleFromFunction( }; } - if (proofArgs.length > 2) { + let proofs: Subclass[] = extractProofs(args); + if (proofs.length > 2) { throw Error( `${proofSystemTag.name}.${methodName}() has more than two proof arguments, which is not supported.\n` + `Suggestion: You can merge more than two proofs by merging two at a time in a binary tree.` ); } - let proofsToVerify = proofArgs.map((Proof) => { + let proofsToVerify = proofs.map((Proof) => { let tag = Proof.tag(); if (tag === proofSystemTag) return { isSelf: true as const }; else if (isDynamicProof(Proof)) { @@ -840,6 +841,13 @@ function picklesRuleFromFunction( }; } +function extractProofs( + args: MethodInterface['args'] +): Subclass[] { + let types: unknown[] = args.map((a) => a.type); + return types.filter(isProof); +} + function synthesizeMethodArguments(intf: MethodInterface, asVariables = false) { let empty = asVariables ? emptyWitness : emptyValue; return intf.args.map(({ type }) => empty(type)); @@ -871,7 +879,7 @@ function emptyWitness(type: ProvableType) { function getMaxProofsVerified(methodIntfs: MethodInterface[]) { return methodIntfs.reduce( - (acc, { proofs }) => Math.max(acc, proofs.length), + (acc, { numberOfProofs }) => Math.max(acc, numberOfProofs), 0 ) as any as 0 | 1 | 2; } From 8037bb0e284ebe6da54f7ab5cbd7c9e7535110bd Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 3 Oct 2024 16:04:47 +0200 Subject: [PATCH 02/14] simplification 2: remove isProof flag --- src/lib/mina/zkapp.ts | 2 +- src/lib/proof-system/zkprogram.ts | 56 ++++++++++++++----------------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/lib/mina/zkapp.ts b/src/lib/mina/zkapp.ts index 0e1283d01..fda5051e4 100644 --- a/src/lib/mina/zkapp.ts +++ b/src/lib/mina/zkapp.ts @@ -313,7 +313,7 @@ function wrapMethod( method.apply( this, actualArgs.map((a, i) => { - return Provable.witness(methodIntf.args[i].type, () => a); + return Provable.witness(methodIntf.args[i], () => a); }) ), noPromiseError diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index f35716994..32b0f20fb 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -448,8 +448,6 @@ type ZkProgram< } > = ReturnType>; -let i = 0; - class SelfProof extends Proof< PublicInput, PublicOutput @@ -473,21 +471,20 @@ function sortMethodArguments( input === SelfProof ? selfProof : input ); - // check if all arguments are provable, and record which are proofs - let args: { type: ProvableType; isProof: boolean }[] = - privateInputs.map((input) => { - if (!isProvable(input)) { - throw Error( - `Argument ${ - i + 1 - } of method ${methodName} is not a provable type: ${input}` - ); - } - return { type: input, isProof: isProof(input) }; - }); + // check if all arguments are provable + let args: ProvableType[] = privateInputs.map((input, i) => { + if (isProvable(input)) return input; - // extract proofs for sanity checks - let proofs: Subclass[] = privateInputs.filter(isProof); + throw Error( + `Argument ${ + i + 1 + } of method ${methodName} is not a provable type: ${input}` + ); + }); + + // extract proofs to count them and for sanity checks + let proofs = extractProofs(args); + let numberOfProofs = proofs.length; // don't allow base classes for proofs proofs.forEach((proof) => { @@ -500,13 +497,13 @@ function sortMethodArguments( }); // don't allow more than 2 proofs - if (proofs.length > 2) { + if (numberOfProofs > 2) { throw Error( `${programName}.${methodName}() has more than two proof arguments, which is not supported.\n` + `Suggestion: You can merge more than two proofs by merging two at a time in a binary tree.` ); } - return { methodName, args, numberOfProofs: proofs.length }; + return { methodName, args, numberOfProofs }; } function isProvable(type: unknown): type is ProvableType { @@ -541,14 +538,14 @@ function getPreviousProofsForProver( ) { let proofs: ProofBase[] = []; for (let i = 0; i < args.length; i++) { - if (args[i].isProof) proofs.push(methodArgs[i].proof); + if (isProof(args[i])) proofs.push(methodArgs[i].proof); } return proofs; } type MethodInterface = { methodName: string; - args: { type: ProvableType; isProof: boolean }[]; + args: ProvableType[]; numberOfProofs: number; returnType?: Provable; }; @@ -725,14 +722,14 @@ function picklesRuleFromFunction( }[] = []; let previousStatements: Pickles.Statement[] = []; for (let i = 0; i < args.length; i++) { - let { type, isProof } = args[i]; + let type = args[i]; try { let value = Provable.witness(type, () => { return argsWithoutPublicInput?.[i] ?? emptyValue(type); }); finalArgs[i] = value; - if (isProof) { - let Proof = type as Subclass>; + if (isProof(type)) { + let Proof = type satisfies Subclass>; let proof = value as ProofBase; proofs.push({ proofInstance: proof, classReference: Proof }); let fields = proof.publicFields(); @@ -841,26 +838,23 @@ function picklesRuleFromFunction( }; } -function extractProofs( - args: MethodInterface['args'] -): Subclass[] { - let types: unknown[] = args.map((a) => a.type); - return types.filter(isProof); +function extractProofs(args: unknown[]): Subclass[] { + return args.filter(isProof); } function synthesizeMethodArguments(intf: MethodInterface, asVariables = false) { let empty = asVariables ? emptyWitness : emptyValue; - return intf.args.map(({ type }) => empty(type)); + return intf.args.map((type) => empty(type)); } function methodArgumentsToConstant(intf: MethodInterface, args: any[]) { - return intf.args.map(({ type }, i) => Provable.toConstant(type, args[i])); + return intf.args.map((type, i) => Provable.toConstant(type, args[i])); } type TypeAndValue = { type: Provable; value: T }; function methodArgumentTypesAndValues(intf: MethodInterface, args: unknown[]) { - return intf.args.map(({ type }, i): TypeAndValue => { + return intf.args.map((type, i): TypeAndValue => { return { type: ProvableType.get(type), value: args[i] }; }); } From 29e14486165e7783e7565da01781a0d7d9b23c47 Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 3 Oct 2024 17:31:00 +0200 Subject: [PATCH 03/14] unit tests --- src/lib/proof-system/proof-system.unit-test.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/proof-system/proof-system.unit-test.ts b/src/lib/proof-system/proof-system.unit-test.ts index a868d9fa1..ccbc898dd 100644 --- a/src/lib/proof-system/proof-system.unit-test.ts +++ b/src/lib/proof-system/proof-system.unit-test.ts @@ -44,11 +44,8 @@ it('pickles rule creation', async () => { expect(methodIntf).toEqual({ methodName: 'main', - args: [ - { type: EmptyProof, isProof: true }, - { type: Bool, isProof: false }, - ], - proofs: [EmptyProof], + args: [EmptyProof, Bool], + numberOfProofs: 1, }); // store compiled tag From c661f692522cf243eb7dce808c390dc81e4cba0a Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 3 Oct 2024 21:06:44 +0200 Subject: [PATCH 04/14] we can filter proofs from values, without types --- src/lib/mina/zkapp.ts | 10 ++------- src/lib/proof-system/zkprogram.ts | 35 ++++++++++++++----------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/lib/mina/zkapp.ts b/src/lib/mina/zkapp.ts index fda5051e4..f4acd2208 100644 --- a/src/lib/mina/zkapp.ts +++ b/src/lib/mina/zkapp.ts @@ -341,10 +341,7 @@ function wrapMethod( methodName: methodIntf.methodName, args: clonedArgs, // proofs actually don't have to be cloned - previousProofs: getPreviousProofsForProver( - actualArgs, - methodIntf - ), + previousProofs: getPreviousProofsForProver(actualArgs), ZkappClass, memoized, blindingValue, @@ -433,10 +430,7 @@ function wrapMethod( { methodName: methodIntf.methodName, args: constantArgs, - previousProofs: getPreviousProofsForProver( - constantArgs, - methodIntf - ), + previousProofs: getPreviousProofsForProver(constantArgs), ZkappClass, memoized, blindingValue: constantBlindingValue, diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 32b0f20fb..ab2b0ea04 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -322,9 +322,7 @@ function ZkProgram< } if (!doProving) { - let previousProofs = MlArray.to( - getPreviousProofsForProver(args, methodIntfs[i]) - ); + let previousProofs = MlArray.to(getPreviousProofsForProver(args)); let publicOutput = await (methods[key].method as any)( publicInput, @@ -342,9 +340,7 @@ function ZkProgram< ); } let publicInputFields = toFieldConsts(publicInputType, publicInput); - let previousProofs = MlArray.to( - getPreviousProofsForProver(args, methodIntfs[i]) - ); + let previousProofs = MlArray.to(getPreviousProofsForProver(args)); let id = snarkContext.enter({ witnesses: args, inProver: true }); let result: UnwrapPromise>; @@ -483,7 +479,7 @@ function sortMethodArguments( }); // extract proofs to count them and for sanity checks - let proofs = extractProofs(args); + let proofs = extractProofTypes(args); let numberOfProofs = proofs.length; // don't allow base classes for proofs @@ -517,7 +513,7 @@ function isProvable(type: unknown): type is ProvableType { ); } -function isProof(type: unknown): type is typeof ProofBase { +function isProofType(type: unknown): type is typeof ProofBase { // the third case covers subclasses return ( type === Proof || @@ -526,19 +522,20 @@ function isProof(type: unknown): type is typeof ProofBase { ); } +function isProof(value: unknown): value is ProofBase { + return value instanceof ProofBase; +} + function isDynamicProof( type: Subclass ): type is Subclass { return typeof type === 'function' && type.prototype instanceof DynamicProof; } -function getPreviousProofsForProver( - methodArgs: any[], - { args }: MethodInterface -) { - let proofs: ProofBase[] = []; - for (let i = 0; i < args.length; i++) { - if (isProof(args[i])) proofs.push(methodArgs[i].proof); +function getPreviousProofsForProver(methodArgs: any[]) { + let proofs: unknown[] = []; + for (let arg of methodArgs) { + if (isProof(arg)) proofs.push(arg.proof); } return proofs; } @@ -728,7 +725,7 @@ function picklesRuleFromFunction( return argsWithoutPublicInput?.[i] ?? emptyValue(type); }); finalArgs[i] = value; - if (isProof(type)) { + if (isProofType(type)) { let Proof = type satisfies Subclass>; let proof = value as ProofBase; proofs.push({ proofInstance: proof, classReference: Proof }); @@ -790,7 +787,7 @@ function picklesRuleFromFunction( }; } - let proofs: Subclass[] = extractProofs(args); + let proofs: Subclass[] = extractProofTypes(args); if (proofs.length > 2) { throw Error( `${proofSystemTag.name}.${methodName}() has more than two proof arguments, which is not supported.\n` + @@ -838,8 +835,8 @@ function picklesRuleFromFunction( }; } -function extractProofs(args: unknown[]): Subclass[] { - return args.filter(isProof); +function extractProofTypes(args: unknown[]): Subclass[] { + return args.filter(isProofType); } function synthesizeMethodArguments(intf: MethodInterface, asVariables = false) { From 05e4c26e96122d4a78815290a84195bd822a329f Mon Sep 17 00:00:00 2001 From: Gregor Date: Thu, 3 Oct 2024 21:36:07 +0200 Subject: [PATCH 05/14] recursively extract proofs --- src/lib/proof-system/proof.ts | 46 ++++++++++++++++++++++++++++++- src/lib/proof-system/zkprogram.ts | 42 ++++++++-------------------- src/lib/provable/option.ts | 2 +- src/lib/provable/types/util.ts | 17 ++++++++++++ 4 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 src/lib/provable/types/util.ts diff --git a/src/lib/proof-system/proof.ts b/src/lib/proof-system/proof.ts index 57cac9a41..52dcf2164 100644 --- a/src/lib/proof-system/proof.ts +++ b/src/lib/proof-system/proof.ts @@ -10,12 +10,21 @@ import type { VerificationKey, JsonProof } from './zkprogram.js'; import { Subclass } from '../util/types.js'; import type { Provable } from '../provable/provable.js'; import { assert } from '../util/assert.js'; +import { Unconstrained } from '../provable/types/unconstrained.js'; +import { ProvableType } from '../provable/types/provable-intf.js'; +import { emptyValue } from '../provable/types/util.js'; // public API export { ProofBase, Proof, DynamicProof }; // internal API -export { dummyProof }; +export { + dummyProof, + extractProofs, + extractProofsFromArray, + extractProofTypes, + extractProofTypesFromArray, +}; type MaxProofs = 0 | 1 | 2; @@ -386,3 +395,38 @@ function provableProof< }, }; } + +function extractProofs(value: unknown): ProofBase[] { + if (value instanceof ProofBase) { + return [value]; + } + if (value instanceof Unconstrained) return []; + if (value instanceof Field) return []; + if (value instanceof Bool) return []; + + if (Array.isArray(value)) { + return value.flatMap((item) => extractProofs(item)); + } + + if (value === null) return []; + if (typeof value === 'object') { + return extractProofs(Object.values(value)); + } + + // functions, primitives + return []; +} + +function extractProofsFromArray(value: unknown[]) { + return value.flatMap(extractProofs); +} + +function extractProofTypes(type: ProvableType) { + let value = emptyValue(type); + let proofValues = extractProofs(value); + return proofValues.map((proof) => proof.constructor as typeof ProofBase); +} + +function extractProofTypesFromArray(type: ProvableType[]) { + return type.flatMap(extractProofTypes); +} diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index ab2b0ea04..5bb3f6298 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -40,11 +40,19 @@ import { import { prefixToField } from '../../bindings/lib/binable.js'; import { prefixes } from '../../bindings/crypto/constants.js'; import { Subclass, Tuple } from '../util/types.js'; -import { dummyProof, DynamicProof, Proof, ProofBase } from './proof.js'; +import { + dummyProof, + DynamicProof, + extractProofsFromArray, + extractProofTypesFromArray, + Proof, + ProofBase, +} from './proof.js'; import { featureFlagsFromGates, featureFlagsToMlOption, } from './feature-flags.js'; +import { emptyValue, emptyWitness } from '../provable/types/util.js'; // public API export { @@ -67,8 +75,6 @@ export { picklesRuleFromFunction, compileProgram, analyzeMethod, - emptyValue, - emptyWitness, synthesizeMethodArguments, methodArgumentsToConstant, methodArgumentTypesAndValues, @@ -479,7 +485,7 @@ function sortMethodArguments( }); // extract proofs to count them and for sanity checks - let proofs = extractProofTypes(args); + let proofs = extractProofTypesFromArray(args); let numberOfProofs = proofs.length; // don't allow base classes for proofs @@ -522,10 +528,6 @@ function isProofType(type: unknown): type is typeof ProofBase { ); } -function isProof(value: unknown): value is ProofBase { - return value instanceof ProofBase; -} - function isDynamicProof( type: Subclass ): type is Subclass { @@ -533,11 +535,7 @@ function isDynamicProof( } function getPreviousProofsForProver(methodArgs: any[]) { - let proofs: unknown[] = []; - for (let arg of methodArgs) { - if (isProof(arg)) proofs.push(arg.proof); - } - return proofs; + return extractProofsFromArray(methodArgs).map((proof) => proof.proof); } type MethodInterface = { @@ -787,7 +785,7 @@ function picklesRuleFromFunction( }; } - let proofs: Subclass[] = extractProofTypes(args); + let proofs: Subclass[] = extractProofTypesFromArray(args); if (proofs.length > 2) { throw Error( `${proofSystemTag.name}.${methodName}() has more than two proof arguments, which is not supported.\n` + @@ -835,10 +833,6 @@ function picklesRuleFromFunction( }; } -function extractProofTypes(args: unknown[]): Subclass[] { - return args.filter(isProofType); -} - function synthesizeMethodArguments(intf: MethodInterface, asVariables = false) { let empty = asVariables ? emptyWitness : emptyValue; return intf.args.map((type) => empty(type)); @@ -856,18 +850,6 @@ function methodArgumentTypesAndValues(intf: MethodInterface, args: unknown[]) { }); } -function emptyValue(type: ProvableType) { - let provable = ProvableType.get(type); - return provable.fromFields( - Array(provable.sizeInFields()).fill(Field(0)), - provable.toAuxiliary() - ); -} - -function emptyWitness(type: ProvableType) { - return Provable.witness(type, () => emptyValue(type)); -} - function getMaxProofsVerified(methodIntfs: MethodInterface[]) { return methodIntfs.reduce( (acc, { numberOfProofs }) => Math.max(acc, numberOfProofs), diff --git a/src/lib/provable/option.ts b/src/lib/provable/option.ts index 86a71fb52..ad8898164 100644 --- a/src/lib/provable/option.ts +++ b/src/lib/provable/option.ts @@ -1,5 +1,5 @@ import { InferValue } from '../../bindings/lib/provable-generic.js'; -import { emptyValue } from '../proof-system/zkprogram.js'; +import { emptyValue } from './types/util.js'; import { Provable } from './provable.js'; import { InferProvable, Struct } from './types/struct.js'; import { provable, ProvableInferPureFrom } from './types/provable-derivers.js'; diff --git a/src/lib/provable/types/util.ts b/src/lib/provable/types/util.ts new file mode 100644 index 000000000..b05d9bf31 --- /dev/null +++ b/src/lib/provable/types/util.ts @@ -0,0 +1,17 @@ +import { createField } from '../core/field-constructor.js'; +import { ProvableType } from './provable-intf.js'; +import { witness } from './witness.js'; + +export { emptyValue, emptyWitness }; + +function emptyValue(type: ProvableType) { + let provable = ProvableType.get(type); + return provable.fromFields( + Array(provable.sizeInFields()).fill(createField(0)), + provable.toAuxiliary() + ); +} + +function emptyWitness(type: ProvableType) { + return witness(type, () => emptyValue(type)); +} From 0f9525fdf39731d3ad4c98a49552bd16daa557f1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 11:07:29 +0200 Subject: [PATCH 06/14] extract nested proof inputs in pickles rule --- src/lib/proof-system/zkprogram.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 5bb3f6298..5a72c7447 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -43,6 +43,7 @@ import { Subclass, Tuple } from '../util/types.js'; import { dummyProof, DynamicProof, + extractProofs, extractProofsFromArray, extractProofTypesFromArray, Proof, @@ -469,6 +470,7 @@ function sortMethodArguments( selfProof: Subclass ): MethodInterface { // replace SelfProof with the actual selfProof + // TODO this does not handle SelfProof nested in inputs privateInputs = privateInputs.map((input) => input === SelfProof ? selfProof : input ); @@ -712,8 +714,8 @@ function picklesRuleFromFunction( assert(!(inProver && argsWithoutPublicInput === undefined)); let finalArgs = []; let proofs: { - proofInstance: ProofBase; - classReference: Subclass>; + Proof: Subclass>; + proof: ProofBase; }[] = []; let previousStatements: Pickles.Statement[] = []; for (let i = 0; i < args.length; i++) { @@ -723,10 +725,10 @@ function picklesRuleFromFunction( return argsWithoutPublicInput?.[i] ?? emptyValue(type); }); finalArgs[i] = value; - if (isProofType(type)) { - let Proof = type satisfies Subclass>; - let proof = value as ProofBase; - proofs.push({ proofInstance: proof, classReference: Proof }); + + for (let proof of extractProofs(value)) { + let Proof = proof.constructor as Subclass>; + proofs.push({ Proof, proof }); let fields = proof.publicFields(); let input = MlFieldArray.to(fields.input); let output = MlFieldArray.to(fields.output); @@ -745,13 +747,13 @@ function picklesRuleFromFunction( result = await func(input, ...finalArgs); } - proofs.forEach(({ proofInstance, classReference }) => { - if (!(proofInstance instanceof DynamicProof)) return; + proofs.forEach(({ Proof, proof }) => { + if (!(proof instanceof DynamicProof)) return; // Initialize side-loaded verification key - const tag = classReference.tag(); + const tag = Proof.tag(); const computedTag = SideloadedTag.get(tag.name); - const vk = proofInstance.usedVerificationKey; + const vk = proof.usedVerificationKey; if (vk === undefined) { throw new Error( @@ -780,7 +782,7 @@ function picklesRuleFromFunction( publicOutput: MlFieldArray.to(publicOutput), previousStatements: MlArray.to(previousStatements), shouldVerify: MlArray.to( - proofs.map((proof) => proof.proofInstance.shouldVerify.toField().value) + proofs.map((proof) => proof.proof.shouldVerify.toField().value) ), }; } From 9d4a17e515e5f14e79f7b99e68f0d5e8ee454d57 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 11:18:18 +0200 Subject: [PATCH 07/14] unit test for nested proof extraction --- .../proof-system/proof-system.unit-test.ts | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/lib/proof-system/proof-system.unit-test.ts b/src/lib/proof-system/proof-system.unit-test.ts index ccbc898dd..c7125ec92 100644 --- a/src/lib/proof-system/proof-system.unit-test.ts +++ b/src/lib/proof-system/proof-system.unit-test.ts @@ -17,7 +17,8 @@ import { it } from 'node:test'; import { Provable } from '../provable/provable.js'; import { bool, equivalentAsync, field, record } from '../testing/equivalent.js'; import { FieldVar, FieldConst } from '../provable/core/fieldvar.js'; -import { ProvablePure } from '../provable/types/provable-intf.js'; +import { ProvablePure, ProvableType } from '../provable/types/provable-intf.js'; +import { sample } from '../testing/random.js'; const EmptyProgram = ZkProgram({ name: 'empty', @@ -27,6 +28,12 @@ const EmptyProgram = ZkProgram({ class EmptyProof extends ZkProgram.Proof(EmptyProgram) {} +class NestedProof extends Struct({ + proof: EmptyProof as any as ProvableType>, + field: Field, +}) {} +const NestedProof2 = Provable.Array(NestedProof, 2); + // unit-test zkprogram creation helpers: // -) sortMethodArguments // -) picklesRuleFromFunction @@ -96,6 +103,60 @@ it('pickles rule creation', async () => { ); }); +it('pickles rule creation: nested proof', async () => { + function main(nested: [NestedProof, NestedProof]) { + // first proof should verify, second should not + nested[0].proof.verify(); + } + + // collect method interface + let methodIntf = sortMethodArguments('mock', 'main', [NestedProof2], Proof); + + expect(methodIntf).toEqual({ + methodName: 'main', + args: [NestedProof2], + numberOfProofs: 2, + }); + + // store compiled tag + CompiledTag.store(EmptyProgram, 'mock tag'); + + // create pickles rule + let rule: Pickles.Rule = picklesRuleFromFunction( + Empty as ProvablePure, + Field as ProvablePure, + main as AnyFunction, + { name: 'mock' }, + methodIntf, + [] + ); + + let dummy = await EmptyProof.dummy(Field(0), undefined, 0); + let nested1 = new NestedProof({ proof: dummy, field: Field(0) }); + let nested2 = new NestedProof({ proof: dummy, field: Field(0) }); + let nested = [nested1, nested2]; + + await Provable.runAndCheck(async () => { + // put witnesses in snark context + snarkContext.get().witnesses = [nested]; + + // call pickles rule + let { + shouldVerify: [, shouldVerify1, shouldVerify2], + previousStatements: [, ...previousStatements], + } = await rule.main([0]); + + expect(previousStatements.length).toBe(2); + + // `shouldVerify` are as expected + expect(Bool(shouldVerify1).isConstant()).toBe(true); + expect(Bool(shouldVerify2).isConstant()).toBe(true); + // first proof should verify, second should not + Bool(shouldVerify1).assertTrue(); + Bool(shouldVerify2).assertFalse(); + }); +}); + // compile works with large inputs const N = 100_000; From 27b07d5d20e0d24bb5634a176c3e96abf06b60a2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 11:33:27 +0200 Subject: [PATCH 08/14] test recursive proving (negative + positive) --- .../proof-system/proof-system.unit-test.ts | 5 +-- src/tests/fake-proof.ts | 35 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/lib/proof-system/proof-system.unit-test.ts b/src/lib/proof-system/proof-system.unit-test.ts index c7125ec92..2e22b0620 100644 --- a/src/lib/proof-system/proof-system.unit-test.ts +++ b/src/lib/proof-system/proof-system.unit-test.ts @@ -4,6 +4,7 @@ import { UInt64 } from '../provable/int.js'; import { CompiledTag, Empty, + Void, ZkProgram, picklesRuleFromFunction, sortMethodArguments, @@ -18,7 +19,6 @@ import { Provable } from '../provable/provable.js'; import { bool, equivalentAsync, field, record } from '../testing/equivalent.js'; import { FieldVar, FieldConst } from '../provable/core/fieldvar.js'; import { ProvablePure, ProvableType } from '../provable/types/provable-intf.js'; -import { sample } from '../testing/random.js'; const EmptyProgram = ZkProgram({ name: 'empty', @@ -29,6 +29,7 @@ const EmptyProgram = ZkProgram({ class EmptyProof extends ZkProgram.Proof(EmptyProgram) {} class NestedProof extends Struct({ + // TODO this coercion should not be necessary proof: EmptyProof as any as ProvableType>, field: Field, }) {} @@ -124,7 +125,7 @@ it('pickles rule creation: nested proof', async () => { // create pickles rule let rule: Pickles.Rule = picklesRuleFromFunction( Empty as ProvablePure, - Field as ProvablePure, + Void as ProvablePure, main as AnyFunction, { name: 'mock' }, methodIntf, diff --git a/src/tests/fake-proof.ts b/src/tests/fake-proof.ts index e1c3ebc23..527e7120f 100644 --- a/src/tests/fake-proof.ts +++ b/src/tests/fake-proof.ts @@ -6,6 +6,8 @@ import { method, ZkProgram, verify, + Struct, + Field, } from 'o1js'; import assert from 'assert'; @@ -32,7 +34,7 @@ const FakeProgram = ZkProgram({ class RealProof extends ZkProgram.Proof(RealProgram) {} const RecursiveProgram = ZkProgram({ - name: 'broken', + name: 'recursive', methods: { verifyReal: { privateInputs: [RealProof], @@ -40,6 +42,12 @@ const RecursiveProgram = ZkProgram({ proof.verify(); }, }, + verifyNested: { + privateInputs: [Field, Struct({ inner: RealProof })], + async method(_unrelated, { inner }: { inner: RealProof }) { + inner.verify(); + }, + }, }, }); @@ -79,9 +87,9 @@ for (let proof of [fakeProof, dummyProof]) { const realProof = await RealProgram.make(UInt64.from(34)); // zkprogram accepts proof -const brokenProof = await RecursiveProgram.verifyReal(realProof); +const recursiveProof = await RecursiveProgram.verifyReal(realProof); assert( - await verify(brokenProof, programVk.data), + await verify(recursiveProof, programVk), 'recursive program accepts real proof' ); @@ -89,8 +97,27 @@ assert( let tx = await Mina.transaction(() => zkApp.verifyReal(realProof)); let [contractProof] = (await tx.prove()).proofs; assert( - await verify(contractProof!, contractVk.data), + await verify(contractProof!, contractVk), 'recursive contract accepts real proof' ); console.log('fake proof test passed 🎉'); + +// same test for nested proofs + +for (let proof of [fakeProof, dummyProof]) { + // zkprogram rejects proof (nested) + await assert.rejects(async () => { + await RecursiveProgram.verifyNested(Field(0), { inner: proof }); + }, 'recursive program rejects fake proof (nested)'); +} + +const recursiveProofNested = await RecursiveProgram.verifyNested(Field(0), { + inner: realProof, +}); +assert( + await verify(recursiveProofNested, programVk), + 'recursive program accepts real proof (nested)' +); + +console.log('fake proof test passed for nested proofs 🎉'); From cb09374737c60d2b7929234decbe5d0f51109747 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 12:23:00 +0200 Subject: [PATCH 09/14] achieve proper intellisense --- .../proof-system/proof-system.unit-test.ts | 23 ++++++----- src/lib/proof-system/proof.ts | 40 +++++++++++++------ src/lib/proof-system/zkprogram.ts | 16 ++++++-- src/tests/fake-proof.ts | 7 +++- 4 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/lib/proof-system/proof-system.unit-test.ts b/src/lib/proof-system/proof-system.unit-test.ts index 2e22b0620..3d97bff19 100644 --- a/src/lib/proof-system/proof-system.unit-test.ts +++ b/src/lib/proof-system/proof-system.unit-test.ts @@ -18,7 +18,7 @@ import { it } from 'node:test'; import { Provable } from '../provable/provable.js'; import { bool, equivalentAsync, field, record } from '../testing/equivalent.js'; import { FieldVar, FieldConst } from '../provable/core/fieldvar.js'; -import { ProvablePure, ProvableType } from '../provable/types/provable-intf.js'; +import { ProvablePure } from '../provable/types/provable-intf.js'; const EmptyProgram = ZkProgram({ name: 'empty', @@ -28,13 +28,6 @@ const EmptyProgram = ZkProgram({ class EmptyProof extends ZkProgram.Proof(EmptyProgram) {} -class NestedProof extends Struct({ - // TODO this coercion should not be necessary - proof: EmptyProof as any as ProvableType>, - field: Field, -}) {} -const NestedProof2 = Provable.Array(NestedProof, 2); - // unit-test zkprogram creation helpers: // -) sortMethodArguments // -) picklesRuleFromFunction @@ -104,10 +97,20 @@ it('pickles rule creation', async () => { ); }); +class NestedProof extends Struct({ proof: EmptyProof, field: Field }) {} +const NestedProof2 = Provable.Array(NestedProof, 2); + +// type inference +NestedProof satisfies Provable<{ proof: Proof; field: Field }>; + it('pickles rule creation: nested proof', async () => { - function main(nested: [NestedProof, NestedProof]) { + function main([first, _second]: [NestedProof, NestedProof]) { // first proof should verify, second should not - nested[0].proof.verify(); + first.proof.verify(); + + // deep type inference + first.proof.publicInput satisfies Field; + first.proof.publicOutput satisfies void; } // collect method interface diff --git a/src/lib/proof-system/proof.ts b/src/lib/proof-system/proof.ts index 52dcf2164..c722b7af7 100644 --- a/src/lib/proof-system/proof.ts +++ b/src/lib/proof-system/proof.ts @@ -24,6 +24,7 @@ export { extractProofsFromArray, extractProofTypes, extractProofTypesFromArray, + type ProofValue, }; type MaxProofs = 0 | 1 | 2; @@ -70,7 +71,7 @@ class ProofBase { this.maxProofsVerified = maxProofsVerified; } - static get provable() { + static get provable(): Provable { if ( this.publicInputType === undefined || this.publicOutputType === undefined @@ -80,7 +81,7 @@ class ProofBase { `class MyProof extends Proof { ... }` ); } - return provableProof( + return provableProof( this, this.publicInputType, this.publicOutputType, @@ -168,6 +169,10 @@ class Proof extends ProofBase { maxProofsVerified, }); } + + static get provable(): ProvableProof> { + return super.provable as any; + } } let sideloadedKeysCounter = 0; @@ -308,6 +313,10 @@ class DynamicProof extends ProofBase { proof: proof.proof, }) as InstanceType; } + + static get provable(): ProvableProof> { + return super.provable as any; + } } async function dummyProof(maxProofsVerified: 0 | 1 | 2, domainLog2: number) { @@ -317,10 +326,23 @@ async function dummyProof(maxProofsVerified: 0 | 1 | 2, domainLog2: number) { ); } +type ProofValue = { + publicInput: Input; + publicOutput: Output; + proof: Pickles.Proof; + maxProofsVerified: 0 | 1 | 2; +}; + +type ProvableProof< + Proof extends ProofBase, + InputV = any, + OutputV = any +> = Provable>; + function provableProof< Class extends Subclass>, - Input, - Output, + Input = any, + Output = any, InputV = any, OutputV = any >( @@ -328,15 +350,7 @@ function provableProof< input: Provable, output: Provable, defaultMaxProofsVerified?: MaxProofs -): Provable< - ProofBase, - { - publicInput: InputV; - publicOutput: OutputV; - proof: unknown; - maxProofsVerified: MaxProofs; - } -> { +): Provable, ProofValue> { return { sizeInFields() { return input.sizeInFields() + output.sizeInFields(); diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 5a72c7447..156244a80 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -48,12 +48,14 @@ import { extractProofTypesFromArray, Proof, ProofBase, + ProofValue, } from './proof.js'; import { featureFlagsFromGates, featureFlagsToMlOption, } from './feature-flags.js'; import { emptyValue, emptyWitness } from '../provable/types/util.js'; +import { InferValue } from '../../bindings/lib/provable-generic.js'; // public API export { @@ -877,10 +879,16 @@ ZkProgram.Proof = function < name: string; publicInputType: PublicInputType; publicOutputType: PublicOutputType; -}) { - type PublicInput = InferProvable; - type PublicOutput = InferProvable; - return class ZkProgramProof extends Proof { +}): typeof Proof< + InferProvable, + InferProvable +> & { + provable: Provable< + Proof, InferProvable>, + ProofValue, InferValue> + >; +} { + return class ZkProgramProof extends Proof { static publicInputType = program.publicInputType; static publicOutputType = program.publicOutputType; static tag = () => program; diff --git a/src/tests/fake-proof.ts b/src/tests/fake-proof.ts index 527e7120f..ffe23a6d7 100644 --- a/src/tests/fake-proof.ts +++ b/src/tests/fake-proof.ts @@ -8,6 +8,7 @@ import { verify, Struct, Field, + Proof, } from 'o1js'; import assert from 'assert'; @@ -32,6 +33,7 @@ const FakeProgram = ZkProgram({ }); class RealProof extends ZkProgram.Proof(RealProgram) {} +const Nested = Struct({ inner: RealProof }); const RecursiveProgram = ZkProgram({ name: 'recursive', @@ -43,8 +45,9 @@ const RecursiveProgram = ZkProgram({ }, }, verifyNested: { - privateInputs: [Field, Struct({ inner: RealProof })], - async method(_unrelated, { inner }: { inner: RealProof }) { + privateInputs: [Field, Nested], + async method(_unrelated, { inner }) { + inner satisfies Proof; inner.verify(); }, }, From 0df8bcd5581bff5aacaaa565a9364fb7c4831f66 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 12:30:07 +0200 Subject: [PATCH 10/14] negative test for > max proofs --- src/lib/proof-system/proof-system.unit-test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/proof-system/proof-system.unit-test.ts b/src/lib/proof-system/proof-system.unit-test.ts index 3d97bff19..03988d2cd 100644 --- a/src/lib/proof-system/proof-system.unit-test.ts +++ b/src/lib/proof-system/proof-system.unit-test.ts @@ -161,6 +161,12 @@ it('pickles rule creation: nested proof', async () => { }); }); +it('fails with more than two (nested) proofs', async () => { + expect(() => { + sortMethodArguments('mock', 'main', [NestedProof2, NestedProof], Proof); + }).toThrowError('mock.main() has more than two proof arguments'); +}); + // compile works with large inputs const N = 100_000; From 055aab3118697ae46d8b01e404a76d333c397b20 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 12:47:13 +0200 Subject: [PATCH 11/14] expose extremely useful utility method --- src/lib/proof-system/proof.ts | 3 +-- src/lib/proof-system/zkprogram.ts | 12 +++--------- src/lib/provable/option.ts | 11 ++++++++--- src/lib/provable/types/provable-intf.ts | 11 +++++++++++ src/lib/provable/types/util.ts | 13 ++----------- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/lib/proof-system/proof.ts b/src/lib/proof-system/proof.ts index c722b7af7..7b5bd453f 100644 --- a/src/lib/proof-system/proof.ts +++ b/src/lib/proof-system/proof.ts @@ -12,7 +12,6 @@ import type { Provable } from '../provable/provable.js'; import { assert } from '../util/assert.js'; import { Unconstrained } from '../provable/types/unconstrained.js'; import { ProvableType } from '../provable/types/provable-intf.js'; -import { emptyValue } from '../provable/types/util.js'; // public API export { ProofBase, Proof, DynamicProof }; @@ -436,7 +435,7 @@ function extractProofsFromArray(value: unknown[]) { } function extractProofTypes(type: ProvableType) { - let value = emptyValue(type); + let value = ProvableType.synthesize(type); let proofValues = extractProofs(value); return proofValues.map((proof) => proof.constructor as typeof ProofBase); } diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 156244a80..c8c0b8ac2 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -54,7 +54,7 @@ import { featureFlagsFromGates, featureFlagsToMlOption, } from './feature-flags.js'; -import { emptyValue, emptyWitness } from '../provable/types/util.js'; +import { emptyWitness } from '../provable/types/util.js'; import { InferValue } from '../../bindings/lib/provable-generic.js'; // public API @@ -78,7 +78,6 @@ export { picklesRuleFromFunction, compileProgram, analyzeMethod, - synthesizeMethodArguments, methodArgumentsToConstant, methodArgumentTypesAndValues, Prover, @@ -679,7 +678,7 @@ function analyzeMethod( method: (...args: any) => unknown ) { return Provable.constraintSystem(() => { - let args = synthesizeMethodArguments(methodIntf, true); + let args = methodIntf.args.map(emptyWitness); let publicInput = emptyWitness(publicInputType); // note: returning the method result here makes this handle async methods if (publicInputType === Undefined || publicInputType === Void) @@ -724,7 +723,7 @@ function picklesRuleFromFunction( let type = args[i]; try { let value = Provable.witness(type, () => { - return argsWithoutPublicInput?.[i] ?? emptyValue(type); + return argsWithoutPublicInput?.[i] ?? ProvableType.synthesize(type); }); finalArgs[i] = value; @@ -837,11 +836,6 @@ function picklesRuleFromFunction( }; } -function synthesizeMethodArguments(intf: MethodInterface, asVariables = false) { - let empty = asVariables ? emptyWitness : emptyValue; - return intf.args.map((type) => empty(type)); -} - function methodArgumentsToConstant(intf: MethodInterface, args: any[]) { return intf.args.map((type, i) => Provable.toConstant(type, args[i])); } diff --git a/src/lib/provable/option.ts b/src/lib/provable/option.ts index ad8898164..75d34b4a6 100644 --- a/src/lib/provable/option.ts +++ b/src/lib/provable/option.ts @@ -1,5 +1,4 @@ import { InferValue } from '../../bindings/lib/provable-generic.js'; -import { emptyValue } from './types/util.js'; import { Provable } from './provable.js'; import { InferProvable, Struct } from './types/struct.js'; import { provable, ProvableInferPureFrom } from './types/provable-derivers.js'; @@ -77,7 +76,10 @@ function Option( fromValue(value: OptionOrValue) { if (value === undefined) - return { isSome: Bool(false), value: emptyValue(strictType) }; + return { + isSome: Bool(false), + value: ProvableType.synthesize(strictType), + }; // TODO: this isn't 100% robust. We would need recursive type validation on any nested objects to make it work if (typeof value === 'object' && 'isSome' in value) return PlainOption.fromValue(value as any); // type-cast here is ok, matches implementation @@ -103,7 +105,10 @@ function Option( static from(value?: V | T) { return value === undefined - ? new Option_({ isSome: Bool(false), value: emptyValue(strictType) }) + ? new Option_({ + isSome: Bool(false), + value: ProvableType.synthesize(strictType), + }) : new Option_({ isSome: Bool(true), value: strictType.fromValue(value), diff --git a/src/lib/provable/types/provable-intf.ts b/src/lib/provable/types/provable-intf.ts index 0a1b58201..a3e4bc418 100644 --- a/src/lib/provable/types/provable-intf.ts +++ b/src/lib/provable/types/provable-intf.ts @@ -1,3 +1,4 @@ +import { createField } from '../core/field-constructor.js'; import type { Field } from '../field.js'; export { @@ -129,4 +130,14 @@ const ProvableType = { : type ) as ToProvable; }, + /** + * Create some value of type `T` from its provable type description. + */ + synthesize(type: ProvableType): T { + let provable = ProvableType.get(type); + return provable.fromFields( + Array(provable.sizeInFields()).fill(createField(0)), + provable.toAuxiliary() + ); + }, }; diff --git a/src/lib/provable/types/util.ts b/src/lib/provable/types/util.ts index b05d9bf31..432b89674 100644 --- a/src/lib/provable/types/util.ts +++ b/src/lib/provable/types/util.ts @@ -1,17 +1,8 @@ -import { createField } from '../core/field-constructor.js'; import { ProvableType } from './provable-intf.js'; import { witness } from './witness.js'; -export { emptyValue, emptyWitness }; - -function emptyValue(type: ProvableType) { - let provable = ProvableType.get(type); - return provable.fromFields( - Array(provable.sizeInFields()).fill(createField(0)), - provable.toAuxiliary() - ); -} +export { emptyWitness }; function emptyWitness(type: ProvableType) { - return witness(type, () => emptyValue(type)); + return witness(type, () => ProvableType.synthesize(type)); } From 8bfbaab88f17a4018dbce69cc3af8d0d00692797 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 12:53:57 +0200 Subject: [PATCH 12/14] remove some mini-abstractions that are only used once --- src/lib/mina/zkapp.ts | 10 ++++++---- src/lib/proof-system/proof.ts | 17 +---------------- src/lib/proof-system/zkprogram.ts | 23 ++++------------------- 3 files changed, 11 insertions(+), 39 deletions(-) diff --git a/src/lib/mina/zkapp.ts b/src/lib/mina/zkapp.ts index f4acd2208..49a1ea577 100644 --- a/src/lib/mina/zkapp.ts +++ b/src/lib/mina/zkapp.ts @@ -46,8 +46,6 @@ import { compileProgram, Empty, getPreviousProofsForProver, - methodArgumentsToConstant, - methodArgumentTypesAndValues, MethodInterface, sortMethodArguments, } from '../proof-system/zkprogram.js'; @@ -383,7 +381,9 @@ function wrapMethod( let blindingValue = getBlindingValue(); let runCalledContract = async () => { - let constantArgs = methodArgumentsToConstant(methodIntf, actualArgs); + let constantArgs = methodIntf.args.map((type, i) => + Provable.toConstant(type, actualArgs[i]) + ); let constantBlindingValue = blindingValue.toConstant(); let accountUpdate = this.self; accountUpdate.body.callDepth = parentAccountUpdate.body.callDepth + 1; @@ -520,7 +520,9 @@ function computeCallData( blindingValue: Field ) { let { returnType, methodName } = methodIntf; - let args = methodArgumentTypesAndValues(methodIntf, argumentValues); + let args = methodIntf.args.map((type, i) => { + return { type: ProvableType.get(type), value: argumentValues[i] }; + }); let input: HashInput = { fields: [], packed: [] }; for (let { type, value } of args) { diff --git a/src/lib/proof-system/proof.ts b/src/lib/proof-system/proof.ts index 7b5bd453f..9111482cf 100644 --- a/src/lib/proof-system/proof.ts +++ b/src/lib/proof-system/proof.ts @@ -17,14 +17,7 @@ import { ProvableType } from '../provable/types/provable-intf.js'; export { ProofBase, Proof, DynamicProof }; // internal API -export { - dummyProof, - extractProofs, - extractProofsFromArray, - extractProofTypes, - extractProofTypesFromArray, - type ProofValue, -}; +export { dummyProof, extractProofs, extractProofTypes, type ProofValue }; type MaxProofs = 0 | 1 | 2; @@ -430,16 +423,8 @@ function extractProofs(value: unknown): ProofBase[] { return []; } -function extractProofsFromArray(value: unknown[]) { - return value.flatMap(extractProofs); -} - function extractProofTypes(type: ProvableType) { let value = ProvableType.synthesize(type); let proofValues = extractProofs(value); return proofValues.map((proof) => proof.constructor as typeof ProofBase); } - -function extractProofTypesFromArray(type: ProvableType[]) { - return type.flatMap(extractProofTypes); -} diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index c8c0b8ac2..f3ea7e322 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -44,8 +44,7 @@ import { dummyProof, DynamicProof, extractProofs, - extractProofsFromArray, - extractProofTypesFromArray, + extractProofTypes, Proof, ProofBase, ProofValue, @@ -78,8 +77,6 @@ export { picklesRuleFromFunction, compileProgram, analyzeMethod, - methodArgumentsToConstant, - methodArgumentTypesAndValues, Prover, dummyBase64Proof, }; @@ -488,7 +485,7 @@ function sortMethodArguments( }); // extract proofs to count them and for sanity checks - let proofs = extractProofTypesFromArray(args); + let proofs = args.flatMap(extractProofTypes); let numberOfProofs = proofs.length; // don't allow base classes for proofs @@ -538,7 +535,7 @@ function isDynamicProof( } function getPreviousProofsForProver(methodArgs: any[]) { - return extractProofsFromArray(methodArgs).map((proof) => proof.proof); + return methodArgs.flatMap(extractProofs).map((proof) => proof.proof); } type MethodInterface = { @@ -788,7 +785,7 @@ function picklesRuleFromFunction( }; } - let proofs: Subclass[] = extractProofTypesFromArray(args); + let proofs: Subclass[] = args.flatMap(extractProofTypes); if (proofs.length > 2) { throw Error( `${proofSystemTag.name}.${methodName}() has more than two proof arguments, which is not supported.\n` + @@ -836,18 +833,6 @@ function picklesRuleFromFunction( }; } -function methodArgumentsToConstant(intf: MethodInterface, args: any[]) { - return intf.args.map((type, i) => Provable.toConstant(type, args[i])); -} - -type TypeAndValue = { type: Provable; value: T }; - -function methodArgumentTypesAndValues(intf: MethodInterface, args: unknown[]) { - return intf.args.map((type, i): TypeAndValue => { - return { type: ProvableType.get(type), value: args[i] }; - }); -} - function getMaxProofsVerified(methodIntfs: MethodInterface[]) { return methodIntfs.reduce( (acc, { numberOfProofs }) => Math.max(acc, numberOfProofs), From 480cfc2b090b559528f17389153d29da3b919c15 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 12:54:33 +0200 Subject: [PATCH 13/14] unrelated: enable detecting struct at runtime --- src/lib/provable/types/struct.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/provable/types/struct.ts b/src/lib/provable/types/struct.ts index 34d808647..44249d937 100644 --- a/src/lib/provable/types/struct.ts +++ b/src/lib/provable/types/struct.ts @@ -159,7 +159,7 @@ function Struct< } { class Struct_ { static type = provable(type); - static _isStruct: true; + static _isStruct: true = true; constructor(value: T) { Object.assign(this, value); From 3616cc8a5b1ff1a90b04ad5e54cbecf68ec3a3f1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 4 Oct 2024 13:01:32 +0200 Subject: [PATCH 14/14] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8c149a48..3e1c2c058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/450943...HEAD) +### Changed + +- Make `Proof` a normal provable type, that can be witnessed and composed into Structs https://github.com/o1-labs/o1js/pull/1847, https://github.com/o1-labs/o1js/pull/1851 + - ZkProgram and SmartContract now also support private inputs that are not proofs themselves, but contain proofs nested within a Struct or array + - Only `SelfProof` can still not be nested because it needs special treatment + ## [1.8.0](https://github.com/o1-labs/o1js/compare/5006e4f...450943) - 2024-09-18 ### Added