Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose sideloaded verification keys #1606

Merged
merged 8 commits into from
Apr 24, 2024

Conversation

rpanic
Copy link
Contributor

@rpanic rpanic commented Apr 22, 2024

This PR implements sideloaded verification keys as outlined in this RFC: MinaFoundation/Core-Grants#3

Bindings: o1-labs/o1js-bindings#269

Replacement to #1238
Closes #673

@rpanic rpanic changed the title Expose sideloaded verification Keys Expose sideloaded verification keys Apr 22, 2024
@rpanic rpanic marked this pull request as ready for review April 23, 2024 13:18
Copy link
Member

@mitschabaude mitschabaude left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks so much for contributing this @rpanic!

I have just a few suggestions

Comment on lines 936 to 939
if (Provable.inProver()) {
Pickles.sideLoaded.inProver(computedTag, vk.data);
}
const circuitVk = Pickles.sideLoaded.vkToCircuit(() => vk.data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would love to understand what these exactly are doing.

vkToCircuit looks like introducing the vk as witnesses.

in inProver? associates the vk.data with the tag?
Just curious where exactly pickles needs this association

same question for inCircuit below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good question, to be honest, I got the instructions on how to use the exposed pickles calls from matthew and just accepted them as-is. Would also be interested in what the background on those is @mrmr1993

@@ -191,8 +198,95 @@ class Proof<Input, Output> {
}
}

var sideloadedKeysCounter = 0;

class DynamicProof<Input, Output> extends ProofBase<Input, Output> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since DynamicProof is the API entry-point to using side loaded vks, it would be good to add a doccomment here, which

  • explains what this is and how to use it
  • highlights the different trust model it associates with the zkapp proofs we are verifying: While verifying a Proof proves that its public input / output originated from one of a known and hard-coded set of circuits, verifying a DynamicProof just means the proof is valid against the associated verification key. It's on the user to constrain that verification key to be anything that is meaningful for the application at hand (and since your merkle tree example does a great job at demonstrating how that might work, you could link to it in the comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love this example!

static maxProofsVerified = 1 as const;
}

const add = ZkProgram({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this example makes me a bit uncomfortable, since add isn't proving that DynamicMultiplyProof came from the multiply program, and that might not be obvious to users seeing this example.

I think adding a TODO comment where you highlight this caveat would be enough to prevent this pattern accidentally ending up in a user app and creating a vulnerability

src/index.ts Outdated
@@ -1,5 +1,5 @@
export type { ProvablePure } from './lib/provable/types/provable-intf.js';
export { Ledger, initializeBindings } from './snarky.js';
export { Ledger, initializeBindings, Pickles } from './snarky.js';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait, why are we exporting the Pickles namespace? if there's no specific reason, I'd rather avoid exporting an undocumented and easy-to-misuse low-level API

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that is a leftover from testing, thanks for catching that!

@@ -787,14 +919,43 @@ function picklesRuleFromFunction(
let input = fromFieldVars(publicInputType, publicInput);
result = await func(input, ...finalArgs);
}

proofs.forEach(({ proofInstance, classReference }, index) => {
if (proofInstance instanceof DynamicProof) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: early return if !(proofInstance instanceof DynamicProof) would be cleaner

src/snarky.d.ts Outdated
Comment on lines 744 to 755
sideLoaded: {
// Create a side-loaded key tag
create:(name: string, numProofsVerified: 0 | 1 | 2, publicInputLength: number, publicOutputLength: number) => unknown /* tag */,
// Instantiate the verification key inside the circuit (required).
inCircuit:(tag: unknown, verificationKey: unknown) => undefined,
// Instantiate the verification key in prover-only logic (also required).
inProver:(tag: unknown, verificationKey: string) => undefined,
// Create an in-circuit representation of a verification key
vkToCircuit:(verificationKey: () => string) => unknown /* verificationKeyInCircuit */,
// Get the digest of a verification key in the circuit
vkDigest:(verificationKeyInCircuit: unknown) => MlArray<FieldVar>,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this doesn't look code-formatted

Comment on lines 128 to 149
let program1Proof: Proof<Field, void>;
let program2Proof: Proof<Program2Struct, Field>;

let program1Vk: VerificationKey;
let program2Vk: VerificationKey;

before(async () => {
program1Vk = (await program1.compile()).verificationKey;
program2Vk = (await program2.compile()).verificationKey;

// Generate sample proofs
const proof1 = await program1.foo(Field(1), Field(1));
program1Proof = proof1;

const proof2 = await program2.foo(
{ field1: Field(1), field2: Field(2) },
Field(3)
);
program2Proof = proof2;

await sideloadedProgram.compile();
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this before() pattern with variable reassignments unreadable and error-prone. I think it's the legacy of Jest + missing top-level await. Can you just move all that preparation logic to before the describe() block?

@mitschabaude mitschabaude merged commit c01e93e into o1-labs:main Apr 24, 2024
12 of 14 checks passed
@Trivo25 Trivo25 mentioned this pull request Apr 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Expose side loaded verification key to snarkyjs
2 participants