circom_scotia/
lib.rs

1// Copyright (c) 2022 Nalin
2// Copyright (c) Lurk Lab
3// SPDX-License-Identifier: MIT
4//
5// Contributors:
6//
7// - Hanting Zhang (winston@lurk-lab.com)
8//   - Adapted the original work here: https://github.com/nalinbhardwaj/Nova-Scotia/blob/main/src/circom
9//   - Retrofitted to support `wasmer` witness generation.
10
11use std::{
12    env::current_dir,
13    fs,
14    path::{Path, PathBuf},
15    process::Command,
16};
17
18use bellpepper_core::{num::AllocatedNum, ConstraintSystem, LinearCombination, SynthesisError};
19use color_eyre::Result;
20use ff::PrimeField;
21use r1cs::{CircomConfig, R1CS};
22
23use crate::reader::load_witness_from_file;
24
25pub mod r1cs;
26pub mod reader;
27pub mod witness;
28
29pub fn generate_witness_from_wasm<F: PrimeField>(
30    witness_dir: PathBuf,
31    witness_input_json: String,
32    witness_output: impl AsRef<Path>,
33) -> Vec<F> {
34    let root = current_dir().unwrap();
35    let witness_generator_input = root.join("circom_input.json");
36    fs::write(&witness_generator_input, witness_input_json).unwrap();
37
38    let mut witness_js = witness_dir.clone();
39    witness_js.push("generate_witness.js");
40    let mut witness_wasm = witness_dir.clone();
41    witness_wasm.push("main.wasm");
42
43    let output = Command::new("node")
44        .arg(&witness_js)
45        .arg(&witness_wasm)
46        .arg(&witness_generator_input)
47        .arg(witness_output.as_ref())
48        .output()
49        .expect("failed to execute process");
50    if !output.stdout.is_empty() || !output.stderr.is_empty() {
51        print!("stdout: {}", std::str::from_utf8(&output.stdout).unwrap());
52        print!("stderr: {}", std::str::from_utf8(&output.stderr).unwrap());
53    }
54    let _ = fs::remove_file(witness_generator_input);
55    load_witness_from_file(witness_output)
56}
57
58/// TODO docs
59pub fn calculate_witness<F: PrimeField>(
60    cfg: &CircomConfig<F>,
61    input: Vec<(String, Vec<F>)>,
62    sanity_check: bool,
63) -> Result<Vec<F>> {
64    let mut lock = cfg.wtns.lock().unwrap();
65    let witness_calculator = &mut *lock;
66    witness_calculator.calculate_witness(input, sanity_check)
67}
68
69/// Reference work is Nota-Scotia: https://github.com/nalinbhardwaj/Nova-Scotia
70pub fn synthesize<F: PrimeField, CS: ConstraintSystem<F>>(
71    cs: &mut CS,
72    r1cs: R1CS<F>,
73    witness: Option<Vec<F>>,
74) -> Result<AllocatedNum<F>, SynthesisError> {
75    //println!("witness: {:?}", witness);
76    //println!("num_inputs: {:?}", r1cs.num_inputs);
77    //println!("num_aux: {:?}", r1cs.num_aux);
78    //println!("num_variables: {:?}", r1cs.num_variables);
79    //println!("num constraints: {:?}", r1cs.constraints.len());
80
81    let witness = &witness;
82
83    let mut vars: Vec<AllocatedNum<F>> = vec![];
84
85    for i in 1..r1cs.num_inputs {
86        let f: F = {
87            match witness {
88                None => F::ONE,
89                Some(w) => w[i],
90            }
91        };
92        let v = AllocatedNum::alloc(cs.namespace(|| format!("public_{}", i)), || Ok(f))?;
93
94        vars.push(v);
95    }
96
97    for i in 0..r1cs.num_aux {
98        // Private witness trace
99        let f: F = {
100            match witness {
101                None => F::ONE,
102                Some(w) => w[i + r1cs.num_inputs],
103            }
104        };
105
106        let v = AllocatedNum::alloc(cs.namespace(|| format!("aux_{}", i)), || Ok(f))?;
107        vars.push(v);
108    }
109
110    let output = vars[0].clone();
111
112    let make_lc = |lc_data: Vec<(usize, F)>| {
113        let res = lc_data.iter().fold(
114            LinearCombination::<F>::zero(),
115            |lc: LinearCombination<F>, (index, coeff)| {
116                lc + if *index > 0 {
117                    (*coeff, vars[*index - 1].get_variable())
118                } else {
119                    (*coeff, CS::one())
120                }
121            },
122        );
123        res
124    };
125
126    for (i, constraint) in r1cs.constraints.into_iter().enumerate() {
127        cs.enforce(
128            || format!("constraint {}", i),
129            |_| make_lc(constraint.0),
130            |_| make_lc(constraint.1),
131            |_| make_lc(constraint.2),
132        );
133    }
134
135    Ok(output)
136}