rings_snark/r1cs/
reader.rs

1//! Module for load r1cs and witness
2//! ================
3//! Copyright (c) 2022 Nalin
4//! Copyright (c) Lurk Lab
5//! Copyright (c) 2024 Rings Network
6//! SPDX-License-Identifier: MIT
7//!
8//! Contributors:
9//!
10//! - Hanting Zhang (winston@lurk-lab.com)
11//!   - Adapted the original work here: https://!github.com/nalinbhardwaj/Nova-Scotia/blob/main/src/circom/reader.rs
12//!
13//! - Rings Network Dev Team
14//!   - refine the original work here: <https://github.com/lurk-lab/circom-scotia>
15
16use std::collections::BTreeMap;
17use std::collections::HashMap;
18use std::fs::File;
19use std::fs::OpenOptions;
20use std::io::BufReader;
21use std::io::Read;
22use std::io::Seek;
23use std::io::SeekFrom;
24use std::path::Path;
25
26use byteorder::LittleEndian;
27use byteorder::ReadBytesExt;
28use crypto_bigint::U256;
29use ff::PrimeField;
30use itertools::Itertools;
31use serde::Deserialize;
32use serde::Serialize;
33
34use crate::error::Error;
35use crate::error::Result;
36use crate::r1cs::Constraint;
37use crate::r1cs::R1CS;
38
39/// Represents the JSON structure of a Circuit.
40/// This struct is used for deserializing the JSON representation of a circuit into a Rust data structure.
41#[derive(Serialize, Deserialize)]
42pub(crate) struct CircuitJson {
43    constraints: Vec<Vec<BTreeMap<String, String>>>,
44    #[serde(rename = "nPubInputs")]
45    num_inputs: usize,
46    #[serde(rename = "nOutputs")]
47    num_outputs: usize,
48    #[serde(rename = "nVars")]
49    num_variables: usize,
50}
51
52/// Represents the header of an R1CS file.
53/// Contains metadata about the R1CS file, such as the size of the field, number of wires, and constraints.
54#[allow(dead_code)]
55#[derive(Debug, Default)]
56struct Header {
57    field_size: u32,
58    prime_size: Vec<u8>,
59    n_wires: u32,
60    n_pub_out: u32,
61    n_pub_in: u32,
62    n_prv_in: u32,
63    n_labels: u64,
64    n_constraints: u32,
65}
66
67/// Represents the content of an R1CS file.
68/// Encapsulates the data read from an R1CS file, including the version, header information, constraints, and wire mapping.
69#[allow(dead_code)]
70#[derive(Debug, Default)]
71pub struct R1CSFile<F: PrimeField> {
72    version: u32,
73    header: Header,
74    constraints: Vec<Constraint<F>>,
75    wire_mapping: Vec<u64>,
76}
77
78/// load witness file by filename with autodetect encoding (bin or json).
79pub fn load_witness_from_file<Fr: PrimeField>(filename: impl AsRef<Path>) -> Vec<Fr> {
80    if filename.as_ref().ends_with("json") {
81        load_witness_from_json_file::<Fr>(filename)
82    } else {
83        load_witness_from_bin_file::<Fr>(filename)
84    }
85}
86
87/// load witness from bin file by filename
88pub fn load_witness_from_bin_file<Fr: PrimeField>(filename: impl AsRef<Path>) -> Vec<Fr> {
89    let reader = OpenOptions::new()
90        .read(true)
91        .open(filename)
92        .expect("unable to open.");
93    load_witness_from_bin_reader::<Fr, BufReader<File>>(BufReader::new(reader))
94        .expect("read witness failed")
95}
96
97/// load witness from u8 array by a reader
98pub fn load_witness_from_bin_reader<Fr: PrimeField, R: Read>(mut reader: R) -> Result<Vec<Fr>> {
99    let mut wtns_header = [0u8; 4];
100    reader.read_exact(&mut wtns_header)?;
101    if wtns_header != [119, 116, 110, 115] {
102        // ruby -e 'p "wtns".bytes' => [119, 116, 110, 115]
103        return Err(Error::LoadR1CS("invalid file header".to_string()));
104    }
105    let version = reader.read_u32::<LittleEndian>()?;
106    // println!("wtns version {}", version);
107    if version > 2 {
108        return Err(Error::LoadR1CS("unsupported file version".to_string()));
109    }
110    let num_sections = reader.read_u32::<LittleEndian>()?;
111    if num_sections != 2 {
112        return Err(Error::LoadR1CS("invalid num sections".to_string()));
113    }
114    // read the first section
115    let sec_type = reader.read_u32::<LittleEndian>()?;
116    if sec_type != 1 {
117        return Err(Error::LoadR1CS("invalid section type".to_string()));
118    }
119    let sec_size = reader.read_u64::<LittleEndian>()?;
120    if sec_size != 4 + 32 + 4 {
121        return Err(Error::LoadR1CS("invalid section len".to_string()));
122    }
123    let field_size = reader.read_u32::<LittleEndian>()?;
124    if field_size != 32 {
125        return Err(Error::LoadR1CS("invalid field byte size".to_string()));
126    }
127    let mut prime = vec![0u8; field_size as usize];
128    reader.read_exact(&mut prime)?;
129    // if prime != hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430".to_string()) {
130    //     return Error::LoadR1CS("invalid curve prime {:?}", prime);
131    // }
132    let witness_len = reader.read_u32::<LittleEndian>()?;
133    //println!("witness len {}", witness_len);
134    let sec_type = reader.read_u32::<LittleEndian>()?;
135    if sec_type != 2 {
136        return Err(Error::LoadR1CS("invalid section type".to_string()));
137    }
138    let sec_size = reader.read_u64::<LittleEndian>()?;
139    if sec_size != u64::from(witness_len * field_size) {
140        return Err(Error::LoadR1CS(format!(
141            "invalid witness section size {}",
142            sec_size
143        )));
144    }
145    let mut result = Vec::with_capacity(witness_len as usize);
146    for _ in 0..witness_len {
147        result.push(read_field::<&mut R, Fr>(&mut reader)?);
148    }
149    Ok(result)
150}
151
152/// load witness from json file by filename
153pub fn load_witness_from_json_file<Fr: PrimeField>(filename: impl AsRef<Path>) -> Vec<Fr> {
154    let reader = OpenOptions::new()
155        .read(true)
156        .open(filename)
157        .expect("unable to open.");
158    load_witness_from_json::<Fr, BufReader<File>>(BufReader::new(reader))
159}
160
161/// load witness from json by a reader
162pub fn load_witness_from_json<Fr: PrimeField, R: Read>(reader: R) -> Vec<Fr> {
163    let witness: Vec<String> = serde_json::from_reader(reader).expect("unable to read.");
164    witness
165        .into_iter()
166        .map(|x| Fr::from_str_vartime(&x).unwrap())
167        .collect::<Vec<Fr>>()
168}
169
170/// load r1cs from bin file by filename
171pub fn load_r1cs_from_bin_file<F: PrimeField>(filename: impl AsRef<Path>) -> R1CS<F> {
172    let reader = OpenOptions::new()
173        .read(true)
174        .open(filename.as_ref())
175        .unwrap_or_else(|_| panic!("unable to open {:?}", filename.as_ref()));
176    load_r1cs_from_bin(BufReader::new(reader))
177}
178
179fn read_field<R: Read, Fr: PrimeField>(mut reader: R) -> Result<Fr> {
180    let mut repr = Fr::ZERO.to_repr();
181    for digit in repr.as_mut().iter_mut() {
182        // TODO: may need to reverse order?
183        *digit = reader.read_u8()?;
184    }
185    let fr = Fr::from_repr(repr).unwrap();
186    Ok(fr)
187}
188
189fn read_header<R: Read>(mut reader: R, size: u64, expected_prime: &str) -> Result<Header> {
190    let field_size = reader.read_u32::<LittleEndian>()?;
191
192    if size != 32 + u64::from(field_size) {
193        return Err(Error::InvalidDataWhenReadingR1CS(
194            "Invalid header section size".to_string(),
195        ));
196    }
197
198    let mut prime_size = vec![0u8; field_size as usize];
199    reader.read_exact(&mut prime_size)?;
200    let prime = U256::from_le_slice(&prime_size);
201    let prime = &prime.to_string().to_ascii_lowercase();
202
203    if prime != &expected_prime[2..] {
204        // get rid of '0x' in the front
205        return Err(Error::InvalidDataWhenReadingR1CS(format!("Mismatched prime field. Expected {expected_prime}, read {prime} in the header instead.")));
206    }
207
208    Ok(Header {
209        field_size,
210        prime_size,
211        n_wires: reader.read_u32::<LittleEndian>()?,
212        n_pub_out: reader.read_u32::<LittleEndian>()?,
213        n_pub_in: reader.read_u32::<LittleEndian>()?,
214        n_prv_in: reader.read_u32::<LittleEndian>()?,
215        n_labels: reader.read_u64::<LittleEndian>()?,
216        n_constraints: reader.read_u32::<LittleEndian>()?,
217    })
218}
219
220fn read_constraint_vec<R: Read, Fr: PrimeField>(
221    mut reader: R,
222    _header: &Header,
223) -> Result<Vec<(usize, Fr)>> {
224    let n_vec = reader.read_u32::<LittleEndian>()? as usize;
225    let mut vec = Vec::with_capacity(n_vec);
226    for _ in 0..n_vec {
227        vec.push((
228            reader.read_u32::<LittleEndian>()? as usize,
229            read_field::<&mut R, Fr>(&mut reader)?,
230        ));
231    }
232    Ok(vec)
233}
234
235fn read_constraints<R: Read, Fr: PrimeField>(
236    mut reader: R,
237    _size: u64,
238    header: &Header,
239) -> Result<Vec<Constraint<Fr>>> {
240    // todo check section size
241    let mut vec = Vec::with_capacity(header.n_constraints as usize);
242    for _ in 0..header.n_constraints {
243        vec.push((
244            read_constraint_vec::<&mut R, Fr>(&mut reader, header)?,
245            read_constraint_vec::<&mut R, Fr>(&mut reader, header)?,
246            read_constraint_vec::<&mut R, Fr>(&mut reader, header)?,
247        ));
248    }
249    Ok(vec)
250}
251
252fn read_map<R: Read>(mut reader: R, size: u64, header: &Header) -> Result<Vec<u64>> {
253    if size != u64::from(header.n_wires) * 8 {
254        return Err(Error::InvalidDataWhenReadingR1CS(
255            "Invalid map section size".to_string(),
256        ));
257    }
258    let mut vec = Vec::with_capacity(header.n_wires as usize);
259    for _ in 0..header.n_wires {
260        vec.push(reader.read_u64::<LittleEndian>()?);
261    }
262    if vec[0] != 0 {
263        return Err(Error::InvalidDataWhenReadingR1CS(
264            "Wire 0 should always be mapped to 0".to_string(),
265        ));
266    }
267    Ok(vec)
268}
269
270fn from_reader<Fr: PrimeField, R: Read + Seek>(mut reader: R) -> Result<R1CSFile<Fr>> {
271    let mut magic = [0u8; 4];
272    reader.read_exact(&mut magic)?;
273    if magic != [0x72, 0x31, 0x63, 0x73] {
274        // magic = "r1cs"
275        return Err(Error::InvalidDataWhenReadingR1CS(
276            "Invalid magic number".to_string(),
277        ));
278    }
279
280    let version = reader.read_u32::<LittleEndian>()?;
281    if version != 1 {
282        return Err(Error::InvalidDataWhenReadingR1CS(
283            "Unsupported version".to_string(),
284        ));
285    }
286
287    let num_sections = reader.read_u32::<LittleEndian>()?;
288
289    // section type -> file offset
290    let mut section_offsets = HashMap::<u32, u64>::new();
291    let mut section_sizes = HashMap::<u32, u64>::new();
292
293    // get file offset of each section
294    for _ in 0..num_sections {
295        let section_type = reader.read_u32::<LittleEndian>()?;
296        let section_size = reader.read_u64::<LittleEndian>()?;
297        let offset = reader.stream_position()?;
298        section_offsets.insert(section_type, offset);
299        section_sizes.insert(section_type, section_size);
300        reader.seek(SeekFrom::Current(section_size as i64))?;
301    }
302
303    let header_type = 1;
304    let constraint_type = 2;
305    let wire2label_type = 3;
306
307    reader.seek(SeekFrom::Start(*section_offsets.get(&header_type).unwrap()))?;
308    let header = read_header(
309        &mut reader,
310        *section_sizes.get(&header_type).unwrap(),
311        Fr::MODULUS,
312    )?;
313    if header.field_size != 32 {
314        return Err(Error::InvalidDataWhenReadingR1CS(
315            "This parser only supports 32-byte fields".to_string(),
316        ));
317    }
318    // if header.prime_size != hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430".to_string()) {
319    //     return Err(Error::new(ErrorKind::InvalidData, "This parser only supports bn256".to_string()));
320    // }
321
322    reader.seek(SeekFrom::Start(
323        *section_offsets.get(&constraint_type).unwrap(),
324    ))?;
325    let constraints = read_constraints::<&mut R, Fr>(
326        &mut reader,
327        *section_sizes.get(&constraint_type).unwrap(),
328        &header,
329    )?;
330
331    reader.seek(SeekFrom::Start(
332        *section_offsets.get(&wire2label_type).unwrap(),
333    ))?;
334    let wire_mapping = read_map(
335        &mut reader,
336        *section_sizes.get(&wire2label_type).unwrap(),
337        &header,
338    )?;
339
340    Ok(R1CSFile {
341        version,
342        header,
343        constraints,
344        wire_mapping,
345    })
346}
347
348/// load r1cs from bin by a reader
349pub fn load_r1cs_from_bin<Fr: PrimeField, R: Read + Seek>(reader: R) -> R1CS<Fr> {
350    let file = from_reader(reader).expect("unable to read.");
351    let num_inputs = (1 + file.header.n_pub_in + file.header.n_pub_out) as usize;
352    let num_variables = file.header.n_wires as usize;
353    let num_aux = num_variables - num_inputs;
354    R1CS {
355        num_aux,
356        num_inputs,
357        num_variables,
358        constraints: file.constraints,
359    }
360}
361
362/// load r1cs file by filename with autodetect encoding (bin or json)
363pub fn load_r1cs<Fr: PrimeField>(filename: impl AsRef<Path>) -> R1CS<Fr> {
364    if filename.as_ref().ends_with("json") {
365        load_r1cs_from_json_file(filename)
366    } else {
367        load_r1cs_from_bin_file(filename)
368    }
369}
370
371/// load r1cs from json file by filename
372pub fn load_r1cs_from_json_file<Fr: PrimeField>(filename: impl AsRef<Path>) -> R1CS<Fr> {
373    let reader = OpenOptions::new()
374        .read(true)
375        .open(filename)
376        .expect("unable to open.");
377    load_r1cs_from_json(BufReader::new(reader))
378}
379
380/// load r1cs from json by a reader
381pub fn load_r1cs_from_json<Fr: PrimeField, R: Read>(reader: R) -> R1CS<Fr> {
382    let circuit_json: CircuitJson = serde_json::from_reader(reader).expect("unable to read.");
383
384    let num_inputs = circuit_json.num_inputs + circuit_json.num_outputs + 1;
385    let num_aux = circuit_json.num_variables - num_inputs;
386
387    let convert_constraint = |lc: &BTreeMap<String, String>| {
388        lc.iter()
389            .map(|(index, coeff)| (index.parse().unwrap(), Fr::from_str_vartime(coeff).unwrap()))
390            .collect_vec()
391    };
392
393    let constraints = circuit_json
394        .constraints
395        .iter()
396        .map(|c| {
397            (
398                convert_constraint(&c[0]),
399                convert_constraint(&c[1]),
400                convert_constraint(&c[2]),
401            )
402        })
403        .collect_vec();
404
405    R1CS {
406        num_inputs,
407        num_aux,
408        num_variables: circuit_json.num_variables,
409        constraints,
410    }
411}