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:
- Twitter/X @o1_labs
- O(1) Labs Blog, especially the What's New in o1js monthly updates
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:
- #zkapps-developers to meet other developers building zkApps with o1js
- #zkapps-general to ask general questions about zkApps, how to use a zkApp, and so on
- #zkapps-questions to ask zkApps-related questions and see Q&A history
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, theimport
statement brings in objects and methods fromo1js
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:
- getActions works with the blockchain network
- fetchActions works with 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, likeField.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 asEncoding.stringToFields()
. Methods like this that don't add anything to your circuit are typically clarified in a doc comment.
- A statement like
- 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:
n.toString()
can't be used in circuit code at all. It throws an error duringSmartContract.compile()
because duringcompile()
, variables liken
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.- 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 ofn
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.assertEquals(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.
How do I restrict a provable method so that it can be invoked only by a specific address?
There are a number of ways to accomplish this:
- Provably add a signed child account update. Then, the method can be invoked only with a signature from the predefined address.
// in your method create empty signed account update
AccountUpdate.createSigned(someAddress);
// later, sign the tx
tx.sign([privateKeyOfAddress]);
- Require a private key as a private input, and check that
privateKey.toPublicKey().assertEquals(this.address)
- Require a signature as an input.
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.