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