Skip to content

Commit

Permalink
Merge pull request #1805 from o1-labs/optional-proving-zkprogram
Browse files Browse the repository at this point in the history
Optional proving for zkProgram
  • Loading branch information
Trivo25 authored Sep 4, 2024
2 parents 4994b44 + fc8f8d8 commit 5006e4f
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 26 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- `SmartContract.emitEventIf()` to conditionally emit an event https://github.com/o1-labs/o1js/pull/1746
- Added `Encryption.encryptV2()` and `Encryption.decryptV2()` for an updated encryption algorithm that guarantees cipher text integrity.
- Also added `Encryption.encryptBytes()` and `Encryption.decryptBytes()` using the same algorithm.
- New option `proofsEnabled` for `zkProgram` (default value: `true`), to quickly test circuit logic with proofs disabled https://github.com/o1-labs/o1js/pull/1805
- Additionally added `MyProgram.proofsEnabled` to get the internal value of `proofsEnabled` and `MyProgram.setProofsEnabled(proofsEnabled)` to set the value dynamically.

### Changed

Expand Down
50 changes: 50 additions & 0 deletions src/examples/zkprogram/program-no-proving.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
SelfProof,
Field,
ZkProgram,
verify,
Proof,
JsonProof,
Provable,
Empty,
Cache,
} from 'o1js';

let MyProgram = ZkProgram({
name: 'example-without-proving',
publicOutput: Field,
publicInput: Field,
methods: {
baseCase: {
privateInputs: [],
async method(publicInput: Field) {
return publicInput.add(4);
},
},
},
});

console.log('program digest', await MyProgram.digest());

// enable proofs to compile the program
const proofsEnabled = true;

await MyProgram.compile({
proofsEnabled,
});

console.log('proofs enabled?', MyProgram.proofsEnabled);

console.log('proving base case... (proofs enabled)');
console.time('proving');
let proof = await MyProgram.baseCase(Field(2));
console.timeEnd('proving');
proof.publicOutput.assertEquals(Field(2).add(4));

console.log('disable proofs, generate dummy proof');
MyProgram.setProofsEnabled(false);
console.log('proofs enabled?', MyProgram.proofsEnabled);
console.time('noProving');
proof = await MyProgram.baseCase(Field(2));
console.timeEnd('noProving');
proof.publicOutput.assertEquals(Field(2).add(4));
102 changes: 76 additions & 26 deletions src/lib/proof-system/zkprogram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,11 @@ function ZkProgram<
}
): {
name: string;
compile: (options?: { cache?: Cache; forceRecompile?: boolean }) => Promise<{
compile: (options?: {
cache?: Cache;
forceRecompile?: boolean;
proofsEnabled?: boolean;
}) => Promise<{
verificationKey: { data: string; hash: Field };
}>;
verify: (
Expand Down Expand Up @@ -590,13 +594,17 @@ function ZkProgram<
Types[I]
>['method'];
};
proofsEnabled: boolean;
setProofsEnabled(proofsEnabled: boolean): void;
} & {
[I in keyof Types]: Prover<
InferProvableOrUndefined<Get<StatementType, 'publicInput'>>,
InferProvableOrVoid<Get<StatementType, 'publicOutput'>>,
Types[I]
>;
} {
let doProving = true;

let methods = config.methods;
let publicInputType: ProvablePure<any> = ProvableType.get(
config.publicInput ?? Undefined
Expand All @@ -605,7 +613,9 @@ function ZkProgram<
config.publicOutput ?? Void
);

let selfTag = { name: config.name };
let selfTag = {
name: config.name,
};
type PublicInput = InferProvableOrUndefined<
Get<StatementType, 'publicInput'>
>;
Expand Down Expand Up @@ -656,22 +666,33 @@ function ZkProgram<
async function compile({
cache = Cache.FileSystemDefault,
forceRecompile = false,
proofsEnabled = true,
} = {}) {
let methodsMeta = await analyzeMethods();
let gates = methodKeys.map((k) => methodsMeta[k].gates);
let { provers, verify, verificationKey } = await compileProgram({
publicInputType,
publicOutputType,
methodIntfs,
methods: methodFunctions,
gates,
proofSystemTag: selfTag,
cache,
forceRecompile,
overrideWrapDomain: config.overrideWrapDomain,
});
compileOutput = { provers, verify };
return { verificationKey };
doProving = proofsEnabled;

if (doProving) {
let methodsMeta = await analyzeMethods();
let gates = methodKeys.map((k) => methodsMeta[k].gates);

let { provers, verify, verificationKey } = await compileProgram({
publicInputType,
publicOutputType,
methodIntfs,
methods: methodFunctions,
gates,
proofSystemTag: selfTag,
cache,
forceRecompile,
overrideWrapDomain: config.overrideWrapDomain,
});

compileOutput = { provers, verify };
return { verificationKey };
} else {
return {
verificationKey: VerificationKey.empty(),
};
}
}

function toProver<K extends keyof Types & string>(
Expand All @@ -682,11 +703,30 @@ function ZkProgram<
publicInput: PublicInput,
...args: TupleToInstances<Types[typeof key]>
): Promise<Proof<PublicInput, PublicOutput>> {
class ProgramProof extends Proof<PublicInput, PublicOutput> {
static publicInputType = publicInputType;
static publicOutputType = publicOutputType;
static tag = () => selfTag;
}

if (!doProving) {
let previousProofs = MlArray.to(
getPreviousProofsForProver(args, methodIntfs[i])
);

let publicOutput = await (methods[key].method as any)(
publicInput,
previousProofs
);

return ProgramProof.dummy(publicInput, publicOutput, maxProofsVerified);
}

let picklesProver = compileOutput?.provers?.[i];
if (picklesProver === undefined) {
throw Error(
`Cannot prove execution of program.${key}(), no prover found. ` +
`Try calling \`await program.compile()\` first, this will cache provers in the background.`
`Try calling \`await program.compile()\` first, this will cache provers in the background.\nIf you compiled your zkProgram with proofs disabled (\`proofsEnabled = false\`), you have to compile it with proofs enabled first.`
);
}
let publicInputFields = toFieldConsts(publicInputType, publicInput);
Expand All @@ -703,35 +743,35 @@ function ZkProgram<
}
let [publicOutputFields, proof] = MlPair.from(result);
let publicOutput = fromFieldConsts(publicOutputType, publicOutputFields);
class ProgramProof extends Proof<PublicInput, PublicOutput> {
static publicInputType = publicInputType;
static publicOutputType = publicOutputType;
static tag = () => selfTag;
}

return new ProgramProof({
publicInput,
publicOutput,
proof,
maxProofsVerified,
});
}

let prove: Prover<PublicInput, PublicOutput, Types[K]>;
if (
(publicInputType as any) === Undefined ||
(publicInputType as any) === Void
) {
prove = ((...args: TupleToInstances<Types[typeof key]>) =>
(prove_ as any)(undefined, ...args)) as any;
prove = ((...args: any) => prove_(undefined as any, ...args)) as any;
} else {
prove = prove_ as any;
}
return [key, prove];
}

let provers = Object.fromEntries(methodKeys.map(toProver)) as {
[I in keyof Types]: Prover<PublicInput, PublicOutput, Types[I]>;
};

function verify(proof: Proof<PublicInput, PublicOutput>) {
if (!doProving) {
return Promise.resolve(true);
}
if (compileOutput?.verify === undefined) {
throw Error(
`Cannot verify proof, verification key not found. Try calling \`await program.compile()\` first.`
Expand All @@ -752,7 +792,7 @@ function ZkProgram<
return hashConstant(digests).toBigInt().toString(16);
}

return Object.assign(
const program = Object.assign(
selfTag,
{
compile,
Expand All @@ -771,9 +811,19 @@ function ZkProgram<
rawMethods: Object.fromEntries(
methodKeys.map((key) => [key, methods[key].method])
) as any,
setProofsEnabled(proofsEnabled: boolean) {
doProving = proofsEnabled;
},
},
provers
);

// Object.assign only shallow-copies, hence we cant use this getter and have to define it explicitly
Object.defineProperty(program, 'proofsEnabled', {
get: () => doProving,
});

return program as ZkProgram<StatementType, Types>;
}

type ZkProgram<
Expand Down

0 comments on commit 5006e4f

Please sign in to comment.