1#![doc = include_str!("../README.md")]
2pub mod qap;
16pub mod wtns;
17pub mod zkey;
18
19use std::io::Cursor;
20
21use ark_bn254::{Bn254, Fq, Fq2, Fr};
22use ark_ff::{BigInteger, PrimeField, UniformRand};
23use ark_groth16::{Groth16, PreparedVerifyingKey, Proof, ProvingKey, prepare_verifying_key};
24use ark_relations::gr1cs::SynthesisError;
25use ark_serialize::SerializationError;
26use curvy_witness::{WitnessError, WitnessGraph};
27use num_bigint::BigUint;
28use sha2::{Digest, Sha256};
29use thiserror::Error;
30
31use qap::CircomReduction;
32use wtns::WtnsError;
33use zkey::ZkeyMatrices;
34
35#[derive(Debug, Error)]
36pub enum ProverError {
37 #[error("expected zkey SHA-256 must be exactly 64 hexadecimal characters")]
38 InvalidExpectedHash,
39 #[error("zkey SHA-256 mismatch: expected {expected}, got {actual}")]
40 ZkeyHashMismatch { expected: String, actual: String },
41 #[error("invalid zkey: {0}")]
42 InvalidZkey(SerializationError),
43 #[error(transparent)]
44 InvalidWitness(#[from] WtnsError),
45 #[error(transparent)]
46 InvalidWitnessGraph(#[from] WitnessError),
47 #[error("witness assignment length mismatch: expected {expected}, got {actual}")]
48 AssignmentLength { expected: usize, actual: usize },
49 #[error("Groth16 proof generation failed: {0}")]
50 ProofGeneration(SynthesisError),
51 #[error("Groth16 verification failed: {0}")]
52 Verification(SynthesisError),
53 #[error("generated Groth16 proof did not verify")]
54 SelfVerificationFailed,
55}
56
57pub struct Prover {
59 pk: ProvingKey<Bn254>,
60 matrices: ZkeyMatrices<Fr>,
61 pvk: PreparedVerifyingKey<Bn254>,
62 assignment_size: usize,
63}
64
65impl Prover {
66 pub fn from_zkey_bytes(bytes: &[u8], expected_sha256: &str) -> Result<Self, ProverError> {
69 verify_sha256(bytes, expected_sha256)?;
70 let mut cursor = Cursor::new(bytes);
71 let (pk, matrices) = zkey::read_zkey(&mut cursor).map_err(ProverError::InvalidZkey)?;
72 let pvk = prepare_verifying_key(&pk.vk);
73 let assignment_size = pk.a_query.len();
74 Ok(Self {
75 pk,
76 matrices,
77 pvk,
78 assignment_size,
79 })
80 }
81
82 pub fn num_constraints(&self) -> usize {
83 self.matrices.num_constraints
84 }
85
86 pub fn num_public(&self) -> usize {
87 self.matrices.num_instance_variables.saturating_sub(1)
88 }
89
90 pub fn prove(&self, full_assignment: &[Fr]) -> Result<Proof<Bn254>, ProverError> {
91 self.validate_assignment(full_assignment)?;
92 let mut rng = ark_std::rand::rngs::OsRng;
93 let r = Fr::rand(&mut rng);
94 let s = Fr::rand(&mut rng);
95 Groth16::<Bn254, CircomReduction>::create_proof_with_reduction_and_matrices(
96 &self.pk,
97 r,
98 s,
99 &self.matrices.matrices,
100 self.matrices.num_instance_variables,
101 self.matrices.num_constraints,
102 full_assignment,
103 )
104 .map_err(ProverError::ProofGeneration)
105 }
106
107 pub fn public_inputs<'a>(&self, full_assignment: &'a [Fr]) -> Result<&'a [Fr], ProverError> {
108 self.validate_assignment(full_assignment)?;
109 Ok(&full_assignment[1..self.matrices.num_instance_variables])
110 }
111
112 pub fn verify(&self, proof: &Proof<Bn254>, public_inputs: &[Fr]) -> Result<bool, ProverError> {
113 Groth16::<Bn254>::verify_proof(&self.pvk, proof, public_inputs)
114 .map_err(ProverError::Verification)
115 }
116
117 pub fn prove_wtns(&self, bytes: &[u8]) -> Result<ProofBundle, ProverError> {
119 let assignment = wtns::read_wtns(bytes)?;
120 self.prove_assignment(&assignment)
121 }
122
123 pub fn prove_assignment(&self, assignment: &[Fr]) -> Result<ProofBundle, ProverError> {
125 let proof = self.prove(assignment)?;
126 let public_inputs = self.public_inputs(assignment)?;
127 if !self.verify(&proof, public_inputs)? {
128 return Err(ProverError::SelfVerificationFailed);
129 }
130 Ok(ProofBundle {
131 proof_json: proof_to_snarkjs_json(&proof),
132 public_signals_json: publics_to_json(public_inputs),
133 })
134 }
135
136 fn validate_assignment(&self, full_assignment: &[Fr]) -> Result<(), ProverError> {
137 self.validate_assignment_size(full_assignment.len())
138 }
139
140 fn validate_assignment_size(&self, actual: usize) -> Result<(), ProverError> {
141 if actual != self.assignment_size {
142 return Err(ProverError::AssignmentLength {
143 expected: self.assignment_size,
144 actual,
145 });
146 }
147 Ok(())
148 }
149}
150
151pub struct CircuitProver {
153 prover: Prover,
154 witness_graph: WitnessGraph,
155}
156
157impl CircuitProver {
158 pub fn from_artifacts(
159 zkey: &[u8],
160 expected_zkey_sha256: &str,
161 witness_graph: &[u8],
162 expected_graph_sha256: &str,
163 ) -> Result<Self, ProverError> {
164 let prover = Prover::from_zkey_bytes(zkey, expected_zkey_sha256)?;
165 let witness_graph = WitnessGraph::from_bytes(witness_graph, expected_graph_sha256)?;
166 prover.validate_assignment_size(witness_graph.assignment_size())?;
167 Ok(Self {
168 prover,
169 witness_graph,
170 })
171 }
172
173 pub fn num_constraints(&self) -> usize {
174 self.prover.num_constraints()
175 }
176
177 pub fn num_public(&self) -> usize {
178 self.prover.num_public()
179 }
180
181 pub fn r1cs_sha256(&self) -> [u8; 32] {
182 self.witness_graph.r1cs_sha256()
183 }
184
185 pub fn calculate_witness_json(&self, input_json: &str) -> Result<Vec<Fr>, ProverError> {
190 Ok(self.witness_graph.calculate_json(input_json)?)
191 }
192
193 pub fn prove_assignment(&self, assignment: &[Fr]) -> Result<ProofBundle, ProverError> {
195 self.prover.prove_assignment(assignment)
196 }
197
198 pub fn prove_json(&self, input_json: &str) -> Result<ProofBundle, ProverError> {
199 let assignment = self.calculate_witness_json(input_json)?;
200 self.prove_assignment(&assignment)
201 }
202}
203
204pub struct ProofBundle {
205 pub proof_json: String,
206 pub public_signals_json: String,
207}
208
209fn verify_sha256(bytes: &[u8], expected_sha256: &str) -> Result<(), ProverError> {
210 if expected_sha256.len() != 64 || !expected_sha256.bytes().all(|byte| byte.is_ascii_hexdigit())
211 {
212 return Err(ProverError::InvalidExpectedHash);
213 }
214 let expected = expected_sha256.to_ascii_lowercase();
215 let actual = Sha256::digest(bytes)
216 .iter()
217 .map(|byte| format!("{byte:02x}"))
218 .collect::<String>();
219 if actual != expected {
220 return Err(ProverError::ZkeyHashMismatch { expected, actual });
221 }
222 Ok(())
223}
224
225fn fq_dec(value: &Fq) -> String {
226 BigUint::from_bytes_be(&value.into_bigint().to_bytes_be()).to_str_radix(10)
227}
228
229fn fr_dec(value: &Fr) -> String {
230 BigUint::from_bytes_be(&value.into_bigint().to_bytes_be()).to_str_radix(10)
231}
232
233fn fq2_json(value: &Fq2) -> String {
234 format!("[\"{}\",\"{}\"]", fq_dec(&value.c0), fq_dec(&value.c1))
235}
236
237pub fn proof_to_snarkjs_json(proof: &Proof<Bn254>) -> String {
239 format!(
240 "{{\"pi_a\":[\"{}\",\"{}\",\"1\"],\"pi_b\":[{}, {},[\"1\",\"0\"]],\"pi_c\":[\"{}\",\"{}\",\"1\"],\"protocol\":\"groth16\",\"curve\":\"bn128\"}}",
241 fq_dec(&proof.a.x),
242 fq_dec(&proof.a.y),
243 fq2_json(&proof.b.x),
244 fq2_json(&proof.b.y),
245 fq_dec(&proof.c.x),
246 fq_dec(&proof.c.y),
247 )
248}
249
250pub fn publics_to_json(publics: &[Fr]) -> String {
251 let items = publics
252 .iter()
253 .map(|public| format!("\"{}\"", fr_dec(public)))
254 .collect::<Vec<_>>();
255 format!("[{}]", items.join(","))
256}
257
258#[cfg(feature = "wasm-threads")]
259pub use wasm_bindgen_rayon::init_thread_pool;
260
261#[cfg(feature = "wasm")]
262mod wasm_api {
263 use wasm_bindgen::prelude::*;
264
265 #[wasm_bindgen]
266 pub struct WasmCircuitProver(crate::CircuitProver);
267
268 #[wasm_bindgen]
269 impl WasmCircuitProver {
270 #[wasm_bindgen(constructor)]
271 pub fn new(
272 zkey: &[u8],
273 expected_zkey_sha256: &str,
274 witness_graph: &[u8],
275 expected_graph_sha256: &str,
276 ) -> Result<WasmCircuitProver, JsError> {
277 crate::CircuitProver::from_artifacts(
278 zkey,
279 expected_zkey_sha256,
280 witness_graph,
281 expected_graph_sha256,
282 )
283 .map(WasmCircuitProver)
284 .map_err(|error| JsError::new(&error.to_string()))
285 }
286
287 #[wasm_bindgen(getter, js_name = numConstraints)]
288 pub fn num_constraints(&self) -> usize {
289 self.0.num_constraints()
290 }
291
292 #[wasm_bindgen(getter, js_name = numPublic)]
293 pub fn num_public(&self) -> usize {
294 self.0.num_public()
295 }
296
297 pub fn prove(&self, input_json: &str) -> Result<String, JsError> {
299 self.0
300 .prove_json(input_json)
301 .map(bundle_json)
302 .map_err(|error| JsError::new(&error.to_string()))
303 }
304 }
305
306 #[wasm_bindgen]
307 pub struct WasmProver(crate::Prover);
308
309 #[wasm_bindgen]
310 impl WasmProver {
311 #[wasm_bindgen(constructor)]
312 pub fn new(zkey: &[u8], expected_sha256: &str) -> Result<WasmProver, JsError> {
313 crate::Prover::from_zkey_bytes(zkey, expected_sha256)
314 .map(WasmProver)
315 .map_err(|error| JsError::new(&error.to_string()))
316 }
317
318 #[wasm_bindgen(getter, js_name = numConstraints)]
319 pub fn num_constraints(&self) -> usize {
320 self.0.num_constraints()
321 }
322
323 #[wasm_bindgen(getter, js_name = numPublic)]
324 pub fn num_public(&self) -> usize {
325 self.0.num_public()
326 }
327
328 pub fn prove(&self, wtns: &[u8]) -> Result<String, JsError> {
330 self.0
331 .prove_wtns(wtns)
332 .map(bundle_json)
333 .map_err(|error| JsError::new(&error.to_string()))
334 }
335 }
336
337 fn bundle_json(bundle: crate::ProofBundle) -> String {
338 format!(
339 "{{\"proof\":{},\"publicSignals\":{}}}",
340 bundle.proof_json, bundle.public_signals_json
341 )
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use sha2::{Digest, Sha256};
348
349 use super::{Prover, ProverError, verify_sha256};
350
351 #[test]
352 fn rejects_an_untrusted_zkey_before_parsing() {
353 let error = verify_sha256(b"not a zkey", &"00".repeat(32)).expect_err("hash must mismatch");
354 assert!(matches!(error, ProverError::ZkeyHashMismatch { .. }));
355 }
356
357 #[test]
358 fn rejects_a_malformed_expected_hash() {
359 assert!(matches!(
360 verify_sha256(b"anything", "not-a-digest"),
361 Err(ProverError::InvalidExpectedHash)
362 ));
363 }
364
365 #[test]
366 fn rejects_malformed_zkey_after_its_digest_matches() {
367 let bytes = b"not a zkey";
368 let digest = Sha256::digest(bytes)
369 .iter()
370 .map(|byte| format!("{byte:02x}"))
371 .collect::<String>();
372 let error = Prover::from_zkey_bytes(bytes, &digest)
373 .err()
374 .expect("zkey parser must reject junk");
375 assert!(matches!(error, ProverError::InvalidZkey(_)));
376 }
377}