UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

281 lines (251 loc) 7.59 kB
import 'reflect-metadata'; import { Snarky, initializeBindings } from '../../snarky.js'; import { MlFieldArray, MlFieldConstArray } from '../ml/fields.js'; import { withThreadPool } from '../../snarky.js'; import { Provable } from '../provable/provable.js'; import { snarkContext, gatesFromJson, } from '../provable/core/provable-context.js'; import { prettifyStacktrace, prettifyStacktracePromise, } from '../util/errors.js'; import { ProvablePure } from '../provable/types/provable-intf.js'; // external API export { public_, circuitMain, Circuit, Keypair, Proof, VerificationKey }; class Circuit { // circuit-writing interface static _main: CircuitData<any, any>; /** * Generates a proving key and a verification key for this circuit. * @example * ```ts * const keypair = await MyCircuit.generateKeypair(); * ``` */ static async generateKeypair() { let main = mainFromCircuitData(this._main); let publicInputSize = this._main.publicInputType.sizeInFields(); await initializeBindings(); return prettifyStacktracePromise( withThreadPool(async () => { let keypair = Snarky.circuit.compile(main, publicInputSize); return new Keypair(keypair); }) ); } /** * Proves a statement using the private input, public input, and the {@link Keypair} of the circuit. * @example * ```ts * const keypair = await MyCircuit.generateKeypair(); * const proof = await MyCircuit.prove(privateInput, publicInput, keypair); * ``` */ static async prove( privateInput: any[], publicInput: any[], keypair: Keypair ) { let main = mainFromCircuitData(this._main, privateInput); let publicInputSize = this._main.publicInputType.sizeInFields(); let publicInputFields = this._main.publicInputType.toFields(publicInput); await initializeBindings(); return prettifyStacktracePromise( withThreadPool(async () => { let proof = Snarky.circuit.prove( main, publicInputSize, MlFieldConstArray.to(publicInputFields), keypair.value ); return new Proof(proof); }) ); } /** * Verifies a proof using the public input, the proof, and the initial {@link Keypair} of the circuit. * @example * ```ts * const keypair = await MyCircuit.generateKeypair(); * const proof = await MyCircuit.prove(privateInput, publicInput, keypair); * const isValid = await MyCircuit.verify(publicInput, keypair.vk, proof); * ``` */ static async verify( publicInput: any[], verificationKey: VerificationKey, proof: Proof ) { let publicInputFields = this._main.publicInputType.toFields(publicInput); await initializeBindings(); return prettifyStacktracePromise( withThreadPool(async () => Snarky.circuit.verify( MlFieldConstArray.to(publicInputFields), proof.value, verificationKey.value ) ) ); } } class Keypair { value: Snarky.Keypair; constructor(value: Snarky.Keypair) { this.value = value; } verificationKey() { return new VerificationKey( Snarky.circuit.keypair.getVerificationKey(this.value) ); } /** * Returns a low-level JSON representation of the {@link Circuit} from its {@link Keypair}: * a list of gates, each of which represents a row in a table, with certain coefficients and wires to other (row, column) pairs * @example * ```ts * const keypair = await MyCircuit.generateKeypair(); * const json = MyProvable.witnessFromKeypair(keypair); * ``` */ constraintSystem() { try { return gatesFromJson( Snarky.circuit.keypair.getConstraintSystemJSON(this.value) ).gates; } catch (error) { throw prettifyStacktrace(error); } } } /** * Proofs can be verified using a {@link VerificationKey} and the public input. */ class Proof { value: Snarky.Proof; constructor(value: Snarky.Proof) { this.value = value; } } /** * Part of the circuit {@link Keypair}. A verification key can be used to verify a {@link Proof} when you provide the correct public input. */ class VerificationKey { value: Snarky.VerificationKey; constructor(value: Snarky.VerificationKey) { this.value = value; } } function public_(target: any, _key: string | symbol, index: number) { // const fieldType = Reflect.getMetadata('design:paramtypes', target, key); if (target._public === undefined) { target._public = []; } target._public.push(index); } type CircuitData<P, W> = { main(publicInput: P, privateInput: W): void; publicInputType: ProvablePure<P>; privateInputType: ProvablePure<W>; }; function mainFromCircuitData<P, W>( data: CircuitData<P, W>, privateInput?: W ): Snarky.Main { return function main(publicInputFields: MlFieldArray) { let id = snarkContext.enter({ inCheckedComputation: true }); try { let publicInput = data.publicInputType.fromFields( MlFieldArray.from(publicInputFields) ); let privateInput_ = Provable.witness( data.privateInputType, () => privateInput as W ); data.main(publicInput, privateInput_); } finally { snarkContext.leave(id); } }; } function circuitMain( target: typeof Circuit, propertyName: string, _descriptor?: PropertyDescriptor ): any { const paramTypes = Reflect.getMetadata( 'design:paramtypes', target, propertyName ); const numArgs = paramTypes.length; const publicIndexSet: Set<number> = new Set((target as any)._public); const witnessIndexSet: Set<number> = new Set(); for (let i = 0; i < numArgs; ++i) { if (!publicIndexSet.has(i)) witnessIndexSet.add(i); } target._main = { main(publicInput: any[], privateInput: any[]) { let args = []; for (let i = 0; i < numArgs; ++i) { let nextInput = publicIndexSet.has(i) ? publicInput : privateInput; args.push(nextInput.shift()); } return (target as any)[propertyName].apply(target, args); }, publicInputType: provableFromTuple( Array.from(publicIndexSet).map((i) => paramTypes[i]) ), privateInputType: provableFromTuple( Array.from(witnessIndexSet).map((i) => paramTypes[i]) ), }; } type ProvableInputPure<T> = ProvablePure<T> | { provable: ProvablePure<T> }; // TODO support auxiliary data function provableFromTuple( inputTypes: ProvableInputPure<any>[] ): ProvablePure<any> { let types = inputTypes.map((t) => ('provable' in t ? t.provable : t)); return { sizeInFields: () => { return types.reduce((acc, type) => acc + type.sizeInFields(), 0); }, toFields: (t: Array<any>) => { if (t.length !== types.length) { throw new Error( `typOfArray: Expected ${types.length}, got ${t.length}` ); } let res = []; for (let i = 0; i < t.length; ++i) { res.push(...types[i].toFields(t[i])); } return res; }, toAuxiliary() { return []; }, fromFields: (xs: Array<any>) => { let offset = 0; let res: Array<any> = []; types.forEach((typ) => { const n = typ.sizeInFields(); res.push(typ.fromFields(xs.slice(offset, offset + n))); offset += n; }); return res; }, check(xs: Array<any>) { types.forEach((typ, i) => (typ as any).check(xs[i])); }, toValue(x) { return types.map((typ, i) => typ.toValue(x[i])); }, fromValue(x) { return types.map((typ, i) => typ.fromValue(x[i])); }, }; }