From 51d6a76f767f5bb8f8beb559a98c3239c7212372 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 18 Apr 2023 14:31:53 +0200 Subject: [PATCH 1/6] handle proof public inputs in js --- src/bindings | 2 +- src/lib/account_update.ts | 6 +--- src/lib/proof_system.ts | 61 ++++++++++++++++----------------------- src/snarky.d.ts | 12 ++++---- 4 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/bindings b/src/bindings index 55232ab61..77e9f4413 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 55232ab6167fa7ce318c160a1aa4168832117260 +Subproject commit 77e9f4413e10afe372d31a265dfc10af025acc6a diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index f818e60f4..fa6bd79da 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -573,11 +573,7 @@ type LazyProof = { kind: 'lazy-proof'; methodName: string; args: any[]; - previousProofs: { - publicInput: Field[]; - publicOutput: Field[]; - proof: Pickles.Proof; - }[]; + previousProofs: Pickles.Proof[]; ZkappClass: typeof SmartContract; memoized: { fields: Field[]; aux: any[] }[]; blindingValue: Field; diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 04c2a43ab..702d8a2e7 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -79,7 +79,7 @@ class Proof { }; publicInput: Input; publicOutput: Output; - proof: RawProof; + proof: Pickles.Proof; maxProofsVerified: 0 | 1 | 2; shouldVerify = Bool(false); @@ -128,7 +128,7 @@ class Proof { publicOutput, maxProofsVerified, }: { - proof: RawProof; + proof: Pickles.Proof; publicInput: Input; publicOutput: Output; maxProofsVerified: 0 | 1 | 2; @@ -144,7 +144,7 @@ async function verify( proof: Proof | JsonProof, verificationKey: string ) { - let picklesProof: unknown; + let picklesProof: Pickles.Proof; let statement: Pickles.Statement; if (typeof proof.proof === 'string') { // json proof @@ -170,7 +170,6 @@ async function verify( ); } -type RawProof = unknown; type JsonProof = { publicInput: string[]; publicOutput: string[]; @@ -259,7 +258,7 @@ function ZkProgram< provers: Pickles.Prover[]; verify: ( statement: Pickles.Statement, - proof: unknown + proof: Pickles.Proof ) => Promise; } | undefined; @@ -474,22 +473,13 @@ function isGeneric(type: unknown): type is typeof GenericArgument { function getPreviousProofsForProver( methodArgs: any[], - { allArgs, proofArgs }: MethodInterface + { allArgs }: MethodInterface ) { - let previousProofs: Pickles.ProofWithStatement[] = []; + let previousProofs: Pickles.Proof[] = []; for (let i = 0; i < allArgs.length; i++) { let arg = allArgs[i]; if (arg.type === 'proof') { - let { proof, publicInput, publicOutput } = methodArgs[i] as Proof< - any, - any - >; - let type = getStatementType(proofArgs[arg.index]); - previousProofs[arg.index] = { - publicInput: type.input.toFields(publicInput), - publicOutput: type.output.toFields(publicOutput), - proof, - }; + previousProofs[arg.index] = (methodArgs[i] as Proof).proof; } } return previousProofs; @@ -538,10 +528,10 @@ async function compileProgram( ); // wrap provers let wrappedProvers = provers.map( - (prover) => + (prover): Pickles.Prover => async function picklesProver( publicInput: Field[], - previousProofs: Pickles.ProofWithStatement[] + previousProofs: Pickles.Proof[] ) { return withThreadPool(() => prover(publicInput, previousProofs)); } @@ -582,38 +572,36 @@ function picklesRuleFromFunction( proofSystemTag: { name: string }, { methodName, witnessArgs, proofArgs, allArgs }: MethodInterface ): Pickles.Rule { - function main( - publicInput: Field[], - previousInputsAndOutputs: Field[][] - ): ReturnType { + function main(publicInput: Field[]): ReturnType { let { witnesses: argsWithoutPublicInput } = snarkContext.get(); let finalArgs = []; let proofs: Proof[] = []; + let previousStatements: Pickles.Statement[] = []; for (let i = 0; i < allArgs.length; i++) { let arg = allArgs[i]; if (arg.type === 'witness') { let type = witnessArgs[arg.index]; finalArgs[i] = argsWithoutPublicInput - ? Circuit.witness(type, () => argsWithoutPublicInput![i]) + ? Circuit.witness(type, () => argsWithoutPublicInput?.[i]) : emptyWitness(type); } else if (arg.type === 'proof') { let Proof = proofArgs[arg.index]; - // split in input & output - // TODO: would be nice to either make Pickles return the right split or completely move - // getting the public input & outputs to the JS side, from `snarkContext`, like private inputs let type = getStatementType(Proof); - let previousStatement = previousInputsAndOutputs[arg.index]; - let inputFields = previousStatement.slice(0, type.input.sizeInFields()); - let outputFields = previousStatement.slice(type.input.sizeInFields()); - let publicInput = type.input.fromFields(inputFields); - let publicOutput = type.output.fromFields(outputFields); - let proof: unknown; - if (argsWithoutPublicInput) { - ({ proof } = argsWithoutPublicInput[i] as any); - } + let proof_ = (argsWithoutPublicInput?.[i] as Proof) ?? { + proof: undefined, + publicInput: emptyValue(type.input), + publicOutput: emptyValue(type.output), + }; + let { proof, publicInput, publicOutput } = proof_; + publicInput = Circuit.witness(type.input, () => publicInput); + publicOutput = Circuit.witness(type.output, () => publicOutput); let proofInstance = new Proof({ publicInput, publicOutput, proof }); finalArgs[i] = proofInstance; proofs.push(proofInstance); + previousStatements.push({ + input: type.input.toFields(publicInput), + output: type.output.toFields(publicOutput), + }); } else if (arg.type === 'generic') { finalArgs[i] = argsWithoutPublicInput?.[i] ?? emptyGeneric(); } @@ -629,6 +617,7 @@ function picklesRuleFromFunction( let publicOutput = hasPublicOutput ? publicOutputType.toFields(result) : []; return { publicOutput, + previousStatements, shouldVerify: proofs.map((proof) => proof.shouldVerify), }; } diff --git a/src/snarky.d.ts b/src/snarky.d.ts index 0832981fd..ca2937cb5 100644 --- a/src/snarky.d.ts +++ b/src/snarky.d.ts @@ -1301,16 +1301,16 @@ declare namespace Pickles { }; type Rule = { identifier: string; - main: ( - publicInput: Field[], - // TODO: these are flat Field arrays which have to be split into input & output - previousInputsAndOutputs: Field[][] - ) => { publicOutput: Field[]; shouldVerify: Bool[] }; + main: (publicInput: Field[]) => { + publicOutput: Field[]; + previousStatements: Statement[]; + shouldVerify: Bool[]; + }; proofsToVerify: ({ isSelf: true } | { isSelf: false; tag: unknown })[]; }; type Prover = ( publicInput: Field[], - previousProofs: ProofWithStatement[] + previousProofs: Proof[] ) => Promise<{ publicOutput: Field[]; proof: Proof }>; } From 6661bcf2323da3194eb52dea96859eb92aa0866f Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 5 May 2023 11:59:35 +0200 Subject: [PATCH 2/6] changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 070e3bad8..0d8d2f153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/bcc666f2...HEAD) -> No unreleased changes yet +### Changes + +- Allow ZkPrograms to return their public output https://github.com/o1-labs/snarkyjs/pull/874 https://github.com/o1-labs/snarkyjs/pull/876 + - new option `ZkProgram({ publicOutput?: Provable, ... })`; `publicOutput` has to match the _return type_ of all ZkProgram methods. + - the `publicInput` option becomes optional; if not provided, methods no longer expect the public input as first argument + - full usage example: https://github.com/o1-labs/snarkyjs/blob/f95cf2903e97292df9e703b74ee1fc3825df826d/src/examples/program.ts ## [0.10.0](https://github.com/o1-labs/snarkyjs/compare/97e393ed...bcc666f2) From 8e4527e76f7ee55645a2770dba27218805216eb4 Mon Sep 17 00:00:00 2001 From: Gregor Date: Fri, 5 May 2023 16:12:07 +0200 Subject: [PATCH 3/6] use Empty / undefined as the empty provable --- src/bindings | 2 +- src/examples/program.ts | 5 ++-- .../zkapps/simple_zkapp_with_proof.ts | 3 ++- src/index.ts | 1 + src/lib/account_update.ts | 7 +++--- src/lib/mina.ts | 4 ++-- src/lib/proof_system.ts | 23 +++++++++++++++---- src/lib/zkapp.ts | 12 +++++----- 8 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/bindings b/src/bindings index a35838297..b1b9b7038 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit a35838297f1d8ae69a14ca1cb2718cf8fd953565 +Subproject commit b1b9b703816176eea487884d394dd4916757325c diff --git a/src/examples/program.ts b/src/examples/program.ts index 76fd144cf..7c364e572 100644 --- a/src/examples/program.ts +++ b/src/examples/program.ts @@ -7,6 +7,7 @@ import { Proof, JsonProof, Provable, + Empty, } from 'snarkyjs'; await isReady; @@ -24,7 +25,7 @@ let MyProgram = Experimental.ZkProgram({ inductiveCase: { privateInputs: [SelfProof], - method(earlierProof: SelfProof) { + method(earlierProof: SelfProof) { earlierProof.verify(); return earlierProof.publicOutput.add(1); }, @@ -32,7 +33,7 @@ let MyProgram = Experimental.ZkProgram({ }, }); // type sanity checks -MyProgram.publicInputType satisfies Provable; +MyProgram.publicInputType satisfies Provable; MyProgram.publicOutputType satisfies typeof Field; let MyProof = Experimental.ZkProgram.Proof(MyProgram); diff --git a/src/examples/zkapps/simple_zkapp_with_proof.ts b/src/examples/zkapps/simple_zkapp_with_proof.ts index 63c67f57f..599cbaaa8 100644 --- a/src/examples/zkapps/simple_zkapp_with_proof.ts +++ b/src/examples/zkapps/simple_zkapp_with_proof.ts @@ -11,6 +11,7 @@ import { ZkappPublicInput, SelfProof, verify, + Empty, } from 'snarkyjs'; await isReady; @@ -32,7 +33,7 @@ class NotSoSimpleZkapp extends SmartContract { @method update( y: Field, - oldProof: SelfProof, + oldProof: SelfProof, trivialProof: TrivialProof ) { oldProof.verify(); diff --git a/src/index.ts b/src/index.ts index cdc9c26dd..d72a56d8b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,7 @@ export { SelfProof, verify, JsonProof, + Empty, Undefined, Void, } from './lib/proof_system.js'; diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index c1a7fcbb9..48e4d6f25 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -16,6 +16,7 @@ import { SmartContract } from './zkapp.js'; import * as Precondition from './precondition.js'; import { dummyBase64Proof, + Empty, inCheckedComputation, Proof, Prover, @@ -1945,7 +1946,7 @@ async function addMissingProofs( { proofsEnabled = true } ): Promise<{ zkappCommand: ZkappCommandProved; - proofs: (Proof | undefined)[]; + proofs: (Proof | undefined)[]; }> { type AccountUpdateProved = AccountUpdate & { lazyAuthorization?: LazySignature; @@ -2017,7 +2018,7 @@ async function addMissingProofs( accountUpdateProved: accountUpdate as AccountUpdateProved, proof: new Proof({ publicInput, - publicOutput: null, + publicOutput: undefined, proof, maxProofsVerified, }), @@ -2028,7 +2029,7 @@ async function addMissingProofs( // compute proofs serially. in parallel would clash with our global variable // hacks let accountUpdatesProved: AccountUpdateProved[] = []; - let proofs: (Proof | undefined)[] = []; + let proofs: (Proof | undefined)[] = []; for (let i = 0; i < accountUpdates.length; i++) { let { accountUpdateProved, proof } = await addProof(i, accountUpdates[i]); accountUpdatesProved.push(accountUpdateProved); diff --git a/src/lib/mina.ts b/src/lib/mina.ts index 4bf22252a..178b8aeee 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -19,7 +19,7 @@ import { import * as Fetch from './fetch.js'; import { assertPreconditionInvariants, NetworkValue } from './precondition.js'; import { cloneCircuitValue, toConstant } from './circuit_value.js'; -import { Proof, snarkContext, verify } from './proof_system.js'; +import { Empty, Proof, verify } from './proof_system.js'; import { Context } from './global-context.js'; import { SmartContract } from './zkapp.js'; import { invalidTransactionError } from './errors.js'; @@ -94,7 +94,7 @@ type Transaction = { * * This can take some time. */ - prove(): Promise<(Proof | undefined)[]>; + prove(): Promise<(Proof | undefined)[]>; /** * Sends the {@link Transaction} to the network. */ diff --git a/src/lib/proof_system.ts b/src/lib/proof_system.ts index 5a53ed065..0dd6a3e4c 100644 --- a/src/lib/proof_system.ts +++ b/src/lib/proof_system.ts @@ -1,4 +1,8 @@ -import { Empty, EmptyUndefined, EmptyVoid } from '../bindings/lib/generic.js'; +import { + EmptyNull, + EmptyUndefined, + EmptyVoid, +} from '../bindings/lib/generic.js'; import { withThreadPool } from '../bindings/js/wrapper.js'; import { Bool, @@ -20,7 +24,16 @@ import { import { Context } from './global-context.js'; // public API -export { Proof, SelfProof, JsonProof, ZkProgram, verify }; +export { + Proof, + SelfProof, + JsonProof, + ZkProgram, + verify, + Empty, + Undefined, + Void, +}; // internal API export { @@ -46,8 +59,6 @@ export { inCheckedComputation, inCompileMode, dummyBase64Proof, - Undefined, - Void, }; // global circuit-related context @@ -66,6 +77,8 @@ let snarkContext = Context.create({ default: {} }); type Undefined = undefined; const Undefined: ProvablePureExtended = EmptyUndefined(); +type Empty = Undefined; +const Empty = Undefined; type Void = undefined; const Void: ProvablePureExtended = EmptyVoid(); @@ -693,7 +706,7 @@ function methodArgumentsToConstant( return constArgs; } -let Generic = Empty(); +let Generic = EmptyNull(); type TypeAndValue = { type: Provable; value: T }; diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index a8093d54d..b66cafc87 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -52,6 +52,7 @@ import { import { analyzeMethod, compileProgram, + Empty, emptyValue, GenericArgument, getPreviousProofsForProver, @@ -68,7 +69,6 @@ import { } from './proof_system.js'; import { PrivateKey, PublicKey } from './signature.js'; import { assertStatePrecondition, cleanStatePrecondition } from './state.js'; -import { Empty } from '../bindings/lib/generic.js'; // external API export { @@ -120,9 +120,9 @@ function method( methodName ); - class SelfProof extends Proof { + class SelfProof extends Proof { static publicInputType = ZkappPublicInput; - static publicOutputType = Empty(); + static publicOutputType = Empty; static tag = () => ZkappClass; } let internalMethodEntry = sortMethodArguments( @@ -628,9 +628,9 @@ class SmartContract { */ static Proof() { let Contract = this; - return class extends Proof { + return class extends Proof { static publicInputType = ZkappPublicInput; - static publicOutputType = Empty(); + static publicOutputType = Empty; static tag = () => Contract; }; } @@ -683,7 +683,7 @@ class SmartContract { verify, } = await compileProgram( ZkappPublicInput, - Empty(), + Empty, methodIntfs, methods, this From 400505a6382c0aac16b81fb2aaf8f91a78107f73 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 May 2023 15:44:56 +0200 Subject: [PATCH 4/6] bindings --- src/bindings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings b/src/bindings index b1b9b7038..83c406cb1 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit b1b9b703816176eea487884d394dd4916757325c +Subproject commit 83c406cb1ada8f4ad357693ed1501e32789b6745 From a632313ade92cc55e9785c6638db226fe9e7b174 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 May 2023 20:45:32 +0200 Subject: [PATCH 5/6] 0.10.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a35d1af8e..879d550ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "snarkyjs", - "version": "0.10.0", + "version": "0.10.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "snarkyjs", - "version": "0.10.0", + "version": "0.10.1", "license": "Apache-2.0", "dependencies": { "blakejs": "1.2.1", diff --git a/package.json b/package.json index 51f54b9c3..a219d5b35 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "snarkyjs", "description": "TypeScript framework for zk-SNARKs and zkApps", - "version": "0.10.0", + "version": "0.10.1", "license": "Apache-2.0", "homepage": "https://github.com/o1-labs/snarkyjs/", "keywords": [ From a983ce5c8f47488743ba40775796d6d7801ad1a0 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 8 May 2023 20:47:21 +0200 Subject: [PATCH 6/6] changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d8d2f153..5b00c868f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm _Security_ in case of vulnerabilities. --> -## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/bcc666f2...HEAD) +## [Unreleased](https://github.com/o1-labs/snarkyjs/compare/a632313a...HEAD) + +> No unreleased changes yet + +## [0.10.1](https://github.com/o1-labs/snarkyjs/compare/bcc666f2...a632313a) ### Changes