o1-labs / o1js

TypeScript framework for zk-SNARKs and zkApps
https://docs.minaprotocol.com/en/zkapps/how-to-write-a-zkapp
Apache License 2.0
502 stars 112 forks source link

Make circuits async: JS side #735

Closed mitschabaude closed 5 months ago

mitschabaude commented 1 year ago

Why:

maht0rz commented 1 year ago

Having an async Circuit.witness could enable much nicer UX on 3rd party library side

maht0rz commented 12 months ago

We've run into a performance bottleneck again while building https://protokit.dev, I'll try to briefly explain what exactly we have to do in the current block production pipeline and how async witnesses could help.

For Smart Contracts / zkApps the on-chain state is preloaded at once (all 8 fields) and therefore synchronously accessible within the smart contract method, e.g. using a synchronous Provable.witness(Field, () => Field(1)). This is only possible since the on-chain state size is known upfront.

If the execution of a smart contract relies on additional private inputs (not known upfront), that depend on the partial result of computation of the method, and these inputs have to be fetched from somewhere - API, DB, etc. Then the code looks something like this:

// in @method()

// some in circuit computation of known inputs
function getUser(a: Field, b: Field) {
  const key = Poseidon.hash([a,b]);

  // non-async witness
  const user = Provable.witness(User, () => {
     // access the user data statically, since the witness is synchronous, the data must be prefetched
     const data = prefetchedData[key];
     return new User(data)
  })

  // alternative async witness (not possible today)
  const user = Provable.witness(User, async () => {
     // access the user data from an API directly, since we can pause the circuit until the witness is resolved
     const data = await fetch("https://my-api.com/" + key)
     return new User(data)
  })
}

What the synchronous witness approach results in is that if we can't reliably determine what data needs to be prefetched for the circuit execution, we end up with re-running the circuit to try and populate all the prefetched data one by one. This becomes especially noticeable if prefetching of "data 2" relies on the result of prefetching "data 1".

To sum it up, async witnesses would greatly increase the performance of Protokit block production by decreasing the complexity of the implementation as well.

mitschabaude commented 12 months ago

Note: async witness is easy to implement purely in o1js if only we have async circuits