Basic standard PLONK JSON tool

Update: Leila Wang from our team made a much more convenient tool where you can experiment with what’s written below directly in a browser

Hey,
I made this very basic tool for people wanting to experiment with barretenberg easily.
It only supports what we call “standard PLONK”,
where you have a single type of add-multiply gate as I will explain (see also here).

To use it you should build barretenberg as explained in the README; but use this branch).

Now in the build directory, you will create a file called constraintsystem.json

Here is an example of what to put in it:
{"varnum":4,"pubvarnum":0,"constraintnum":1,"constraints":[{"a":1,"b":2,"c":3,"ql":1,"qr":1,"qm":0,"qo":-1,"qc":0}]}

varnum - is the total variable numbers
pubvarnum - is the number of public variables (though this will be ignored for now)
constraintnum - is the total number of constraints.
constraints - is an array describing the constraints, where each constraint has the following form:
a,b,c are integers describing the indices of the witness variables to be used in the constraint.
Suppose the variables used in the constraint are x,y,z.
The constraint itself has the form
q_l\cdot x + q_r\cdot y + q_o\cdot z + q_m\cdot x\cdot y + q_c =0.
For the constants ql,qr,qo,qm,qc of the constraint.
Thus in the above example the constraint is x+y=z.

Now in the build directory, also create a file witness.json of the following form, for example:
{"witness":[0,1,2,3]}

For technical reasons, the vector must always start with a zero, followed by the actual witness values.

Note that this sequence of values indeed satisfies the constraint.

Now to generate a proof and verify it, run from the same directory:
./src/aztec/plonk/composer/composer_tests --gtest_filter=standard_composer. prove_and_verify_from_file

You can try modifying the witness so that the last value is not the sum of the former two, and see if it rejects.

You can make your own examples, but here is one more for the pair of constraints x+y=z, y\cdot z +1=w:

{"varnum":4,"pubvarnum":0,"constraintnum":2,"constraints":[{"a":1,"b":2,"c":3,"ql":1,"qr":1,"qm":0,"qo":-1,"qc":0},{"a":2,"b":3,"c":4,"ql":0,"qr":0,"qm":1,"qo":-1,"qc":1}]}

{"witness":[0,1,2,3,7]}

Separate proving and verifying

Suppose you want to write the proof into a file, instead of proving and verifying in one test.
With the same files witness.json and constraintsystem.json in the directory, use:

./src/aztec/plonk/composer/composer_tests --gtest_filter=standard_composer. write_proof_and_keys_to_file

This will write the proof, proving key and verifying key to the files proof.data,pk.data,vk.data respectively.

To verify, you must add a file PI.json with public inputs, e.g.
{"pubvarnum":1,"PI":[1]}

And now run

./src/aztec/plonk/composer/composer_tests --gtest_filter=standard_composer. write_proof_and_keys_to_file

You are encouraged to try and tinker with the test code.

3 Likes

Hi,

Is the purpose of the json to serialise the circuit representation? I am just thinking that it is rather unwieldy and difficult to read.

I was thinking that standard software abstractions in terms of standard circuit components is much easier to read and reason about.

Hence, my take on this is that it is more productive to define a json representation for gates and gate configs, rather than circuits.

I have been designing such an interface in Rust that can be converted into a json representation.

Here is a preliminary look via an example.

fn arith_gate() -> Gate {
    gateCfgs = GateConfigRegistry::new()
        .insert("mul".to_string(), [
            ("m", Field::one()),
            ("l", Field::zero()),
            ("r", Field::zero()),
            ("o", -Field::one())
        ])
        .insert("add".to_string(), [
            ("m", Field::zero()),
            ("l", Field::one()),
            ("r", Field::one()),
            ("o", -Field::one()),
        ]);

    monomials = vec![
        GateMonomial{sel: vec!["m"], wit: vec![0, 1]},
        GateMonomial{sel: vec!["l"], wit: vec![0]},
        GateMonomial{sel: vec!["r"], wit: vec![1]},
        GateMonomial{sel: vec!["o"], wit: vec![2]},
    ];

    Gate {
        deg: 3,
        width: 3,
        params: params,
        selectorWires: from_list(["m", "l", "r", "o"]),
        data: monomials,
        cfgRegistry: gateCfgs,
    }
}

fn circuit() {
    let c = PlonkCircuit::new_with_expected_size(1 << 20);
    c.register_gate("arith", std_gates::arith_gate());

    c.add_constraint(ConstraintData {
            gateName: "arith",
            gateConfig: Some("mul"),
            inputs: c.get_inputs(["x", "y", "z"]),
            dynamicConfig: None,
        }
    );
}
1 Like

Hey…first of all, I’m curious, is your interface part of another plonk implementation?
The json format is not the easiest way to understand the circuit I guess.
It’s first and main goal is to allow people to use barretenberg without needing to delve into the C++.
Moreover, this format of constraints with the single add-multiply gate is what the standard plonk prover can “eat”
So I hope people will write compilers into this format, e.g. from circom, or from your interface

Hi, yes, I am writing a (turbo)plonk implementation for the zexe library. My hope is to add plookup support as well.

By the way, is my understanding correct - that to combine plookup with plonk, one should just use a permutation polynomial to get values lying in the polys a, b, c to agree with some values lying in f, and then perform the plookup argument only for f? Meaning to say, one should separate the plookup wires from non-plookup wires, right? Using multi-table, one only needs one f.

Hmm, I see, I think it’s not a bad idea. But a bit of abstraction can go a long way to make it more pleasant to experiment with.

1 Like

Hey,
Full details of our plookup integration are still wip, but your idea is good; in some cases we use something close to what you are saying.

Like I said, I definitely hope people will write high level tools that will compile to this format.