circom_prover/prover/ark_circom/
r1cs_reader.rs

1// This file is copied from https://github.dev/zkmopro/circom-compat/tree/wasm-delete
2
3//! R1CS circom file reader
4//! Copied from <https://github.com/poma/zkutil>
5//! Spec: <https://github.com/iden3/r1csfile/blob/master/doc/r1cs_bin_format.md>
6use byteorder::{LittleEndian, ReadBytesExt};
7use std::io::{Error, ErrorKind};
8
9use ark_ec::pairing::Pairing;
10use ark_serialize::{CanonicalDeserialize, SerializationError, SerializationError::IoError};
11use ark_std::io::{Read, Seek, SeekFrom};
12
13use std::collections::HashMap;
14
15type IoResult<T> = Result<T, SerializationError>;
16
17use super::{ConstraintVec, Constraints};
18
19#[derive(Clone, Debug)]
20pub struct R1CS<E: Pairing> {
21    pub num_inputs: usize,
22    pub num_aux: usize,
23    pub num_variables: usize,
24    pub constraints: Vec<Constraints<E>>,
25    pub wire_mapping: Option<Vec<usize>>,
26}
27
28impl<E: Pairing> From<R1CSFile<E>> for R1CS<E> {
29    fn from(file: R1CSFile<E>) -> Self {
30        let num_inputs = (1 + file.header.n_pub_in + file.header.n_pub_out) as usize;
31        let num_variables = file.header.n_wires as usize;
32        let num_aux = num_variables - num_inputs;
33        R1CS {
34            num_aux,
35            num_inputs,
36            num_variables,
37            constraints: file.constraints,
38            wire_mapping: Some(file.wire_mapping.iter().map(|e| *e as usize).collect()),
39        }
40    }
41}
42
43pub struct R1CSFile<E: Pairing> {
44    pub version: u32,
45    pub header: Header,
46    pub constraints: Vec<Constraints<E>>,
47    pub wire_mapping: Vec<u64>,
48}
49
50impl<E: Pairing> R1CSFile<E> {
51    /// reader must implement the Seek trait, for example with a Cursor
52    ///
53    /// ```rust,ignore
54    /// let reader = BufReader::new(Cursor::new(&data[..]));
55    /// ```
56    pub fn new<R: Read + Seek>(mut reader: R) -> IoResult<R1CSFile<E>> {
57        let mut magic = [0u8; 4];
58        reader.read_exact(&mut magic)?;
59        if magic != [0x72, 0x31, 0x63, 0x73] {
60            return Err(IoError(Error::new(
61                ErrorKind::InvalidData,
62                "Invalid magic number",
63            )));
64        }
65
66        let version = reader.read_u32::<LittleEndian>()?;
67        if version != 1 {
68            return Err(IoError(Error::new(
69                ErrorKind::InvalidData,
70                "Unsupported version",
71            )));
72        }
73
74        let num_sections = reader.read_u32::<LittleEndian>()?;
75
76        // todo: handle sec_size correctly
77        // section type -> file offset
78        let mut sec_offsets = HashMap::<u32, u64>::new();
79        let mut sec_sizes = HashMap::<u32, u64>::new();
80
81        // get file offset of each section
82        for _ in 0..num_sections {
83            let sec_type = reader.read_u32::<LittleEndian>()?;
84            let sec_size = reader.read_u64::<LittleEndian>()?;
85            let offset = reader.stream_position()?;
86            sec_offsets.insert(sec_type, offset);
87            sec_sizes.insert(sec_type, sec_size);
88            reader.seek(SeekFrom::Current(sec_size as i64))?;
89        }
90
91        let header_type = 1;
92        let constraint_type = 2;
93        let wire2label_type = 3;
94
95        let header_offset = sec_offsets.get(&header_type).ok_or_else(|| {
96            Error::new(
97                ErrorKind::InvalidData,
98                "No section offset for header type found",
99            )
100        });
101
102        reader.seek(SeekFrom::Start(*header_offset?))?;
103
104        let header_size = sec_sizes.get(&header_type).ok_or_else(|| {
105            Error::new(
106                ErrorKind::InvalidData,
107                "No section size for header type found",
108            )
109        });
110
111        let header = Header::new(&mut reader, *header_size?)?;
112
113        let constraint_offset = sec_offsets.get(&constraint_type).ok_or_else(|| {
114            Error::new(
115                ErrorKind::InvalidData,
116                "No section offset for constraint type found",
117            )
118        });
119
120        reader.seek(SeekFrom::Start(*constraint_offset?))?;
121
122        let constraint_size = sec_sizes.get(&constraint_type).ok_or_else(|| {
123            Error::new(
124                ErrorKind::InvalidData,
125                "No section size for constraint type found",
126            )
127        });
128
129        let constraints = read_constraints::<&mut R, E>(&mut reader, *constraint_size?, &header)?;
130
131        let wire2label_offset = sec_offsets.get(&wire2label_type).ok_or_else(|| {
132            Error::new(
133                ErrorKind::InvalidData,
134                "No section offset for wire2label type found",
135            )
136        });
137
138        reader.seek(SeekFrom::Start(*wire2label_offset?))?;
139
140        let wire2label_size = sec_sizes.get(&wire2label_type).ok_or_else(|| {
141            Error::new(
142                ErrorKind::InvalidData,
143                "No section size for wire2label type found",
144            )
145        });
146
147        let wire_mapping = read_map(&mut reader, *wire2label_size?, &header)?;
148
149        Ok(R1CSFile {
150            version,
151            header,
152            constraints,
153            wire_mapping,
154        })
155    }
156}
157
158pub struct Header {
159    pub field_size: u32,
160    pub prime_size: Vec<u8>,
161    pub n_wires: u32,
162    pub n_pub_out: u32,
163    pub n_pub_in: u32,
164    pub n_prv_in: u32,
165    pub n_labels: u64,
166    pub n_constraints: u32,
167}
168
169impl Header {
170    fn new<R: Read>(mut reader: R, size: u64) -> IoResult<Header> {
171        let field_size = reader.read_u32::<LittleEndian>()?;
172        if field_size != 32 {
173            return Err(IoError(Error::new(
174                ErrorKind::InvalidData,
175                "This parser only supports 32-byte fields",
176            )));
177        }
178
179        if size != 32 + field_size as u64 {
180            return Err(IoError(Error::new(
181                ErrorKind::InvalidData,
182                "Invalid header section size",
183            )));
184        }
185
186        let mut prime_size = vec![0u8; field_size as usize];
187        reader.read_exact(&mut prime_size)?;
188
189        if prime_size
190            != hex_literal::hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430")
191        {
192            return Err(IoError(Error::new(
193                ErrorKind::InvalidData,
194                "This parser only supports bn256",
195            )));
196        }
197
198        Ok(Header {
199            field_size,
200            prime_size,
201            n_wires: reader.read_u32::<LittleEndian>()?,
202            n_pub_out: reader.read_u32::<LittleEndian>()?,
203            n_pub_in: reader.read_u32::<LittleEndian>()?,
204            n_prv_in: reader.read_u32::<LittleEndian>()?,
205            n_labels: reader.read_u64::<LittleEndian>()?,
206            n_constraints: reader.read_u32::<LittleEndian>()?,
207        })
208    }
209}
210
211fn read_constraint_vec<R: Read, E: Pairing>(mut reader: R) -> IoResult<ConstraintVec<E>> {
212    let n_vec = reader.read_u32::<LittleEndian>()? as usize;
213    let mut vec = Vec::with_capacity(n_vec);
214    for _ in 0..n_vec {
215        vec.push((
216            reader.read_u32::<LittleEndian>()? as usize,
217            E::ScalarField::deserialize_uncompressed(&mut reader)?,
218        ));
219    }
220    Ok(vec)
221}
222
223fn read_constraints<R: Read, E: Pairing>(
224    mut reader: R,
225    size: u64,
226    header: &Header,
227) -> IoResult<Vec<Constraints<E>>> {
228    // Calculate expected size: each constraint has 3 constraint vectors (A, B, C)
229    // Each constraint vector has a variable number of terms, but we need to read the data to validate
230    // For now, we'll implement a basic size check to ensure we don't read beyond the section
231    let expected_min_size = header.n_constraints as u64 * 12; // Minimum 4 bytes per constraint vector count (3 vectors per constraint)
232    if size < expected_min_size {
233        return Err(IoError(Error::new(
234            ErrorKind::InvalidData,
235            "Invalid constraints section size: too small for expected number of constraints",
236        )));
237    }
238    let mut vec = Vec::with_capacity(header.n_constraints as usize);
239    for _ in 0..header.n_constraints {
240        vec.push((
241            read_constraint_vec::<&mut R, E>(&mut reader)?,
242            read_constraint_vec::<&mut R, E>(&mut reader)?,
243            read_constraint_vec::<&mut R, E>(&mut reader)?,
244        ));
245    }
246    Ok(vec)
247}
248
249fn read_map<R: Read>(mut reader: R, size: u64, header: &Header) -> IoResult<Vec<u64>> {
250    if size != header.n_wires as u64 * 8 {
251        return Err(IoError(Error::new(
252            ErrorKind::InvalidData,
253            "Invalid map section size",
254        )));
255    }
256    let mut vec = Vec::with_capacity(header.n_wires as usize);
257    for _ in 0..header.n_wires {
258        vec.push(reader.read_u64::<LittleEndian>()?);
259    }
260    if vec[0] != 0 {
261        return Err(IoError(Error::new(
262            ErrorKind::InvalidData,
263            "Wire 0 should always be mapped to 0",
264        )));
265    }
266    Ok(vec)
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272    use ark_bn254::{Bn254, Fr};
273    use ark_std::io::{BufReader, Cursor};
274
275    #[test]
276    fn sample() {
277        let data = hex_literal::hex!(
278            "
279        72316373
280        01000000
281        03000000
282        01000000 40000000 00000000
283        20000000
284        010000f0 93f5e143 9170b979 48e83328 5d588181 b64550b8 29a031e1 724e6430
285        07000000
286        01000000
287        02000000
288        03000000
289        e8030000 00000000
290        03000000
291        02000000 88020000 00000000
292        02000000
293        05000000 03000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
294        06000000 08000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
295        03000000
296        00000000 02000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
297        02000000 14000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
298        03000000 0C000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
299        02000000
300        00000000 05000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
301        02000000 07000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
302        03000000
303        01000000 04000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
304        04000000 08000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
305        05000000 03000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
306        02000000
307        03000000 2C000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
308        06000000 06000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
309        00000000
310        01000000
311        06000000 04000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
312        03000000
313        00000000 06000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
314        02000000 0B000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
315        03000000 05000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
316        01000000
317        06000000 58020000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
318        03000000 38000000 00000000
319        00000000 00000000
320        03000000 00000000
321        0a000000 00000000
322        0b000000 00000000
323        0c000000 00000000
324        0f000000 00000000
325        44010000 00000000
326    "
327        );
328
329        let reader = BufReader::new(Cursor::new(&data[..]));
330        let file = R1CSFile::<Bn254>::new(reader).unwrap();
331        assert_eq!(file.version, 1);
332
333        assert_eq!(file.header.field_size, 32);
334        assert_eq!(
335            file.header.prime_size,
336            hex_literal::hex!("010000f093f5e1439170b97948e833285d588181b64550b829a031e1724e6430")
337        );
338        assert_eq!(file.header.n_wires, 7);
339        assert_eq!(file.header.n_pub_out, 1);
340        assert_eq!(file.header.n_pub_in, 2);
341        assert_eq!(file.header.n_prv_in, 3);
342        assert_eq!(file.header.n_labels, 0x03e8);
343        assert_eq!(file.header.n_constraints, 3);
344
345        assert_eq!(file.constraints.len(), 3);
346        assert_eq!(file.constraints[0].0.len(), 2);
347        assert_eq!(file.constraints[0].0[0].0, 5);
348        assert_eq!(file.constraints[0].0[0].1, Fr::from(3));
349        assert_eq!(file.constraints[2].1[0].0, 0);
350        assert_eq!(file.constraints[2].1[0].1, Fr::from(6));
351        assert_eq!(file.constraints[1].2.len(), 0);
352
353        assert_eq!(file.wire_mapping.len(), 7);
354        assert_eq!(file.wire_mapping[1], 3);
355    }
356}