Skip to main content

o1js FAQ

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 JS code such as 'hello world'.split('').join(' ') that doesn't use o1js built-ins are not included in your zk proof in any way since it does 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 JS language constructs to facilitate writing circuits. But beware: 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.