Skip to content

Commit

Permalink
Improve Support for WASM (#60)
Browse files Browse the repository at this point in the history
* feat: use `XofReader` instead of `io::Read` for no_std compatibility

* feat: remove `thiserror` dependency

thiserror is not no_std safe

* feat: move deps std features behind new `std` feature

- add new `std` feature
- add `simd_backend`
- move `colored` dep behind profile feature

* feat: use old rand(v0.7) for cubic example.

since curve25519-dalek (v3) uses old rand(v0.7) we need this.
should upgrade curve25519-dalek to v4 once it out of pre release

* feat: only build bench & profile if std in enabled

* feat: remove rand_core as dependency

* feat(ci): add job to test wasm build

* fix: rollback rand to v7 and update debug test

* fix(ci): Cargo.toml patching

* feat: make clippy happy

* feat: add wasm doc in readme

* feat: readme formatting

* feat: derive `Default` for `ProofVerifyError`
  • Loading branch information
ashutoshvarma authored Jan 17, 2023
1 parent 2dee78c commit 2be72b2
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 87 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,39 @@ jobs:
- name: Check clippy warnings
run: cargo clippy --all-targets --all-features -- -D warnings

build_nightly_wasm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Install
run: rustup default nightly

- name: Build without std
run: cargo build --no-default-features --verbose

- name: Run tests without std
run: cargo test --no-default-features --verbose

- name: Build examples without std
run: cargo build --examples --no-default-features --verbose

- name: Install wasm32-wasi target
run: rustup target add wasm32-wasi

- name: Install wasm32-unknown-unknown target
run: rustup target add wasm32-unknown-unknown

- name: Build for target wasm-wasi
run: RUSTFLAGS="" cargo build --target=wasm32-wasi --no-default-features --verbose

- name: Patch Cargo.toml for wasm-bindgen
run: |
echo "[dependencies.getrandom]" >> Cargo.toml
echo "version = \"0.1\"" >> Cargo.toml
echo "default-features = false" >> Cargo.toml
echo "features = [\"wasm-bindgen\"]" >> Cargo.toml
- name: Build for target wasm32-unknown-unknown
run: RUSTFLAGS="" cargo build --target=wasm32-unknown-unknown --no-default-features --verbose

54 changes: 37 additions & 17 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ license-file = "LICENSE"
keywords = ["zkSNARKs", "cryptography", "proofs"]

[dependencies]
curve25519-dalek = {version = "3.2.0", features = ["serde"]}
merlin = "3.0.0"
rand = "0.7.3"
digest = "0.8.1"
sha3 = "0.8.2"
byteorder = "1.3.4"
curve25519-dalek = { version = "3.2.0", features = [
"serde",
"u64_backend",
"alloc",
], default-features = false }
merlin = { version = "3.0.0", default-features = false }
rand = { version = "0.7.3", features = ["getrandom"], default-features = false }
digest = { version = "0.8.1", default-features = false }
sha3 = { version = "0.8.2", default-features = false }
byteorder = { version = "1.3.4", default-features = false }
rayon = { version = "1.3.0", optional = true }
serde = { version = "1.0.106", features = ["derive"] }
bincode = "1.2.1"
subtle = { version = "2.4", default-features = false }
rand_core = { version = "0.5", default-features = false }
zeroize = { version = "1", default-features = false }
itertools = "0.10.0"
colored = "2.0.0"
flate2 = "1.0.14"
thiserror = "1.0"
serde = { version = "1.0.106", features = ["derive"], default-features = false }
bincode = { version = "1.3.3", default-features = false }
subtle = { version = "2.4", features = ["i128"], default-features = false }
zeroize = { version = "1.5", default-features = false }
itertools = { version = "0.10.0", default-features = false }
colored = { version = "2.0.0", default-features = false, optional = true }
flate2 = { version = "1.0.14" }

[dev-dependencies]
criterion = "0.3.1"
Expand All @@ -38,20 +40,38 @@ path = "src/lib.rs"
[[bin]]
name = "snark"
path = "profiler/snark.rs"
required-features = ["std"]

[[bin]]
name = "nizk"
path = "profiler/nizk.rs"
required-features = ["std"]

[[bench]]
name = "snark"
harness = false
required-features = ["std"]

[[bench]]
name = "nizk"
harness = false
required-features = ["std"]

[features]
default = ["curve25519-dalek/simd_backend"]
default = ["std", "simd_backend"]
std = [
"curve25519-dalek/std",
"digest/std",
"merlin/std",
"rand/std",
"sha3/std",
"byteorder/std",
"serde/std",
"subtle/std",
"zeroize/std",
"itertools/use_std",
"flate2/rust_backend",
]
simd_backend = ["curve25519-dalek/simd_backend"]
multicore = ["rayon"]
profile = []
profile = ["colored"]
129 changes: 82 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
# Spartan: High-speed zkSNARKs without trusted setup

![Rust](https://github.com/microsoft/Spartan/workflows/Rust/badge.svg)
[![](https://img.shields.io/crates/v/spartan.svg)]((https://crates.io/crates/spartan))
[![](https://img.shields.io/crates/v/spartan.svg)](<(https://crates.io/crates/spartan)>)

Spartan 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.

A 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.

Note that this library has *not* received a security review or audit.
Note that this library has _not_ received a security review or audit.

## Highlights

We now highlight Spartan's distinctive features.

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

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

* **Sub-linear verification costs:** Spartan is the first transparent proof system with sub-linear verification costs for arbitrary NP statements (e.g., R1CS).
- **Sub-linear verification costs:** Spartan is the first transparent proof system with sub-linear verification costs for arbitrary NP statements (e.g., R1CS).

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

* **State-of-the-art performance:**
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.
- **State-of-the-art performance:**
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.

### Implementation details
`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.

`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.

## Examples

To import `libspartan` into your Rust project, add the following dependency to `Cargo.toml`:

```text
spartan = "0.7.1"
```
Expand All @@ -36,11 +40,11 @@ The following example shows how to use `libspartan` to create and verify a SNARK
Some of our public APIs' style is inspired by the underlying crates we use.

```rust
# extern crate libspartan;
# extern crate merlin;
# use libspartan::{Instance, SNARKGens, SNARK};
# use merlin::Transcript;
# fn main() {
extern crate libspartan;
extern crate merlin;
use libspartan::{Instance, SNARKGens, SNARK};
use merlin::Transcript;
fn main() {
// specify the size of an R1CS instance
let num_vars = 1024;
let num_cons = 1024;
Expand All @@ -66,16 +70,17 @@ Some of our public APIs' style is inspired by the underlying crates we use.
.verify(&comm, &inputs, &mut verifier_transcript, &gens)
.is_ok());
println!("proof verification successful!");
# }
}
```

Here is another example to use the NIZK variant of the Spartan proof system:

```rust
# extern crate libspartan;
# extern crate merlin;
# use libspartan::{Instance, NIZKGens, NIZK};
# use merlin::Transcript;
# fn main() {
extern crate libspartan;
extern crate merlin;
use libspartan::{Instance, NIZKGens, NIZK};
use merlin::Transcript;
fn main() {
// specify the size of an R1CS instance
let num_vars = 1024;
let num_cons = 1024;
Expand All @@ -97,20 +102,22 @@ Here is another example to use the NIZK variant of the Spartan proof system:
.verify(&inst, &inputs, &mut verifier_transcript, &gens)
.is_ok());
println!("proof verification successful!");
# }
}
```

Finally, we provide an example that specifies a custom R1CS instance instead of using a synthetic instance

```rust
#![allow(non_snake_case)]
# extern crate curve25519_dalek;
# extern crate libspartan;
# extern crate merlin;
# use curve25519_dalek::scalar::Scalar;
# use libspartan::{InputsAssignment, Instance, SNARKGens, VarsAssignment, SNARK};
# use merlin::Transcript;
# use rand::rngs::OsRng;
# fn main() {
extern crate curve25519_dalek;
extern crate libspartan;
extern crate merlin;
use curve25519_dalek::scalar::Scalar;
use libspartan::{InputsAssignment, Instance, SNARKGens, VarsAssignment, SNARK};
use merlin::Transcript;
use rand::rngs::OsRng;

fn main() {
// produce a tiny instance
let (
num_cons,
Expand Down Expand Up @@ -146,17 +153,17 @@ Finally, we provide an example that specifies a custom R1CS instance instead of
.verify(&comm, &assignment_inputs, &mut verifier_transcript, &gens)
.is_ok());
println!("proof verification successful!");
# }

# fn produce_tiny_r1cs() -> (
# usize,
# usize,
# usize,
# usize,
# Instance,
# VarsAssignment,
# InputsAssignment,
# ) {
}

fn produce_tiny_r1cs() -> (
usize,
usize,
usize,
usize,
Instance,
VarsAssignment,
InputsAssignment,
) {
// We will use the following example, but one could construct any R1CS instance.
// Our R1CS instance is three constraints over five variables and two public inputs
// (Z0 + Z1) * I0 - Z2 = 0
Expand All @@ -182,7 +189,7 @@ Finally, we provide an example that specifies a custom R1CS instance instead of
// a variable that holds a byte representation of 1
let one = Scalar::one().to_bytes();

// R1CS is a set of three sparse matrices A B C, where is a row for every
// R1CS is a set of three sparse matrices A B C, where is a row for every
// constraint and a column for every entry in z = (vars, 1, inputs)
// An R1CS instance is satisfiable iff:
// Az \circ Bz = Cz, where z = (vars, 1, inputs)
Expand Down Expand Up @@ -233,7 +240,7 @@ Finally, we provide an example that specifies a custom R1CS instance instead of
inputs[0] = i0.to_bytes();
inputs[1] = i1.to_bytes();
let assignment_inputs = InputsAssignment::new(&inputs).unwrap();

// check if the instance we created is satisfiable
let res = inst.is_sat(&assignment_vars, &assignment_inputs);
assert_eq!(res.unwrap(), true);
Expand All @@ -247,64 +254,92 @@ Finally, we provide an example that specifies a custom R1CS instance instead of
assignment_vars,
assignment_inputs,
)
# }
}
```

For more examples, see [`examples/`](examples) directory in this repo.

## Building `libspartan`

Install [`rustup`](https://rustup.rs/)

Switch to nightly Rust using `rustup`:

```text
rustup default nightly
```

Clone the repository:

```text
git clone https://github.com/Microsoft/Spartan
cd Spartan
```

To build docs for public APIs of `libspartan`:

```text
cargo doc
```

To run tests:

```text
RUSTFLAGS="-C target_cpu=native" cargo test
```

To build `libspartan`:

```text
RUSTFLAGS="-C target_cpu=native" cargo build --release
```

> 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`.
### Supported features
* `profile`: enables fine-grained profiling information (see below for its use)

- `std`: enables std features (enabled by default)
- `simd_backend`: enables `curve25519-dalek`'s simd feature (enabled by default)
- `profile`: enables fine-grained profiling information (see below for its use)

### WASM Support

`libspartan` depends upon `rand::OsRng` (internally uses `getrandom` crate), it has out of box support for `wasm32-wasi`.

For the target `wasm32-unknown-unknown` disable default features for spartan
and add direct dependency on `getrandom` with `wasm-bindgen` feature enabled.

```toml
[dependencies]
spartan = { version = "0.7", default-features = false }
# since spartan uses getrandom(rand's OsRng), we need to enable 'wasm-bindgen'
# feature to make it feed rand seed from js/nodejs env
# https://docs.rs/getrandom/0.1.16/getrandom/index.html#support-for-webassembly-and-asmjs
getrandom = { version = "0.1", features = ["wasm-bindgen"] }
```

## Performance

### End-to-end benchmarks

`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).

To run end-to-end benchmarks:
```text

```text
RUSTFLAGS="-C target_cpu=native" cargo bench
```

### Fine-grained profiling

Build `libspartan` with `profile` feature enabled. It creates two profilers: `./target/release/snark` and `./target/release/nizk`.

These profilers report performance as depicted below (for varying R1CS instance sizes). The reported
performance 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).
See Section 9 in our [paper](https://eprint.iacr.org/2019/550) to see how this compares with other zkSNARKs in the literature.

```text
$ ./target/release/snark
$ ./target/release/snark
Profiler:: SNARK
* number_of_constraints 1048576
* number_of_variables 1048576
Expand Down Expand Up @@ -355,7 +390,7 @@ Profiler:: SNARK
```

```text
$ ./target/release/nizk
$ ./target/release/nizk
Profiler:: NIZK
* number_of_constraints 1048576
* number_of_variables 1048576
Expand Down
Loading

0 comments on commit 2be72b2

Please sign in to comment.