Skip to content

Commit 2d22bff

Browse files
authored
Add APIs to specify an R1CS Instance (#24)
1 parent 131fad9 commit 2d22bff

File tree

12 files changed

+398
-77
lines changed

12 files changed

+398
-77
lines changed

README.md

Lines changed: 151 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,12 @@ We now highlight Spartan's distinctive features.
1515

1616
* **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.
1717

18-
* **Sub-linear verification costs and linear-time proving costs:** Spartan is the first transparent proof system with sub-linear verification costs for arbitrary NP statements (e.g., R1CS). Spartan also features a time-optimal prover, a property that has remained elusive for nearly all zkSNARKs in the literature.
18+
* **Sub-linear verification costs and linear-time proving costs:** Spartan is the first transparent proof system with sub-linear verification costs for arbitrary NP statements (e.g., R1CS). Spartan also features a linear-time prover, a property that has remained elusive for nearly all zkSNARKs in the literature.
1919

2020
* **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`.
2121

2222
* **State-of-the-art performance:**
23-
Among transparent SNARKs, Spartan
24-
offers the fastest prover with speedups of 36–152× depending on the baseline,
25-
produces proofs that are shorter by 1.2–416×, and incurs the lowest verification
26-
times with speedups of 3.6–1326×. When compared to the state-of-the-art zkSNARK
27-
with trusted setup, Spartan’s prover is 2× faster for arbitrary R1CS instances and
28-
16× faster for data-parallel workloads.
29-
30-
### Status
31-
Development is ongoing (PRs are welcome!). For example, the library does not yet offer APIs to specify an NP statement, but we will in the future offer standardized APIs and also integrate with a compiler that produces R1CS instances from high level programs.
23+
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.
3224

3325
### Implementation details
3426
`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.
@@ -53,7 +45,7 @@ Some of our public APIs' style is inspired by the underlying crates we use.
5345
let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_non_zero_entries);
5446

5547
// ask the library to produce a synthentic R1CS instance
56-
let (inst, vars, inputs) = Instance::new(num_cons, num_vars, num_inputs);
48+
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
5749

5850
// create a commitment to the R1CS instance
5951
let (comm, decomm) = SNARK::encode(&inst, &gens);
@@ -86,7 +78,7 @@ Here is another example to use the NIZK variant of the Spartan proof system:
8678
let gens = NIZKGens::new(num_cons, num_vars);
8779

8880
// ask the library to produce a synthentic R1CS instance
89-
let (inst, vars, inputs) = Instance::new(num_cons, num_vars, num_inputs);
81+
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
9082

9183
// produce a proof of satisfiability
9284
let mut prover_transcript = Transcript::new(b"nizk_example");
@@ -100,6 +92,153 @@ Here is another example to use the NIZK variant of the Spartan proof system:
10092
# }
10193
```
10294

95+
Finally, we provide an example that specifies a custom R1CS instance instead of using a synthetic instance
96+
```rust
97+
# extern crate curve25519_dalek;
98+
# extern crate libspartan;
99+
# extern crate merlin;
100+
# use curve25519_dalek::scalar::Scalar;
101+
# use libspartan::{InputsAssignment, Instance, SNARKGens, VarsAssignment, SNARK};
102+
# use merlin::Transcript;
103+
# use rand::rngs::OsRng;
104+
# fn main() {
105+
// produce a tiny instance
106+
let (
107+
num_cons,
108+
num_vars,
109+
num_inputs,
110+
num_non_zero_entries,
111+
inst,
112+
assignment_vars,
113+
assignment_inputs,
114+
) = produce_tiny_r1cs();
115+
116+
// produce public parameters
117+
let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_non_zero_entries);
118+
119+
// create a commitment to the R1CS instance
120+
let (comm, decomm) = SNARK::encode(&inst, &gens);
121+
122+
// produce a proof of satisfiability
123+
let mut prover_transcript = Transcript::new(b"snark_example");
124+
let proof = SNARK::prove(
125+
&inst,
126+
&decomm,
127+
assignment_vars,
128+
&assignment_inputs,
129+
&gens,
130+
&mut prover_transcript,
131+
);
132+
133+
// verify the proof of satisfiability
134+
let mut verifier_transcript = Transcript::new(b"snark_example");
135+
assert!(proof
136+
.verify(&comm, &assignment_inputs, &mut verifier_transcript, &gens)
137+
.is_ok());
138+
# }
139+
140+
# fn produce_tiny_r1cs() -> (
141+
# usize,
142+
# usize,
143+
# usize,
144+
# usize,
145+
# Instance,
146+
# VarsAssignment,
147+
# InputsAssignment,
148+
# ) {
149+
// We will use the following example, but one could construct any R1CS instance.
150+
// Our R1CS instance is three constraints over five variables and two public inputs
151+
// (Z0 + Z1) * I0 - Z2 = 0
152+
// (Z0 + I1) * Z2 - Z3 = 0
153+
// Z4 * 1 - 0 = 0
154+
155+
// parameters of the R1CS instance rounded to the nearest power of two
156+
let num_cons = 4;
157+
let num_vars = 8;
158+
let num_inputs = 2;
159+
let num_non_zero_entries = 8;
160+
161+
// We will encode the above constraints into three matrices, where
162+
// the coefficients in the matrix are in the little-endian byte order
163+
let mut A: Vec<(usize, usize, [u8; 32])> = Vec::new();
164+
let mut B: Vec<(usize, usize, [u8; 32])> = Vec::new();
165+
let mut C: Vec<(usize, usize, [u8; 32])> = Vec::new();
166+
167+
// The constraint system is defined over a finite field, which in our case is
168+
// the scalar field of ristreeto255/curve25519 i.e., p = 2^{252}+27742317777372353535851937790883648493
169+
// To construct these matrices, we will use `curve25519-dalek` but one can use any other method.
170+
171+
// a variable that holds a byte representation of 1
172+
let one = Scalar::one().to_bytes();
173+
174+
// R1CS is a set of three sparse matrices A B C, where is a row for every
175+
// constraint and a column for every entry in z = (vars, 1, inputs)
176+
// An R1CS instance is satisfiable iff:
177+
// Az \circ Bz = Cz, where z = (vars, 1, inputs)
178+
179+
// constraint 0 entries in (A,B,C)
180+
// constraint 0 is (Z0 + Z1) * I0 - Z2 = 0.
181+
// We set 1 in matrix A for columns that correspond to Z0 and Z1
182+
// We set 1 in matrix B for column that corresponds to I0
183+
// We set 1 in matrix C for column that corresponds to Z2
184+
A.push((0, 0, one));
185+
A.push((0, 1, one));
186+
B.push((0, num_vars + 1, one));
187+
C.push((0, 2, one));
188+
189+
// constraint 1 entries in (A,B,C)
190+
A.push((1, 0, one));
191+
A.push((1, num_vars + 2, one));
192+
B.push((1, 2, one));
193+
C.push((1, 3, one));
194+
195+
// constraint 3 entries in (A,B,C)
196+
A.push((2, 4, one));
197+
B.push((2, num_vars, one));
198+
199+
let inst = Instance::new(num_cons, num_vars, num_inputs, &A, &B, &C).unwrap();
200+
201+
// compute a satisfying assignment
202+
let mut csprng: OsRng = OsRng;
203+
let i0 = Scalar::random(&mut csprng);
204+
let i1 = Scalar::random(&mut csprng);
205+
let z0 = Scalar::random(&mut csprng);
206+
let z1 = Scalar::random(&mut csprng);
207+
let z2 = (z0 + z1) * i0; // constraint 0
208+
let z3 = (z0 + i1) * z2; // constraint 1
209+
let z4 = Scalar::zero(); //constraint 2
210+
211+
// create a VarsAssignment
212+
let mut vars = vec![Scalar::zero().to_bytes(); num_vars];
213+
vars[0] = z0.to_bytes();
214+
vars[1] = z1.to_bytes();
215+
vars[2] = z2.to_bytes();
216+
vars[3] = z3.to_bytes();
217+
vars[4] = z4.to_bytes();
218+
let assignment_vars = VarsAssignment::new(&vars).unwrap();
219+
220+
// create an InputsAssignment
221+
let mut inputs = vec![Scalar::zero().to_bytes(); num_inputs];
222+
inputs[0] = i0.to_bytes();
223+
inputs[1] = i1.to_bytes();
224+
let assignment_inputs = InputsAssignment::new(&inputs).unwrap();
225+
226+
// check if the instance we created is satisfiable
227+
let res = inst.is_sat(&assignment_vars, &assignment_inputs);
228+
assert_eq!(res.unwrap(), true);
229+
230+
(
231+
num_cons,
232+
num_vars,
233+
num_inputs,
234+
num_non_zero_entries,
235+
inst,
236+
assignment_vars,
237+
assignment_inputs,
238+
)
239+
# }
240+
```
241+
103242
## Building `libspartan`
104243
Install [`rustup`](https://rustup.rs/)
105244

benches/nizk.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn nizk_prove_benchmark(c: &mut Criterion) {
2222
let num_cons = num_vars;
2323
let num_inputs = 10;
2424

25-
let (inst, vars, inputs) = Instance::new(num_cons, num_vars, num_inputs);
25+
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
2626

2727
let gens = NIZKGens::new(num_cons, num_vars);
2828

@@ -52,7 +52,7 @@ fn nizk_verify_benchmark(c: &mut Criterion) {
5252
let num_vars = (2 as usize).pow(s as u32);
5353
let num_cons = num_vars;
5454
let num_inputs = 10;
55-
let (inst, vars, inputs) = Instance::new(num_cons, num_vars, num_inputs);
55+
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
5656

5757
let gens = NIZKGens::new(num_cons, num_vars);
5858

benches/snark.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn snark_encode_benchmark(c: &mut Criterion) {
1515
let num_vars = (2 as usize).pow(s as u32);
1616
let num_cons = num_vars;
1717
let num_inputs = 10;
18-
let (inst, _vars, _inputs) = Instance::new(num_cons, num_vars, num_inputs);
18+
let (inst, _vars, _inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
1919

2020
// produce public parameters
2121
let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_cons);
@@ -41,7 +41,7 @@ fn snark_prove_benchmark(c: &mut Criterion) {
4141
let num_cons = num_vars;
4242
let num_inputs = 10;
4343

44-
let (inst, vars, inputs) = Instance::new(num_cons, num_vars, num_inputs);
44+
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
4545

4646
// produce public parameters
4747
let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_cons);
@@ -77,7 +77,7 @@ fn snark_verify_benchmark(c: &mut Criterion) {
7777
let num_vars = (2 as usize).pow(s as u32);
7878
let num_cons = num_vars;
7979
let num_inputs = 10;
80-
let (inst, vars, inputs) = Instance::new(num_cons, num_vars, num_inputs);
80+
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
8181

8282
// produce public parameters
8383
let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_cons);

profiler/nizk.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ pub fn main() {
2222
let num_vars = (2 as usize).pow(s as u32);
2323
let num_cons = num_vars;
2424
let num_inputs = 10;
25-
let (inst, vars, inputs) = Instance::new(num_cons, num_vars, num_inputs);
25+
26+
// produce a synthetic R1CSInstance
27+
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
2628

2729
// produce public generators
2830
let gens = NIZKGens::new(num_cons, num_vars);

profiler/snark.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ pub fn main() {
2121
let num_vars = (2 as usize).pow(s as u32);
2222
let num_cons = num_vars;
2323
let num_inputs = 10;
24-
let (inst, vars, inputs) = Instance::new(num_cons, num_vars, num_inputs);
24+
25+
// produce a synthetic R1CSInstance
26+
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
2527

2628
// produce public generators
2729
let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_cons);

src/errors.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,28 @@ impl fmt::Debug for ProofVerifyError {
1313
write!(f, "{{ file: {}, line: {} }}", file!(), line!())
1414
}
1515
}
16+
17+
pub enum R1CSError {
18+
/// returned if the number of constraints is not a power of 2
19+
NonPowerOfTwoCons,
20+
/// returned if the number of variables is not a power of 2
21+
NonPowerOfTwoVars,
22+
/// returned if a wrong number of inputs in an assignment are supplied
23+
InvalidNumberOfInputs,
24+
/// returned if a wrong number of variables in an assignment are supplied
25+
InvalidNumberOfVars,
26+
/// returned if a [u8;32] does not parse into a valid Scalar in the field of ristretto255
27+
InvalidScalar,
28+
}
29+
30+
impl fmt::Display for R1CSError {
31+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32+
write!(f, "R1CSError")
33+
}
34+
}
35+
36+
impl fmt::Debug for R1CSError {
37+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38+
write!(f, "{{ file: {}, line: {} }}", file!(), line!())
39+
}
40+
}

0 commit comments

Comments
 (0)