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

Cache prover keys #1187

Merged
merged 39 commits into from
Oct 31, 2023
Merged

Cache prover keys #1187

merged 39 commits into from
Oct 31, 2023

Conversation

mitschabaude
Copy link
Collaborator

@mitschabaude mitschabaude commented Oct 19, 2023

closes #87

mina: MinaProtocol/mina#14396 and MinaProtocol/mina#14412
bindings: o1-labs/o1js-bindings#187

This PR enables caching of prover and verifier keys in Node.js. The caching interface is kept flexible and will allow a variety of other caching strategies -- for example, bundling snark keys with your web app, or downloading them from some CDN, etc.

Results for calling compile() on the simple_zkapp.ts example (my machine)

  • before this PR / on the first compile: 20s
  • on subsequent compiles when no circuit changed: 13s

The majority of the remaining 13s is spent in recreating SRS and Lagrange bases (= long lists of elliptic curve points which are the same every time). Caching of those is added in a follow-up PR.

After caching a bunch of SmartContracts and ZkPrograms, your cache directory will look like this:
image

Note that per contract, several different keys are cached:

  • a step prover key (step-pk) and verification key (step-vk) for every method.
  • a wrap prover key (wrap-pk) and verification key (wrap-vk) for the entire contract
  • a .header file for each cache item. this just contains a unique string which is used to determine whether we can use the cached data

If only one of the methods changes, then only the wrap keys and one of the step keys will be regenerated.

@mitschabaude mitschabaude marked this pull request as ready for review October 23, 2023 15:23
@mitschabaude
Copy link
Collaborator Author

mitschabaude commented Oct 25, 2023

Update: Not ready for review, I'm going to refactor the default caching strategy to not fill the developer's disk so quickly. This also depends on a planned addition to ZkProgram to get a name argument, so that we have a persistent identifier for caching

EDIT: ready now

Comment on lines +80 to +82
let privilegedKey = PrivateKey.fromBase58(
'EKEeoESE2A41YQnSht9f7mjiKpJSeZ4jnfHXYatYi8xJdYSxWBep'
);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

there's no caching if the contract depends on random constants 😬

Copy link
Contributor

Choose a reason for hiding this comment

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

Huh, why is that? 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Constants that are used in constraints are baked into the circuit digest as well as prover and verifier keys. This example was using PrivateKey.random() so the circuit was different every time. Since we compare the circuit digest to decide whether or not we can use a cached key, the answer was "no" every time.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, yes, pretty straightforward. That makes sense, thank you!

Copy link
Collaborator Author

@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.

some comments to understand what is happening here

Comment on lines +12 to +16
/**
* Interface for storing and retrieving values, for caching.
* `read()` and `write()` can just throw errors on failure.
*/
type Cache = {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this file contains the main new interface -- Cache! plus a default implementation of Cache which works in Node.js and uses a common cache directory

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this file provides helpers to

  • encode and decode all 4 kinds of snark keys to/from bytes
  • create a header which is passed to the Cache so that it can figure out where and if to read from cache

Copy link
Contributor

Choose a reason for hiding this comment

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

This file is pretty verbose and could be hard to parse for other readers/maintainers in the future. Mind throwing up a top-level comment that is basically your comment here? It'll be much easier to read once the intent is given in particular :D

Comment on lines +590 to +601
let picklesCache: Pickles.Cache = [
0,
function read_(mlHeader) {
let header = parseHeader(proofSystemTag.name, methodIntfs, mlHeader);
try {
let bytes = cache.read(header);
if (bytes === undefined) return MlResult.unitError();
return MlResult.ok(decodeProverKey(mlHeader, bytes));
} catch (e: any) {
return MlResult.unitError();
}
},
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this code connects all the dots:

  • our helpers to create cache headers and decode snark keys from bytes
  • the user-defined cache where we can read and write from
  • the Pickles.Cache which Pickles expects

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

simple wrapper around a few node-only functions, so that we can provide alternative (dummy) implementations on the web

Copy link
Contributor

Choose a reason for hiding this comment

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

Great design choice! 👍

@@ -661,7 +662,7 @@ class SmartContract {
* it so that proofs end up in the original finite field). These are fairly expensive operations, so **expect compiling to take at least 20 seconds**,
* up to several minutes if your circuit is large or your hardware is not optimal for these operations.
*/
static async compile() {
static async compile({ cache = Cache.FileSystemDefault } = {}) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

SmartContract.compile() now expects an optional Cache. same for ZkProgram.compile()

Copy link
Contributor

@MartinMinkov MartinMinkov left a comment

Choose a reason for hiding this comment

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

Great work! What are your thoughts on documenting the caching strategy we use? Should we have something on our docs website or/and in-depth doc comments?

I think this information is particularly valuable, and it should probably be easily accessible outside of a PR description:

a step prover key (step-pk) and verification key (step-vk) for every method.
a wrap prover key (wrap-pk) and verification key (wrap-vk) for the entire contract
a .header file for each cache item. this just contains a unique string which is used to determine whether we can use the cached data

src/examples/simple_zkapp.web.ts Outdated Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

Great design choice! 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

This file is pretty verbose and could be hard to parse for other readers/maintainers in the future. Mind throwing up a top-level comment that is basically your comment here? It'll be much easier to read once the intent is given in particular :D

src/lib/proof-system/cache.ts Show resolved Hide resolved
@mitschabaude mitschabaude mentioned this pull request Oct 31, 2023
@mitschabaude
Copy link
Collaborator Author

I think this information is particularly valuable, and it should probably be easily accessible outside of a PR description:

Addressed in the SRS caching PR!

@mitschabaude mitschabaude merged commit c5a1402 into main Oct 31, 2023
13 checks passed
@mitschabaude mitschabaude deleted the feature/pickles-js-caching branch October 31, 2023 13:33
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.

Restore prover keys
2 participants