Skip to main content
Version: 2.4.0

Analyzing Constraints

Optimizing o1js code is often not a matter of copying over optimized functions from VMs that operate over bytes, but instead a matter of minimizing the number of constraints required to model the code. It's important to analyze the functions you write to understand the constraint system that they generate. o1js provides the tools toy need to understand how many, and what types of constraints your program generates.

Example provable function

For the following examples, let's define a dummy function that we can reuse in different contexts:

function exampleProvableFunction() {
const x = Provable.witness(Field, () => Field(10));
const y = Provable.witness(Field, () => Field(20));

y.assertGreaterThan(x);
return Poseidon.hash([x, y]);
}

Using Provable.constraintSystem

Provable.constraintSystem is a way to create something like a "snippet" of provable code. o1js can convert the snippet into a constraint system, which can then be analyzed. This simple wrapper is the lightest-weight way to quickly analyze some provable code. You can also use it to analyze specific pieces of a larger program, or specific functions.

const cs = await Provable.constraintSystem(() => {
exampleProvableFunction();
});

cs.summary(); // Count of constraints, grouped by gate type
cs.rows; // Count of total constraints
cs.gates; // Array of gates that make up the constraint system (essentially a serialization of the raw system)

The summary looks like this:

{
"ForeignFieldAdd": 3,
"Generic": 3,
"Poseidon": 11,
"RangeCheck0": 8,
"RangeCheck1": 4,
"Total rows": 36,
"Zero": 7
}

Read more at the language reference: Provable.constraintSystem.

Using ZkProgram

ZkProgram is a more complete provable implementation of a program. It has proper inputs and outputs, and methods that can be called to create a proof. The ZkProgram itself can be analyzed, which is a handy shortcut to simply get the analysis output for each method in the program. ZkProgram has type requirements and more configuration required than Provable.constraintSystem, so to check just some pieces of code, Provable.constraintSystem is the better choice, but to analyze your methods in the context of a full program that's subject to specific return types, inputs, and outputs, ZkProgram is the better choice.

const zkp = ZkProgram({
name: "Example",
publicOutput: Field,
methods: {
example: {
privateInputs: [],
method: async () => {
const publicOutput = exampleProvableFunction();
return { publicOutput };
},
},
},
});

const zkpAnalysis = await zkp.analyzeMethods();

zkpAnalysis.example.summary();
zkpAnalysis.example.rows;
zkpAnalysis.example.gates;

Note how the same function results in the same constraint system summary:

{
"ForeignFieldAdd": 3,
"Generic": 3,
"Poseidon": 11,
"RangeCheck0": 8,
"RangeCheck1": 4,
"Total rows": 36,
"Zero": 7
}

Read more at the language reference: ZkProgram.

Using SmartContract

SmartContract is a specialized class for writing proofs that interact with the Mina blockchain. For the purposes of analyzing the constraint system, it works like ZkProgram. Keep in mind, that not every method on the SmartContract actually has to be provable! Non-provable methods will not be converted into constraint systems. As a class, SmartContracts are just a JavaScript object, so you can add any methods you want, just like any class. Only methods decorated with @method will be marked as provable, and included in the resulting constraint system.

class ExampleContract extends SmartContract {
@state(Field) x = State<Field>();

@method async example(y: Field, knownHash: Field) {
const x = this.x.getAndRequireEquals();
y.assertGreaterThan(x);
Poseidon.hash([x, y]).assertEquals(knownHash);
this.x.set(y);
}

// Helper function, totally out of scope for analyzeMethods
get stringX() {
return this.x.get().toString();
}
}

const scAnalysis = await ExampleContract.analyzeMethods();

scAnalysis.example.rows;
scAnalysis.example.gates;

Read more at the language reference: SmartContract.