import { From } from '../../bindings/lib/provable-generic.js'; import { Bool } from '../provable/bool.js'; import { Provable } from '../provable/provable.js'; import { ProvableType } from '../provable/types/provable-intf.js'; import { InferProvable } from '../provable/types/struct.js'; import { mapObject, mapToObject, zip } from '../util/arrays.js'; import { Tuple } from '../util/types.js'; import { Proof } from './proof.js'; import { Undefined, Void } from './zkprogram.js'; export { Recursive }; function Recursive< PublicInputType extends Provable, PublicOutputType extends Provable, PrivateInputs extends { [Key in string]: Tuple; }, >( zkprogram: { name: string; publicInputType: PublicInputType; publicOutputType: PublicOutputType; privateInputTypes: PrivateInputs; rawMethods: { [Key in keyof PrivateInputs]: ( ...args: any ) => Promise<{ publicOutput: InferProvable }>; }; maxProofsVerified: () => Promise<0 | 1 | 2>; } & { [Key in keyof PrivateInputs]: (...args: any) => Promise<{ proof: Proof, InferProvable>; }>; } ): { [Key in keyof PrivateInputs]: RecursiveProver< InferProvable, PublicInputType, InferProvable, PrivateInputs[Key] > & { if: ConditionalRecursiveProver< InferProvable, PublicInputType, InferProvable, PrivateInputs[Key] >; }; } { type PublicInput = InferProvable; type PublicOutput = InferProvable; type MethodKey = keyof PrivateInputs; let { publicInputType, publicOutputType, privateInputTypes: privateInputs, rawMethods: methods, } = zkprogram; let hasPublicInput = publicInputType !== Undefined && publicInputType !== Void; class SelfProof extends Proof { static publicInputType = publicInputType; static publicOutputType = publicOutputType; static tag = () => zkprogram; } let methodKeys: MethodKey[] = Object.keys(methods); let regularRecursiveProvers = mapToObject(methodKeys, (key) => { return async function proveRecursively_( conditionAndConfig: Bool | { condition: Bool; domainLog2?: number }, publicInput: PublicInput, ...args: TupleFrom ): Promise { let condition = conditionAndConfig instanceof Bool ? conditionAndConfig : conditionAndConfig.condition; // create the base proof in a witness block let proof = await Provable.witnessAsync(SelfProof, async () => { // move method args to constants let constInput = Provable.toConstant( publicInputType, publicInputType.fromValue(publicInput) ); let constArgs = zip(args, privateInputs[key]).map(([arg, type]) => Provable.toConstant(type, ProvableType.get(type).fromValue(arg)) ); if (!condition.toBoolean()) { let publicOutput: PublicOutput = ProvableType.synthesize(publicOutputType); let maxProofsVerified = await zkprogram.maxProofsVerified(); return SelfProof.dummy( publicInput, publicOutput, maxProofsVerified, conditionAndConfig instanceof Bool ? undefined : conditionAndConfig.domainLog2 ); } let prover = zkprogram[key]; if (hasPublicInput) { let { proof } = await prover(constInput, ...constArgs); return proof; } else { let { proof } = await prover(...constArgs); return proof; } }); // assert that the witnessed proof has the correct public input (which will be used by Pickles as part of verification) if (hasPublicInput) { Provable.assertEqual(publicInputType, proof.publicInput, publicInput); } // declare and verify the proof, and return its public output proof.declare(); proof.verifyIf(condition); return proof.publicOutput; }; }); return mapObject( regularRecursiveProvers, ( prover ): RecursiveProver & { if: ConditionalRecursiveProver< PublicInput, PublicInputType, PublicOutput, PrivateInputs[MethodKey] >; } => { if (!hasPublicInput) { return Object.assign( ((...args: any) => prover(new Bool(true), undefined as any, ...args)) as any, { if: (condition: Bool | { condition: Bool; domainLog2?: number }, ...args: any) => prover(condition, undefined as any, ...args), } ); } else { return Object.assign( ((pi: PublicInput, ...args: any) => prover(new Bool(true), pi, ...args)) as any, { if: ( condition: Bool | { condition: Bool; domainLog2?: number }, pi: PublicInput, ...args: any ) => prover(condition, pi, ...args), } ); } } ); } type RecursiveProver< PublicInput, PublicInputType, PublicOutput, Args extends Tuple, > = PublicInput extends undefined ? (...args: TupleFrom) => Promise : (publicInput: From, ...args: TupleFrom) => Promise; type ConditionalRecursiveProver< PublicInput, PublicInputType, PublicOutput, Args extends Tuple, > = PublicInput extends undefined ? ( condition: Bool | { condition: Bool; domainLog2?: number }, ...args: TupleFrom ) => Promise : ( condition: Bool | { condition: Bool; domainLog2?: number }, publicInput: From, ...args: TupleFrom ) => Promise; type TupleFrom = { [I in keyof T]: From; };