Middleware to compile Circom circuits to Bellpepper
This repository provides necessary middleware to take generated output of the Circom compiler (R1CS constraints and generated witnesses) and use them with Bellperson. It is based off the work of Nova-Scotia and Arkworks' Circom-Compat. Please see Credits at the bottom for proper credits towards the various works used here.
To use it yourself, install version 2.1.6 or greater of Circom. Refer to the Circom documentation for more information.
When you're ready, compile your circuit using circom [file].circom --r1cs --wasm --prime vesta
for the vesta curve. We will later use the R1CS file ([file].r1cs
) and the witness generator ([file]_js/[file].wasm
), so make note of their filepaths. You can independently test these circuits by running witness generation as described in the Circom documentation.
Now, start a new Rust project and add Circom Scotia (cargo add circom-scotia
) to your dependencies. Then, you can start using your Circom circuits with Bellperson. Start by defining the paths to the Circom output and loading the R1CS file and witness generator:
let root = current_dir().unwrap().join("examples/sha256");
let wtns = root.join("sha256.wasm");
let r1cs = root.join("sha256.r1cs");
let cfg = CircomConfig::new(wtns, r1cs).unwrap();
Then, create the inputs, which must be in the shape that the original Circom project expected them to be in, and calculate the witness. The original Circom input would've looked something like this:
{ "arg_in": ["a", "b"] }
So, we reflect the same names and dimensions of input. The witness can then be calculated by providing the loaded configuration and the input.
let arg_in = ("arg_in".into(), vec![Fr::ZERO, Fr::ZERO]);
let input = vec![arg_in];
let witness = calculate_witness(&cfg, input, true).unwrap();
Now, setup a test constraint system, and synthesize the generated witness with the r1cs
information we previously loaded.
let mut cs = TestConstraintSystem::<Fr>::new();
let output = synthesize(
&mut cs.namespace(|| "sha256_circom"),
cfg.r1cs.clone(),
Some(witness),
);
Finally, we can compare our expected output and confirm that the circuit is indeed satisfied.
let expected = "0x00000000008619b3767c057fdf8e6d99fde2680c5d8517eb06761c0878d40c40";
let output_num = format!("{:?}", output.unwrap().get_value().unwrap());
assert!(output_num == expected);
assert!(cs.is_satisfied());
For the full code, see the sha256.rs
example.
- Generic big integers were replaced by
U256
, so currently we do not generalize to prime field of size greater than 256 bits.
Credits to the Circom language from the iden3 team.
The parsing and generation borrows judiciously from Nova-Scotia and ark-circom, respectively. All the loading code is essentially copied over. The wasmer
witness generator was copied, then retrofitted for support without arkworks
libraries such as ark-ff
or ark-bignum
; these were replaced with ff
and crypto-bignum
. The other bits that glue everything together is original.