testudo/README.md
README.mdRaw
1# Spartan: High-speed zkSNARKs without trusted setup
2
3
4[](<(https://crates.io/crates/spartan)>)
5
6Spartan is a high-speed zero-knowledge proof system, a cryptographic primitive that enables a prover to prove a mathematical statement to a verifier without revealing anything besides the validity of the statement. This repository provides `libspartan,` a Rust library that implements a zero-knowledge succinct non-interactive argument of knowledge (zkSNARK), which is a type of zero-knowledge proof system with short proofs and fast verification times. The details of the Spartan proof system are described in our [paper](https://eprint.iacr.org/2019/550) published at [CRYPTO 2020](https://crypto.iacr.org/2020/). The security of the Spartan variant implemented in this library is based on the discrete logarithm problem in the random oracle model.
7
8A simple example application is proving the knowledge of a secret s such that H(s) == d for a public d, where H is a cryptographic hash function (e.g., SHA-256, Keccak). A more complex application is a database-backed cloud service that produces proofs of correct state machine transitions for auditability. See this [paper](https://eprint.iacr.org/2020/758.pdf) for an overview and this [paper](https://eprint.iacr.org/2018/907.pdf) for details.
9
10Note that this library has _not_ received a security review or audit.
11
12## Highlights
13
14We now highlight Spartan's distinctive features.
15
16- **No "toxic" waste:** Spartan is a _transparent_ zkSNARK and does not require a trusted setup. So, it does not involve any trapdoors that must be kept secret or require a multi-party ceremony to produce public parameters.
17
18- **General-purpose:** Spartan produces proofs for arbitrary NP statements. `libspartan` supports NP statements expressed as rank-1 constraint satisfiability (R1CS) instances, a popular language for which there exists efficient transformations and compiler toolchains from high-level programs of interest.
19
20- **Sub-linear verification costs:** Spartan is the first transparent proof system with sub-linear verification costs for arbitrary NP statements (e.g., R1CS).
21
22- **Standardized security:** Spartan's security relies on the hardness of computing discrete logarithms (a standard cryptographic assumption) in the random oracle model. `libspartan` uses `ristretto255`, a prime-order group abstraction atop `curve25519` (a high-speed elliptic curve). We use [`curve25519-dalek`](https://docs.rs/curve25519-dalek) for arithmetic over `ristretto255`.
23
24- **State-of-the-art performance:**
25 Among transparent SNARKs, Spartan offers the fastest prover with speedups of 36–152× depending on the baseline, produces proofs that are shorter by 1.2–416×, and incurs the lowest verification times with speedups of 3.6–1326×. The only exception is proof sizes under Bulletproofs, but Bulletproofs incurs slower verification both asymptotically and concretely. When compared to the state-of-the-art zkSNARK with trusted setup, Spartan’s prover is 2× faster for arbitrary R1CS instances and 16× faster for data-parallel workloads.
26
27### Implementation details
28
29`libspartan` uses [`merlin`](https://docs.rs/merlin/) to automate the Fiat-Shamir transform. We also introduce a new type called `RandomTape` that extends a `Transcript` in `merlin` to allow the prover's internal methods to produce private randomness using its private transcript without having to create `OsRng` objects throughout the code. An object of type `RandomTape` is initialized with a new random seed from `OsRng` for each proof produced by the library.
30
31## Examples
32
33To import `libspartan` into your Rust project, add the following dependency to `Cargo.toml`:
34
35```text
36spartan = "0.4.1"
37```
38
39The following example shows how to use `libspartan` to create and verify a SNARK proof.
40Some of our public APIs' style is inspired by the underlying crates we use.
41
42```rust
43# extern crate libspartan;
44# extern crate merlin;
45# use libspartan::{Instance, SNARKGens, SNARK};
46# use merlin::Transcript;
47# fn main() {
48 // specify the size of an R1CS instance
49 let num_vars = 1024;
50 let num_cons = 1024;
51 let num_inputs = 10;
52 let num_non_zero_entries = 1024;
53
54 // produce public parameters
55 let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_non_zero_entries);
56
57 // ask the library to produce a synthentic R1CS instance
58 let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
59
60 // create a commitment to the R1CS instance
61 let (comm, decomm) = SNARK::encode(&inst, &gens);
62
63 // produce a proof of satisfiability
64 let mut prover_transcript = Transcript::new(b"snark_example");
65 let proof = SNARK::prove(&inst, &comm, &decomm, vars, &inputs, &gens, &mut prover_transcript);
66
67 // verify the proof of satisfiability
68 let mut verifier_transcript = Transcript::new(b"snark_example");
69 assert!(proof
70 .verify(&comm, &inputs, &mut verifier_transcript, &gens)
71 .is_ok());
72 println!("proof verification successful!");
73# }
74```
75
76Here is another example to use the NIZK variant of the Spartan proof system:
77
78```rust
79# extern crate libspartan;
80# extern crate merlin;
81# use libspartan::{Instance, NIZKGens, NIZK};
82# use merlin::Transcript;
83# fn main() {
84 // specify the size of an R1CS instance
85 let num_vars = 1024;
86 let num_cons = 1024;
87 let num_inputs = 10;
88
89 // produce public parameters
90 let gens = NIZKGens::new(num_cons, num_vars, num_inputs);
91
92 // ask the library to produce a synthentic R1CS instance
93 let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
94
95 // produce a proof of satisfiability
96 let mut prover_transcript = Transcript::new(b"nizk_example");
97 let proof = NIZK::prove(&inst, vars, &inputs, &gens, &mut prover_transcript);
98
99 // verify the proof of satisfiability
100 let mut verifier_transcript = Transcript::new(b"nizk_example");
101 assert!(proof
102 .verify(&inst, &inputs, &mut verifier_transcript, &gens)
103 .is_ok());
104 println!("proof verification successful!");
105# }
106```
107
108Finally, we provide an example that specifies a custom R1CS instance instead of using a synthetic instance
109
110```rust
111#![allow(non_snake_case)]
112# extern crate ark_std;
113# extern crate libspartan;
114# extern crate merlin;
115# mod scalar;
116# use scalar::Scalar;
117# use libspartan::{InputsAssignment, Instance, SNARKGens, VarsAssignment, SNARK};
118# use merlin::Transcript;
119# use ark_ff::{PrimeField, Field, BigInteger};
120# use ark_std::{One, Zero, UniformRand};
121# fn main() {
122 // produce a tiny instance
123 let (
124 num_cons,
125 num_vars,
126 num_inputs,
127 num_non_zero_entries,
128 inst,
129 assignment_vars,
130 assignment_inputs,
131 ) = produce_tiny_r1cs();
132
133 // produce public parameters
134 let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_non_zero_entries);
135
136 // create a commitment to the R1CS instance
137 let (comm, decomm) = SNARK::encode(&inst, &gens);
138
139 // produce a proof of satisfiability
140 let mut prover_transcript = Transcript::new(b"snark_example");
141 let proof = SNARK::prove(
142 &inst,
143 &comm,
144 &decomm,
145 assignment_vars,
146 &assignment_inputs,
147 &gens,
148 &mut prover_transcript,
149 );
150
151 // verify the proof of satisfiability
152 let mut verifier_transcript = Transcript::new(b"snark_example");
153 assert!(proof
154 .verify(&comm, &assignment_inputs, &mut verifier_transcript, &gens)
155 .is_ok());
156 println!("proof verification successful!");
157# }
158
159# fn produce_tiny_r1cs() -> (
160# usize,
161# usize,
162# usize,
163# usize,
164# Instance,
165# VarsAssignment,
166# InputsAssignment,
167# ) {
168 // We will use the following example, but one could construct any R1CS instance.
169 // Our R1CS instance is three constraints over five variables and two public inputs
170 // (Z0 + Z1) * I0 - Z2 = 0
171 // (Z0 + I1) * Z2 - Z3 = 0
172 // Z4 * 1 - 0 = 0
173
174 // parameters of the R1CS instance rounded to the nearest power of two
175 let num_cons = 4;
176 let num_vars = 5;
177 let num_inputs = 2;
178 let num_non_zero_entries = 5;
179
180 // We will encode the above constraints into three matrices, where
181 // the coefficients in the matrix are in the little-endian byte order
182 let mut A: Vec<(usize, usize, Vec<u8>)> = Vec::new();
183 let mut B: Vec<(usize, usize, Vec<u8>)> = Vec::new();
184 let mut C: Vec<(usize, usize, Vec<u8>)> = Vec::new();
185
186 // The constraint system is defined over a finite field, which in our case is
187 // the scalar field of ristreeto255/curve25519 i.e., p = 2^{252}+27742317777372353535851937790883648493
188 // To construct these matrices, we will use `curve25519-dalek` but one can use any other method.
189
190 // a variable that holds a byte representation of 1
191 let one = Scalar::one().into_repr().to_bytes_le();
192
193 // R1CS is a set of three sparse matrices A B C, where is a row for every
194 // constraint and a column for every entry in z = (vars, 1, inputs)
195 // An R1CS instance is satisfiable iff:
196 // Az \circ Bz = Cz, where z = (vars, 1, inputs)
197
198 // constraint 0 entries in (A,B,C)
199 // constraint 0 is (Z0 + Z1) * I0 - Z2 = 0.
200 // We set 1 in matrix A for columns that correspond to Z0 and Z1
201 // We set 1 in matrix B for column that corresponds to I0
202 // We set 1 in matrix C for column that corresponds to Z2
203 A.push((0, 0, one.clone()));
204 A.push((0, 1, one.clone()));
205 B.push((0, num_vars + 1, one.clone()));
206 C.push((0, 2, one.clone()));
207
208 // constraint 1 entries in (A,B,C)
209 A.push((1, 0, one.clone()));
210 A.push((1, num_vars + 2, one.clone()));
211 B.push((1, 2, one.clone()));
212 C.push((1, 3, one.clone()));
213
214 // constraint 3 entries in (A,B,C)
215 A.push((2, 4, one.clone()));
216 B.push((2, num_vars, one.clone()));
217
218 let inst = Instance::new(num_cons, num_vars, num_inputs, &A, &B, &C).unwrap();
219
220 // compute a satisfying assignment
221let mut rng = ark_std::rand::thread_rng();
222 let i0 = Scalar::rand(&mut rng);
223 let i1 = Scalar::rand(&mut rng);
224 let z0 = Scalar::rand(&mut rng);
225 let z1 = Scalar::rand(&mut rng);
226 let z2 = (z0 + z1) * i0; // constraint 0
227 let z3 = (z0 + i1) * z2; // constraint 1
228 let z4 = Scalar::zero(); //constraint 2
229
230 // create a VarsAssignment
231 let mut vars = vec![Scalar::zero().into_repr().to_bytes_le(); num_vars];
232 vars[0] = z0.into_repr().to_bytes_le();
233 vars[1] = z1.into_repr().to_bytes_le();
234 vars[2] = z2.into_repr().to_bytes_le();
235 vars[3] = z3.into_repr().to_bytes_le();
236 vars[4] = z4.into_repr().to_bytes_le();
237 let assignment_vars = VarsAssignment::new(&vars).unwrap();
238
239 // create an InputsAssignment
240 let mut inputs = vec![Scalar::zero().into_repr().to_bytes_le(); num_inputs];
241 inputs[0] = i0.into_repr().to_bytes_le();
242 inputs[1] = i1.into_repr().to_bytes_le();
243 let assignment_inputs = InputsAssignment::new(&inputs).unwrap();
244
245 // check if the instance we created is satisfiable
246 let res = inst.is_sat(&assignment_vars, &assignment_inputs);
247 assert_eq!(res.unwrap(), true);
248
249 (
250 num_cons,
251 num_vars,
252 num_inputs,
253 num_non_zero_entries,
254 inst,
255 assignment_vars,
256 assignment_inputs,
257 )
258# }
259```
260
261For more examples, see [`examples/`](examples) directory in this repo.
262
263## Building `libspartan`
264
265Install [`rustup`](https://rustup.rs/)
266
267Switch to nightly Rust using `rustup`:
268
269```text
270rustup default nightly
271```
272
273Clone the repository:
274
275```text
276git clone https://github.com/Microsoft/Spartan
277cd Spartan
278```
279
280To build docs for public APIs of `libspartan`:
281
282```text
283cargo doc
284```
285
286To run tests:
287
288```text
289RUSTFLAGS="-C target_cpu=native" cargo test
290```
291
292To build `libspartan`:
293
294```text
295RUSTFLAGS="-C target_cpu=native" cargo build --release
296```
297
298> NOTE: We enable SIMD instructions in `curve25519-dalek` by default, so if it fails to build remove the "simd_backend" feature argument in `Cargo.toml`.
299
300### Supported features
301
302- `profile`: enables fine-grained profiling information (see below for its use)
303
304## Performance
305
306### End-to-end benchmarks
307
308`libspartan` includes two benches: `benches/nizk.rs` and `benches/snark.rs`. If you report the performance of Spartan in a research paper, we recommend using these benches for higher accuracy instead of fine-grained profiling (listed below).
309
310To run end-to-end benchmarks:
311
312```text
313RUSTFLAGS="-C target_cpu=native" cargo bench
314```
315
316### Fine-grained profiling
317
318Build `libspartan` with `profile` feature enabled. It creates two profilers: `./target/release/snark` and `./target/release/nizk`.
319
320These profilers report performance as depicted below (for varying R1CS instance sizes). The reported
321performance is from running the profilers on a Microsoft Surface Laptop 3 on a single CPU core of Intel Core i7-1065G7 running Ubuntu 20.04 (atop WSL2 on Windows 10).
322See Section 9 in our [paper](https://eprint.iacr.org/2019/550) to see how this compares with other zkSNARKs in the literature.
323
324```text
325$ ./target/release/snark
326Profiler:: SNARK
327 * number_of_constraints 1048576
328 * number_of_variables 1048576
329 * number_of_inputs 10
330 * number_non-zero_entries_A 1048576
331 * number_non-zero_entries_B 1048576
332 * number_non-zero_entries_C 1048576
333 * SNARK::encode
334 * SNARK::encode 14.2644201s
335 * SNARK::prove
336 * R1CSProof::prove
337 * polycommit
338 * polycommit 2.7175848s
339 * prove_sc_phase_one
340 * prove_sc_phase_one 683.7481ms
341 * prove_sc_phase_two
342 * prove_sc_phase_two 846.1056ms
343 * polyeval
344 * polyeval 193.4216ms
345 * R1CSProof::prove 4.4416193s
346 * len_r1cs_sat_proof 47024
347 * eval_sparse_polys
348 * eval_sparse_polys 377.357ms
349 * R1CSEvalProof::prove
350 * commit_nondet_witness
351 * commit_nondet_witness 14.4507331s
352 * build_layered_network
353 * build_layered_network 3.4360521s
354 * evalproof_layered_network
355 * len_product_layer_proof 64712
356 * evalproof_layered_network 15.5708066s
357 * R1CSEvalProof::prove 34.2930559s
358 * len_r1cs_eval_proof 133720
359 * SNARK::prove 39.1297568s
360 * SNARK::proof_compressed_len 141768
361 * SNARK::verify
362 * verify_sat_proof
363 * verify_sat_proof 20.0828ms
364 * verify_eval_proof
365 * verify_polyeval_proof
366 * verify_prod_proof
367 * verify_prod_proof 1.1847ms
368 * verify_hash_proof
369 * verify_hash_proof 81.06ms
370 * verify_polyeval_proof 82.3583ms
371 * verify_eval_proof 82.8937ms
372 * SNARK::verify 103.0536ms
373```
374
375```text
376$ ./target/release/nizk
377Profiler:: NIZK
378 * number_of_constraints 1048576
379 * number_of_variables 1048576
380 * number_of_inputs 10
381 * number_non-zero_entries_A 1048576
382 * number_non-zero_entries_B 1048576
383 * number_non-zero_entries_C 1048576
384 * NIZK::prove
385 * R1CSProof::prove
386 * polycommit
387 * polycommit 2.7220635s
388 * prove_sc_phase_one
389 * prove_sc_phase_one 722.5487ms
390 * prove_sc_phase_two
391 * prove_sc_phase_two 862.6796ms
392 * polyeval
393 * polyeval 190.2233ms
394 * R1CSProof::prove 4.4982305s
395 * len_r1cs_sat_proof 47024
396 * NIZK::prove 4.5139888s
397 * NIZK::proof_compressed_len 48134
398 * NIZK::verify
399 * eval_sparse_polys
400 * eval_sparse_polys 395.0847ms
401 * verify_sat_proof
402 * verify_sat_proof 19.286ms
403 * NIZK::verify 414.5102ms
404```
405
406## LICENSE
407
408See [LICENSE](./LICENSE)
409
410## Contributing
411
412See [CONTRIBUTING](./CONTRIBUTING.md)