Skip to content

Commit

Permalink
Merge pull request #625 from o1-labs/tuto9
Browse files Browse the repository at this point in the history
docs: verify Tutorial 9: Recursion
  • Loading branch information
barriebyron authored Oct 9, 2023
2 parents 65b327d + 5729bc6 commit bc90605
Showing 1 changed file with 110 additions and 115 deletions.
225 changes: 110 additions & 115 deletions docs/zkapps/tutorials/09-recursion.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 'Tutorial 9: Recursion'
hide_title: true
sidebar_label: 'Tutorial 9: Recursion'
description: Use recursion on-chain and off-chain with zkApps. Learn about use cases to create high-throughput applications, use larger proof sizes, and create multi-party zero knowledge proof constructions.
description: Use recursion on-chain and off-chain with zkApps. Learn about use cases to create high-throughput applications, use larger proof sizes and create multi-party zero knowledge proof constructions.
keywords:
- smart contracts
- zkapps
Expand All @@ -24,82 +24,73 @@ zkApp programmability is not yet available on the Mina Mainnet. You can get star

# Tutorial 9: Recursion

## Overview
One of the most powerful features of zkApps is [recursion](/zkapps/o1js/recursion).

One of the most powerful features of zkApps is recursion.
With recursion, you can realize composability between zero knowledge proofs. Recursion unlocks many powerful technical abilities, such as creating high-throughput applications, creating proofs of large computations, and constructing multi-party proofs.

With recursion, you can realize composability between zero knowledge proofs. This unlocks many powerful technical abilities, such as creating high-throughput applications, creating proofs of large computations, and constructing multi-party proofs.
### Scaling Throughput with zkRollups and App Chains

### Scaling Throughput - zkRollups & App Chains
Verifying large amounts of information is usually challenging for blockchains. With zero knowledge proofs (ZKPs), and particularly recursive ZKPs, this becomes far easier.

Verifying large amounts of information is usually challenging for blockchains. But with zero knowledge proofs, and particularly recursive ZKPs, this becomes far easier.
By leveraging recursive verification, it is possible to easily construct zkRollups and app chains. This tutorial provides an example of a simple zkRollup.

By leveraging recursive verification, it is possible to easily construct both zkRollups and app chains. We’ll give an example of a simple zkRollup near the end of this tutorial.
Recursive composition gives you the flexibility to handle live demand by letting you choose between a high tree height (higher throughput, but logarithmically slower latency) and a lower tree height (lower throughput, but faster latency). You can modify the depth of the tree to handle whatever traffic is present on the network while still offering optimal latency to commit transactions back to the chain.

Recursive composition even gives us flexibility to handle live demand. Recursive composition gives you flexibility to choose between a high tree height (higher throughput, but logarithmically slower latency), and lower tree height (lower throughput, but faster latency). The depth of the tree can be modified live to handle whatever traffic actually present on the network, while still offering optimal latency to commit transactions back to the chain.
For an example of an app chain, one could imagine an on-chain trading pair that uses an order book. Rolling up the transactions for the application with zero knowledge proofs lets the app handle the expensive computations of keeping buy and sell orders sorted while still posting complete verification to the chain.

For an example of an ‘app chain’, one could imagine an on-chain trading pair that uses an orderbook. Rolling up the transactions for the application with zero knowledge proofs can allow our app to handle the expensive computations of keeping buy and sell orders sorted, while still posting full verification to the chain.

At the end of this tutorial, we’ll review a simple zkRollup example that can be used for implementing a zkRollup or an app chain.
This tutorial guides you through a review of a simple zkRollup example that can be used to implement a zkRollup or an app chain.

### Scaling Proof Size

Recursive ZKPs also give you the ability to construct very large transactions that wouldnt otherwise be possible.
Recursive ZKPs allow you to construct very large transactions that wouldn't otherwise be possible.

For example, recursive ZKPs could be used to prove the output of a machine learning (ML) model. This could be used to prove an inference is truly generated by a model, or for a more computationally intensive case, to verify a model has been trained on a particular dataset.
For example, recursive ZKPs can be used to prove the output of a machine learning (ML) model to prove an inference is genuinely generated by a model or, for a more computationally intensive case, to verify that a model has been trained on a particular dataset.

### Off-chain, multi-party proof construction

Recursive zero knowledge proofs also make it easy to allow multiple parties to construct transactions. To accomplish off-chain proof construction, one or more parties can recursively update a ZKP and its associated public state.When the mulit-party stage is completed, that state and its proof can then be sent as part of a transaction on-chain, or used as part of an off-chain application leveraging ZKPs.
Recursive ZKPs also make it easy to allow multiple parties to construct transactions. One or more parties can recursively update a ZKP and its associated public state to accomplish off-chain proof construction. When the multi-party stage is completed, that state and its proof can then be sent as part of an on-chain transaction or used as part of an off-chain application leveraging ZKPs.

## ZkProgram Example

## ZkProgram
You build recursive zkApps with [ZkProgram](/zkapps/o1js-reference/modules/Experimental#zkprogram), the o1js general purpose API for creating zero knowledge proofs. A ZkProgram is similar to zkApp smart contracts but isn't tied to an on-chain account.

Recursive zkApps are built using [ZkProgram](../../zkapps/o1js-reference/modules/Experimental#zkprogram). ZkProgram is similar to the zkApp smart contracts we’ve seen before, but aren’t tied to an on-chain account.
Proofs generated using a ZkProgram can be passed into zkApp smart contracts for them to verify recursively. They can even be passed recursively into their own functions for off-chain recursive composition.

Proofs generated via ZkProgram can be passed into zkApp smart contracts (for them to verify recursively), and can even be passed recursively into their own functions for off-chain recursive composition.
The following ZkProgram example code is provided in the [main.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/09-recursion/src/main.ts) example file.

To see an example of this, let’s build a simple ZkProgram that uses recursion. You can find the code for this example [here](https://github.com/es92/zkApp-examples/blob/main/09-recursion/src/main.ts).
To create a ZkProgram, start with the `init()` method.

This example will be in a single file, `main.ts`. We’ll create our ZkProgram as follows:
- For each method, declare the inputs it will receive.
- The first argument of a ZkProgram method is always the state of the ZkProgram, named `publicInput` since it is public.

```typescript
const Add = Experimental.ZkProgram({
publicInput: Field,

methods: {
init: {
privateInputs: [],
method(state: Field) {
state.assertEquals(Field(0));
methods: {
init: {
privateInputs: [],

method(state: Field) {
state.assertEquals(Field(0));
},
}
}
}
},
```
We start with one method, `init`. Note that for each method, we need to declare what inputs it will receive. The first argument of a ZkProgram method is always the state of the ZkProgram, named “publicInput” since it will be public.
Let’s add another method to this:
Add another method that takes an existing proof, adds a new number to it, and produces a new proof:
```typescript
addNumber: {
privateInputs: [SelfProof, Field ],
addNumber: {
privateInputs: [SelfProof, Field ],

method(
newState: Field,
earlierProof: SelfProof<Field>,
numberToAdd: Field
) {
earlierProof.verify();
newState.assertEquals(earlierProof.publicInput.add(numberToAdd));
method(newState: Field, earlierProof: SelfProof<Field>, numberToAdd: Field) {
earlierProof.verify();
newState.assertEquals(earlierProof.publicInput.add(numberToAdd));
},
},
},
```
Here, we take an existing proof, and add a new number to it, and produce a new proof.
We can also use recursion to combine two proofs:
Use recursion to combine two proofs:
```typescript
add: {
Expand All @@ -117,103 +108,93 @@ We can also use recursion to combine two proofs:
},
```
To use ZkProgram, we need to compile it, and then call methods on it:
To use ZkProgram, compile it and then call methods on it:
```typescript
console.log('compiling...');

const { verificationKey } = await Add.compile();

console.log('making proof 0')

const proof0 = await Add.init(Field(0));

console.log('making proof 1')

const proof1 = await Add.addNumber(Field(4), proof0, Field(4));

console.log('making proof 2')

const proof2 = await Add.add(Field(4), proof1, proof0);

console.log('verifying proof 2');
console.log('proof 2 data', proof2.publicInput.toString());

const ok = await verify(proof2.toJSON(), verificationKey);
console.log('ok', ok);
```
Note that verification of the proof can occur off-chain using the `verify()` method. This is useful for applications where you want to prove something to an off-chain entity.
Verification of the proof can occur off-chain using the `verify()` method. This is useful for applications where you want to prove something to an off-chain entity.
For another example of off-chain multi-party proof construction, see the code [here](https://github.com/es92/zkApp-examples/blob/main/09-recursion/src/vote.ts) - showing how to do Private Off-Chain voting with Recursive ZKPs.
## Voting Example
## Using ZkProgram in SmartContracts
Another example of off-chain multi-party proof construction with recursive ZKPs is provided in the [vote.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/09-recursion/src/vote.ts) example file.
Once we have a recursive ZKP, we may want or need to settle this to the Mina blockchain. We can accomplish this using a method on a SmartContract. To show how to use ZkProgram from a SmartContract, let’s build a zkRollup.
## Using ZkProgram in Smart Contracts
Our zkRollup will operate over a MerkleMap of “accounts”, which each store a number. Our update rule will be to increment the value stored at an account - though you could imagine using more substantial rules to implement a particular application.
After you build a recursive ZKP, use a method on a smart contract to settle the proof to the Mina blockchain.
The zkApp will store the Merkle root of this Merkle map of accounts on chain. We will allow this to be updated when authorized by a recursive zero knowledge proof generated by our ZkProgram.
Build a zkRollup to use ZkProgram from a smart contract.
This both allows it to be flexible to how much compute is being demanded of it (for latency), and allows it to scale to arbitrary numbers of proofs (for throughput).
This example code builds a zkRollup to operate over a MerkleMap of accounts that each store a number. The update rule increments the value stored at an account - though you could imagine using more substantial rules to implement a particular application.
Let’s discuss the code for the ZkProgram part of a rollup. You can find the full example [here](https://github.com/es92/zkApp-examples/blob/main/09-recursion/src/rollup.ts).
The zkApp will store the Merkle root of this MerkleMap of accounts on chain. Updates occur only when authorized by a recursive zero knowledge proof generated by the ZkProgram.
On one machine, the below code doesn’t offer a high throughput, but by switching the mapreduce used for a mapreduce that runs over tens or hundreds of machines, very high throughputs can be achieved. In fact, any level of throughput can be achieved as long as you’re willing to incur `log(N)` latency when constructing the proof of N transactions. You can find an implementation that distributes compute over AWS instances [here](https://github.com/Trivo25/proof_aggregator).
This zkRollup design is flexible to how much compute is being demanded of it (for latency) and allows it to scale to arbitrary numbers of proofs (for throughput).
To start, let’s set up our ZkProgram:
On a single machine, the example code does not offer a high throughput. However, you can achieve very high throughputs by switching the mapreduce here for a mapreduce that runs over tens or hundreds of machines. In fact, you can achieve any level of throughput as long as you're willing to incur `log(N)` latency when constructing the proof of N transactions.
The following code for the ZkProgram part of a rollup is provided in the [rollup.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/09-recursion/src/rollup.ts) example file.
A community-contributed implementation distributes compute over AWS instances in this [proof_aggregator](https://github.com/Trivo25/proof_aggregator) example.
### Set up ZkProgram
```typescript
class RollupState extends Struct({
initialRoot: Field,
latestRoot: Field,
}) {
static createOneStep(...)
}) {

static createMerged(state1: RollupState, state2: RollupState) {
return new RollupState({
initialRoot: state1.initialRoot,
latestRoot: state2.latestRoot
});
}
static createOneStep(
initialRoot: Field,
latestRoot: Field,
key: Field,
currentValue: Field,
incrementAmount: Field,
merkleMapWitness: MerkleMapWitness,
) {
const [ witnessRootBefore, witnessKey ] = merkleMapWitness.computeRootAndKey(currentValue);
initialRoot.assertEquals(witnessRootBefore);
witnessKey.assertEquals(key);
const [ witnessRootAfter, _ ] = merkleMapWitness.computeRootAndKey(currentValue.add(incrementAmount));
latestRoot.assertEquals(witnessRootAfter);

static assertEquals(state1: RollupState, state2: RollupState) {
state1.initialRoot.assertEquals(state2.initialRoot);
state1.latestRoot.assertEquals(state2.latestRoot);
return new RollupState({
initialRoot,
latestRoot
});
}
}

const Rollup = Experimental.ZkProgram({
publicInput: RollupState,

methods: {
oneStep: {
...
},

merge: {
privateInputs: [ SelfProof, SelfProof ],
```
method(
newState: RollupState,
rollup1proof: SelfProof<RollupState>,
rollup2proof: SelfProof<RollupState>,
) {
rollup1proof.verify(); // A -> B
rollup2proof.verify(); // B -> C
A proof generated by the `merge()` method indicates there is a valid sequence of transactions (for example, the applications of `oneStep`):
rollup1proof.publicInput.initialRoot.assertEquals(newState.initialRoot);
- Get from an `initialRoot`, the root of a MerkleMap
- To a `latestRoot` root of the Merkle map after transactions are applied
rollup1proof.publicInput.latestRoot.assertEquals(rollup2proof.publicInput.initialRoot);
### Consume the proofs
rollup2proof.publicInput.latestRoot.assertEquals(newState.latestRoot);
}
}
```
A proof generated by our `merge` method will indicate there is a valid sequence of transactions (i.e. applications of “oneStep”) that get from an “initialRoot” (the root of a MerkleMap) to a “latestRoot” (root of the Merkle map after transactions are applied).
We will consume these in a SmartContract, which will use the recursive proof to update its internal state:
To consume these proofs in a smart contract (`SmartContract`) that uses the recursive proof to update its internal state:
```typescript
class RollupContract extends SmartContract {
Expand Down Expand Up @@ -244,9 +225,15 @@ class RollupContract extends SmartContract {
}
```
Last, is just to fill in the methods above, particularly, `oneStep()`, and `createOneStep()`. This single “step” of the rollup will check the value at an “account” was incremented by a particular amount.
### Verify the value at account is incremented
Fill in the previous methods, particularly `oneStep()` and `createOneStep()`.
This step of the rollup checks that the value at an account was incremented by a particular amount.
To check that the value at an account was incremented by a particular amount, our code computes an updated state - inside the recursive SNARK - by calling `RollupState.createOneStep()`. And then, asserting that the new state of the recursive snark, is equivalent to the computed state. Inside `createOneStep()`, a single step of the rollup is created.
- The code computes an updated state inside the recursive SNARK by calling `RollupState.createOneStep()`.
- Then, asserting that the new state of the recursive SNARK is equivalent to the computed state.
- Inside `createOneStep()`, a single step of the rollup is created.
```typescript
class RollupState extends Struct({
Expand Down Expand Up @@ -306,18 +293,26 @@ const Rollup = Experimental.ZkProgram({

```
`createOneStep()` returns a proof, that acts as the leaf in a tree of recursive proofs. To use this rollup, we would first construct all of the leafs in parallel. Then, we would recursively merge these leafs, until we get a proof for the entire sequence. This parallel merging gives the high throughput properties we’re looking for in a rollup.
The `createOneStep()` method returns a proof that acts as the leaf in a tree of recursive proofs.
And with that last function, we have completed the code for a zkRollup!
To use this rollup, first you construct all of the leafs in parallel then recursively merge these leafs until you get a proof for the entire sequence. This parallel merging gives the high throughput properties for the rollup.
Hopefully it’s clear how one could re-implement the above for more substantial functionality, just by changing `createOneStep()`. For example, to implement a DEX zkRollup that uses an orderbook we could change this to:
* Updating buy/sell orders on an orderbook
* Executing those buy/sell orders
* Adding a “queue” of tokens to move to the app chain in the SmartContract
* Adding a “queue” of tokens to move out of the app chain in the ZkProgram
You can reimplement the example code for more substantial functionality, just by changing `createOneStep()`. For example, to implement a DEX zkRollup that uses an orderbook change the code to:
* Update buy/sell orders on an order book
* Execute those buy/sell orders
* Add a queue of tokens to move to the app chain in the smart contract
* Add a queue of tokens to move out of the app chain in the ZkProgram
## Conclusion
We have finished showing how to use recursion with zkApps, both on and off chain, and discussed their use cases to create high-throughput applications, use larger proof sizes, and create multi-party proof constructions We hope this helps show how recursive zero knowledge proofs can help you build powerful zkApps.
You learned about:
- Recursion with zkApps, both on-chain and off-chain
- Potential use cases to create high-throughput applications
- Larger proof sizes
- Create multi-party proof constructions
Recursive zero knowledge proofs can help you build powerful zkApps.
Check out the next tutorial to learn about AccountUpdates, the underlying structure of zkApps, and how they enable permissions, preconditions, and composability.
Check out [Tutorial 10: Account Updates](zkapps/tutorials/account-updates) to learn about account updates, the underlying structure of zkApps, and how they enable permissions, preconditions, and composability.

2 comments on commit bc90605

@vercel
Copy link

@vercel vercel bot commented on bc90605 Oct 9, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

07-oracles – ./examples/zkapps/07-oracles/oracle

07-oracles-minadocs.vercel.app
07-oracles.vercel.app
07-oracles-git-main-minadocs.vercel.app

@vercel
Copy link

@vercel vercel bot commented on bc90605 Oct 9, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

docs2 – ./

docs2-git-main-minadocs.vercel.app
docs.minaprotocol.com
docs2-minadocs.vercel.app

Please sign in to comment.