Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Support for WASM #60

Merged
merged 13 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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