Analyzing Constraints
Optimizing o1js code is often not a matter of copying over optimized functions traditional programming languages, 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 you 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.
This will not check correctness of the code. It will only convert the code into a constraint system, which is essentially a list of steps to execute. It will not execute the steps, or provide any guarantees that the constraint system generated is correct or secure.
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.
ZkProgram.analyzeMethods
uses Provable.constraintSystem
under the hood, so ultimately, they expose the same functionality.
The main difference is the ergonomics of the API. Provable.constraintSystem
is a pure constraint system generation tool, while
ZkProgram
is a more complete provable implementation, of which constraint system generation is one part.
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.
SmartContract.analyzeMethods
uses Provable.constraintSystem
under the hood, so ultimately, they expose the same functionality.
Just like ZkProgram
, SmartContract
is a complete provable implementation of a program, but it layers on even more extra
functionality in the form of compatibility with the Mina blockchain.
So Provable.constraintSystem
is strictly a constraint system generation tool. It's extremely generic and can't be used to create
a proof. ZkProgram
is a complete provable program, including constraint system generation, but also proof generation and
verification. SmartContract
is a specialized wrapper around ZkProgram
that is designed to work with the Mina blockchain. All
of these tools can expose generated constraint system analyses.
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.