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,