diff --git a/src/examples/zkprogram/witness-proof-2.ts b/src/examples/zkprogram/witness-proof-2.ts new file mode 100644 index 0000000000..0991bcd375 --- /dev/null +++ b/src/examples/zkprogram/witness-proof-2.ts @@ -0,0 +1,54 @@ +import { + SelfProof, + Field, + ZkProgram, + verify, + isReady, + Proof, + JsonProof, + Provable, + Empty, +} from 'o1js'; + +await isReady; + +let MyProgram = ZkProgram({ + name: 'example-with-output', + publicOutput: Field, + + methods: { + baseCase: { + privateInputs: [], + method() { + return Field(0); + }, + }, + }, +}); + +class ProgramProof extends ZkProgram.Proof(MyProgram) {} + +await MyProgram.compile(); + +let proof = await MyProgram.baseCase(); + +let MyProgram2 = ZkProgram({ + name: 'example-with-output2', + + methods: { + baseCase: { + privateInputs: [ProgramProof], + method(p: ProgramProof) { + p.verify(); + + // should say 0 and fail verification + Provable.log(proof.publicOutput); + }, + }, + }, +}); +console.log('GOING INTO SECOND ZKPROGRAM'); +await MyProgram2.compile(); +console.log('PROVING'); + +await MyProgram2.baseCase(proof); diff --git a/src/examples/zkprogram/witness-proof.ts b/src/examples/zkprogram/witness-proof.ts new file mode 100644 index 0000000000..b36fc89dee --- /dev/null +++ b/src/examples/zkprogram/witness-proof.ts @@ -0,0 +1,58 @@ +import { + SelfProof, + Field, + ZkProgram, + verify, + isReady, + Proof, + JsonProof, + Provable, + Empty, +} from 'o1js'; + +await isReady; + +let MyProgram = ZkProgram({ + name: 'example-with-output', + publicOutput: Field, + + methods: { + baseCase: { + privateInputs: [], + method() { + return Field(0); + }, + }, + }, +}); + +class ProgramProof extends ZkProgram.Proof(MyProgram) {} + +await MyProgram.compile(); + +let proof = await MyProgram.baseCase(); + +let MyProgram2 = ZkProgram({ + name: 'example-with-output2', + + methods: { + baseCase: { + privateInputs: [], + method() { + let p = Provable.witness(ProgramProof.provable, () => { + proof.publicOutput = Field(5); + return proof; + }); + + ProgramProof.declare(p); + p.verify(); + // should say 0 and fail verification + Provable.log(proof.publicOutput); + }, + }, + }, +}); +console.log('GOING INTO SECOND ZKPROGRAM'); +await MyProgram2.compile(); +console.log('PROVING'); +await MyProgram2.baseCase(); diff --git a/src/lib/gadgets/ecdsa.unit-test.ts b/src/lib/gadgets/ecdsa.unit-test.ts index e008a011ef..8cb4124a8e 100644 --- a/src/lib/gadgets/ecdsa.unit-test.ts +++ b/src/lib/gadgets/ecdsa.unit-test.ts @@ -175,7 +175,7 @@ Provable.runAndCheck(program.rawMethods.ecdsa); console.timeEnd('ecdsa verify (witness gen / check)'); console.time('ecdsa verify (build constraint system)'); -let cs = program.analyzeMethods().ecdsa; +let cs = program.analyzeMethods().ecdsa.cs; console.timeEnd('ecdsa verify (build constraint system)'); console.log(cs.summary()); diff --git a/src/lib/proof-system.ts b/src/lib/proof-system.ts index 4682e42a97..d6ef0f33ce 100644 --- a/src/lib/proof-system.ts +++ b/src/lib/proof-system.ts @@ -17,15 +17,25 @@ import { FlexibleProvable, FlexibleProvablePure, InferProvable, + ProvableExtended, ProvablePureExtended, Struct, + Unconstrained, provable, provablePure, toConstant, } from './circuit-value.js'; import { Provable } from './provable.js'; import { assert, prettifyStacktracePromise } from './errors.js'; -import { snarkContext } from './provable-context.js'; +import { + CircuitContext, + ProofContext, + circuitContext, + inAnalyze, + inCompile, + inProver, + snarkContext, +} from './provable-context.js'; import { hashConstant } from './hash.js'; import { MlArray, MlBool, MlResult, MlPair } from './ml/base.js'; import { MlFieldArray, MlFieldConstArray } from './ml/fields.js'; @@ -37,6 +47,7 @@ import { parseHeader, } from './proof-system/prover-keys.js'; import { setSrsCache, unsetSrsCache } from '../bindings/crypto/bindings/srs.js'; +import { provableFromClass } from '../bindings/lib/provable-snarky.js'; // public API export { @@ -54,6 +65,8 @@ export { // internal API export { + isProof, + Subclass, CompiledTag, sortMethodArguments, getPreviousProofsForProver, @@ -90,7 +103,7 @@ class Proof { }; publicInput: Input; publicOutput: Output; - proof: Pickles.Proof; + proof: Unconstrained; maxProofsVerified: 0 | 1 | 2; shouldVerify = Bool(false); @@ -106,7 +119,7 @@ class Proof { publicInput: type.input.toFields(this.publicInput).map(String), publicOutput: type.output.toFields(this.publicOutput).map(String), maxProofsVerified: this.maxProofsVerified, - proof: Pickles.proofToBase64([this.maxProofsVerified, this.proof]), + proof: Pickles.proofToBase64([this.maxProofsVerified, this.proof.get()]), }; } static fromJSON>( @@ -128,7 +141,7 @@ class Proof { return new this({ publicInput, publicOutput, - proof, + proof: Unconstrained.from(proof), maxProofsVerified, }) as any; } @@ -146,7 +159,7 @@ class Proof { }) { this.publicInput = publicInput; this.publicOutput = publicOutput; - this.proof = proof; // TODO optionally convert from string? + this.proof = Unconstrained.from(proof); // TODO optionally convert from string? this.maxProofsVerified = maxProofsVerified; } @@ -327,7 +340,7 @@ function ZkProgram< return Object.fromEntries( methodIntfs.map((methodEntry, i) => [ methodEntry.methodName, - analyzeMethod(publicInputType, methodEntry, methodFunctions[i]), + analyzeMethod(publicInputType, methodEntry, methodFunctions[i]).cs, ]) ) as any as { [I in keyof Types]: ReturnType; @@ -344,6 +357,8 @@ function ZkProgram< } | undefined; + let witnessedProofs: ProofContext[][] = []; + async function compile({ cache = Cache.FileSystemDefault, forceRecompile = false, @@ -351,7 +366,9 @@ function ZkProgram< let methodsMeta = methodIntfs.map((methodEntry, i) => analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) ); - let gates = methodsMeta.map((m) => m.gates); + witnessedProofs = methodsMeta.map((m) => m.proofData); + console.log('Witnessed proofs in compile', witnessedProofs); + let gates = methodsMeta.map((m) => m.cs.gates); let { provers, verify, verificationKey } = await compileProgram({ publicInputType, publicOutputType, @@ -362,6 +379,7 @@ function ZkProgram< cache, forceRecompile, overrideWrapDomain: config.overrideWrapDomain, + witnessedProofs, }); compileOutput = { provers, verify }; return { verificationKey }; @@ -383,8 +401,14 @@ function ZkProgram< ); } let publicInputFields = toFieldConsts(publicInputType, publicInput); + + console.log( + 'CONTEXT', + witnessedProofs[0].map((p) => console.log(p)) + ); + let previousProofs = MlArray.to( - getPreviousProofsForProver(args, methodIntfs[i]) + getPreviousProofsForProver(args, methodIntfs[i], witnessedProofs[i]) ); let id = snarkContext.enter({ witnesses: args, inProver: true }); @@ -438,8 +462,9 @@ function ZkProgram< } function digest() { - let methodData = methodIntfs.map((methodEntry, i) => - analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) + let methodData = methodIntfs.map( + (methodEntry, i) => + analyzeMethod(publicInputType, methodEntry, methodFunctions[i]).cs ); let hash = hashConstant( Object.values(methodData).map((d) => Field(BigInt('0x' + d.digest))) @@ -563,8 +588,11 @@ function isProof(type: unknown): type is typeof Proof { function getPreviousProofsForProver( methodArgs: any[], - { allArgs }: MethodInterface + { allArgs }: MethodInterface, + witnessedProofs: ProofContext[] ) { + console.log('ARGS', allArgs); + console.log('PREVIOUS POROOF', methodArgs); let previousProofs: Pickles.Proof[] = []; for (let i = 0; i < allArgs.length; i++) { let arg = allArgs[i]; @@ -572,6 +600,13 @@ function getPreviousProofsForProver( previousProofs[arg.index] = (methodArgs[i] as Proof).proof; } } + console.log('witnessedProofs POROOF', witnessedProofs[0]); + + for (let i = 0; i < witnessedProofs.length; i++) { + console.log('Unconstrained proof', witnessedProofs[i].proof); + previousProofs.push(witnessedProofs[i].proof as Unconstrained); + } + return previousProofs; } @@ -597,6 +632,7 @@ async function compileProgram({ proofSystemTag, cache, forceRecompile, + witnessedProofs, overrideWrapDomain, }: { publicInputType: ProvablePure; @@ -607,6 +643,7 @@ async function compileProgram({ proofSystemTag: { name: string }; cache: Cache; forceRecompile: boolean; + witnessedProofs: ProofContext[][]; overrideWrapDomain?: 0 | 1 | 2; }) { let rules = methodIntfs.map((methodEntry, i) => @@ -616,9 +653,11 @@ async function compileProgram({ methods[i], proofSystemTag, methodEntry, - gates[i] + gates[i], + witnessedProofs[i] ) ); + let maxProofs = getMaxProofsVerified(methodIntfs); overrideWrapDomain ??= maxProofsToWrapDomain[maxProofs]; @@ -703,13 +742,24 @@ function analyzeMethod( methodIntf: MethodInterface, method: (...args: any) => T ) { - return Provable.constraintSystem(() => { + console.log('ANALYZING METHOD, ENTERING CONTEXT'); + let { context } = CircuitContext.enter(methodIntf.methodName); + let id = circuitContext.enter(context); + + let cs = Provable.constraintSystem(() => { let args = synthesizeMethodArguments(methodIntf, true); + let publicInput = emptyWitness(publicInputType); if (publicInputType === Undefined || publicInputType === Void) return method(...args); return method(publicInput, ...args); }); + // TODO moose + console.log(context.proofs); + console.log(' analyzed methods, dopne'); + let proofData = context.proofs; + circuitContext.leave(id); + return { cs, proofData }; } function picklesRuleFromFunction( @@ -718,7 +768,8 @@ function picklesRuleFromFunction( func: (...args: unknown[]) => any, proofSystemTag: { name: string }, { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface, - gates: Gate[] + gates: Gate[], + witnessedProofs: ProofContext[] ): Pickles.Rule { function main(publicInput: MlFieldArray): ReturnType { let { witnesses: argsWithoutPublicInput, inProver } = snarkContext.get(); @@ -729,12 +780,16 @@ function picklesRuleFromFunction( for (let i = 0; i < allArgs.length; i++) { let arg = allArgs[i]; if (arg.type === 'witness') { + console.log('witness', arg); let type = witnessArgs[arg.index]; finalArgs[i] = Provable.witness(type, () => { return argsWithoutPublicInput?.[i] ?? emptyValue(type); }); } else if (arg.type === 'proof') { + console.log('proof', arg); + let Proof = proofArgs[arg.index]; + Provable.log(Proof); let type = getStatementType(Proof); let proof_ = (argsWithoutPublicInput?.[i] as Proof) ?? { proof: undefined, @@ -744,6 +799,8 @@ function picklesRuleFromFunction( let { proof, publicInput, publicOutput } = proof_; publicInput = Provable.witness(type.input, () => publicInput); publicOutput = Provable.witness(type.output, () => publicOutput); + Provable.log(publicInput); + Provable.log(publicOutput); let proofInstance = new Proof({ publicInput, publicOutput, proof }); finalArgs[i] = proofInstance; proofs.push(proofInstance); @@ -752,6 +809,42 @@ function picklesRuleFromFunction( previousStatements.push(MlPair(input, output)); } } + + for (let i = 0; i < witnessedProofs.length; i++) { + console.log('WE ARE WITNESSING A PROOF'); + + let proofContext = witnessedProofs[i]; + console.log('proofContext', proofContext.proof); + let ProofClass = proofContext.proofClass; + let type = getStatementType(proofContext.proofClass); + Provable.log(Proof); + + let { publicInput: input_, publicOutput: output_ } = proofContext; + Provable.log(input_); + console.log('---------'); + let proof_ = { + proof: undefined, + publicInput: emptyValue(type.input), + publicOutput: emptyValue(type.output), + }; + let { proof, publicInput, publicOutput } = proof_; + Provable.log(publicInput); + Provable.log(publicOutput); + publicInput = Provable.witness(type.input, () => publicInput); + publicOutput = Provable.witness(type.output, () => publicOutput); + let proofInstance = new ProofClass({ + publicInput, + publicOutput, + proof, + }); + proofInstance.shouldVerify = Bool(true); + //finalArgs[i] = proofInstance; + proofs.push(proofInstance); + let input = toFieldVars(type.input, publicInput); + let output = toFieldVars(type.output, publicOutput); + previousStatements.push(MlPair(input, output)); + } + let result: any; if (publicInputType === Undefined || publicInputType === Void) { result = func(...finalArgs); @@ -762,6 +855,7 @@ function picklesRuleFromFunction( // if the public output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case let hasPublicOutput = publicOutputType.sizeInFields() !== 0; let publicOutput = hasPublicOutput ? publicOutputType.toFields(result) : []; + console.log('------ COMPILE DONE ------'); return { publicOutput: MlFieldArray.to(publicOutput), previousStatements: MlArray.to(previousStatements), @@ -940,6 +1034,46 @@ ZkProgram.Proof = function < static publicInputType = program.publicInputType; static publicOutputType = program.publicOutputType; static tag = () => program; + + static declare(proof: Proof) { + console.log('inProver', inProver()); + + let ctx = circuitContext.get()!; + + console.log('declaring', { + proof: proof.proof, + publicInput: proof.publicInput, + publicOutput: proof.publicOutput, + maxProofsVerified: proof.maxProofsVerified, + proofClass: this, + shouldVerify: proof.shouldVerify, + }); + ctx.proofs.push({ + proof: proof.proof, + publicInput: proof.publicInput, + publicOutput: proof.publicOutput, + maxProofsVerified: proof.maxProofsVerified, + proofClass: this, + shouldVerify: proof.shouldVerify, + }); + } + + static get provable() { + // ^? + + return provableFromClass(ZkProgramProof, { + publicInput: program.publicInputType, + publicOutput: program.publicOutputType, + proof: Unconstrained.provable, + }) satisfies ProvableExtended; + } + static witness( + cb: () => Proof + ): Proof { + let p = Provable.witness(this.provable, cb); + + return cb(); + } }; }; ExperimentalZkProgram.Proof = ZkProgram.Proof; diff --git a/src/lib/proof-system.unit-test.ts b/src/lib/proof-system.unit-test.ts index d9572e54d4..35f471b777 100644 --- a/src/lib/proof-system.unit-test.ts +++ b/src/lib/proof-system.unit-test.ts @@ -61,6 +61,7 @@ it('pickles rule creation', async () => { main as AnyFunction, { name: 'mock' }, methodIntf, + [], [] ); diff --git a/src/lib/provable-context.ts b/src/lib/provable-context.ts index 9779fa2ab1..ec5c1a660a 100644 --- a/src/lib/provable-context.ts +++ b/src/lib/provable-context.ts @@ -1,11 +1,17 @@ import { Context } from './global-context.js'; -import { Gate, GateType, JsonGate, Snarky } from '../snarky.js'; +import { Gate, GateType, JsonGate, ProvablePure, Snarky } from '../snarky.js'; import { parseHexString32 } from '../bindings/crypto/bigint-helpers.js'; import { prettifyStacktrace } from './errors.js'; import { Fp } from '../bindings/crypto/finite-field.js'; +import { FlexibleProvablePure } from './circuit-value.js'; +import { Proof, Subclass } from './proof-system.js'; +import { Bool } from './bool.js'; // internal API export { + ProofContext, + circuitContext, + CircuitContext, snarkContext, SnarkContext, asProver, @@ -23,6 +29,29 @@ export { // global circuit-related context +type ProofContext = { + proofClass: Subclass; + publicInput: ProvablePure; + publicOutput: ProvablePure; + proof: unknown; + shouldVerify: Bool; + maxProofsVerified: number; +}; +// context that observes and collects meta data about circuits and their methods +type CircuitContext = { + methodName: string; + proofs: ProofContext[]; +}; +let circuitContext = Context.create({}); + +const CircuitContext = { + enter(methodName: string) { + let context: CircuitContext = { methodName, proofs: [] }; + let id = circuitContext.enter(context); + return { id, context }; + }, +}; + type SnarkContext = { witnesses?: unknown[]; proverData?: any; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index de5386fa21..421bf81389 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -285,7 +285,8 @@ function wrapMethod( // proofs actually don't have to be cloned previousProofs: getPreviousProofsForProver( actualArgs, - methodIntf + methodIntf, + [] ), ZkappClass, memoized, @@ -390,7 +391,8 @@ function wrapMethod( args: constantArgs, previousProofs: getPreviousProofsForProver( constantArgs, - methodIntf + methodIntf, + [] ), ZkappClass, memoized, @@ -607,6 +609,7 @@ class SmartContract extends SmartContractBase { proofSystemTag: this, cache, forceRecompile, + witnessedProofs: [], }); this._provers = provers; this._verificationKey = verificationKey; @@ -1111,7 +1114,7 @@ super.init(); accountUpdate = instance.#executionState!.accountUpdate; return result; } - ); + ).cs; methodMetadata[methodIntf.methodName] = { actions: accountUpdate!.body.actions.data.length, rows,