Skip to main content

zkApps and o1js FAQ

Answers to common questions about zkApps (zero knowledge apps) and o1js, a TypeScript library for writing zk smart contracts.

How do I stay up to date with zkApps and o1js?

Follow the official O(1) Labs channels:

Where can I ask questions and contribute answers?

Mina Protocol Discord is the most popular place where Mina enthusiasts and technical contributors gather.

Join us in these zkApps channels:

What files do I use to write zkApps?

There are many approaches to building a smart contract. For the zkApp tutorials, most examples follow this convention:

  • index.ts: The entry point of your project that imports all smart contract classes you want access to and exports them to your smart contract.
  • main.ts: How you interact with the smart contract. For example, the import statement brings in objects and methods from o1js that you use to interact with your smart contract.
  • <yourcontract>.ts: Your specific smart contract logic.

Where can I find the o1js API reference documentation?

See the autogenerated o1js reference documentation with doc comments, like the Provable module.

What is ZkProgram?

A general-purpose API for creating zk proofs. A ZkProgram is similar to a zkApp smart contract but isn't tied to an on-chain account.

What is the difference between getActions and fetchActions?

Use the appropriate module to work with the live network or with historical archive nodes:

Does o1js compile my JavaScript code to an arithmetic circuit?

No, o1js does NOT compile into anything else. In contrast to other zk ecosystems, o1js is just a JS library. It creates zk circuits from user code by executing that code. If you have a smart contract with a @method myMethod(), for example, o1js simply calls myMethod(); during proof generation.

This works because o1js sets up some global state - a "circuit" - where it collects variables and constraints. The use of functions like Field.mul or Bool.assertEquals inside your smart contract methods add corresponding variables and constraints to the global circuit.

This has some implications:

  • To turn your logic into a proof, you must use o1js built-in datatypes such as Field and use the o1js functions that operate on them, like Field.mul().
    • A statement like x.mul(y) adds a generic PLONK gate to your circuit and returns a variable that you can use in further statements that get wired to the multiplication gate.
    • Some o1js methods allow you to convert normal JavaScript datatypes into Field elements and back, such as Encoding.stringToFields(). Methods like this that don't add anything to your circuit are typically clarified in a doc comment.
  • Conventional JavaScript code such as 'hello world'.split('').join(' ') that doesn't use o1js built-ins are not included in your zk proof since it doesn't add anything to your circuit.
    • Why? Because it doesn't call any of the functions that build the circuit.
    • There's nothing wrong with having non-circuit code inside your method, as long as you're aware of what it's (not) doing.
  • It's fine to use if-statements, for-loops, arrays, objects, and any other JavaScript language constructs to facilitate writing circuits. However, be aware that these flexible constructs don't allow you to overcome the static nature of circuits.

This example asserts that a Field element x is not equal to 5, 10 or 15:

// good
for (let y of [5, 10, 15]) {
x.equals(y).assertFalse();
}

The previous for-loop example just stitches together a fixed number of o1js commands, which is fine. However, the following snippet, where the loop's length is determined from user input, won't work:

// bad
@method myMethod(x: Field, n: Field) {
let n0 = Number(n.toString()); // nope
for (let y = 0; y < n0; y += 5) {
x.equals(y).assertFalse();
}
}

This example fails for two reasons:

  1. n.toString() can't be used in circuit code at all. It throws an error during SmartContract.compile() because during compile(), variables like n don't have any JS values attached to them; they represent abstract variables used to build up an abstract arithmetic circuit. So, in general, you can't use any of the methods that read out the JS value of your Field elements: Field.toString(), Field.toBigInt(), Bool.toBoolean() etc.
  2. More subtly, your methods must create the same constraints every time because a proof cannot be verified against a verification key for a differing set of constraints. The code above adds x.equals(y).assertFalse() on condition of the value of n which leads to constraints varying between executions of the proof.

Why is the variable currentState set to a value retrieved from the blockchain and then immediately compared to that value?

As represented in the tutorial example code:

const currentState = this.num.get();

this.num.requireEquals(currentState);
  • The first line of code executes before the proof is generated.
  • The second line of code creates a precondition that is checked when the proof is sent in a transaction to the blockchain to be verified.

This ensures that the transaction fails if the value of the field in question has changed.

Can I pass hex values into Fields?

Yes, just pass in the appropriate BigInt literal.

Field(0x1337)

Can I pass arguments to the SmartContract init method?

The best practice is no. To test something with more than one initialization state, you can set the state by passing arguments to another user-defined method.

Can I use TypeScript enums?

You can try! We experimented with this, so it might generate unconstrained functionality.

How can I use the Field type?

Are there specific reasons I want to specify the Field type?

All provable types are built using the type Field. For efficiency, use Field only when you do not need to take advantage of the properties of the other provable types in o1js.

What does the @method decorator do?

It allows the method to be invoked by a user interacting with the smart contract. You can check out the compiled JavaScript in ./build/src to see exactly what's going on.

How can you enforce that an account update must be signed by the account owner?

Use the requireSignature command. See the requireSignature Method reference.

How do I configure who has the authority to interact and make changes to a specific part of an account?

Permissions determine who has the authority to interact and make changes to a specific part of a smart contract. See Permissions.

How are proofs generated in the Mina Protocol?

Kimchi is the main machinery that generates the recursive proofs that allow the Mina blockchain to remain of a fixed size of about 22 KB. See Proof systems.

Are there proof generation scenarios when recursive proofs are not needed?

Yes. It is possible to use the Kimchi proof system without the Pickles recursion layer. See Pickles in the Mina book.

Which curves are used by the Mina Protocol to generate proofs?

Pasta curves (Pallas and Vesta). See Pasta Curves in the Mina book.

When do I use Provable conditional logic?

Are there situations in which I would not want to use the Provable versions? If the conditional logic is not part of your provable code, you do not need to use Provable conditional statements.