demo added, docs updated
This commit is contained in:
parent
e544678a6a
commit
d9be906ed7
@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Sean Bowe <ewillbefull@gmail.com>", "Alex Vlasov <alex.m.vlasov@gmail.com>"]
|
authors = ["Sean Bowe <ewillbefull@gmail.com>", "Alex Vlasov <alex.m.vlasov@gmail.com>", "Alex Gluchowski <alex@gluchowski.net"]
|
||||||
description = "zk-SNARK library"
|
description = "zk-SNARK library"
|
||||||
documentation = "https://github.com/matterinc/bellman"
|
documentation = "https://github.com/matterinc/bellman"
|
||||||
homepage = "https://github.com/matterinc/bellman"
|
homepage = "https://github.com/matterinc/bellman"
|
||||||
|
2
src/demo/.gitignore
vendored
Normal file
2
src/demo/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
12
src/demo/Cargo.toml
Normal file
12
src/demo/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "bellman-demo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.4"
|
||||||
|
hex = "0.3.2"
|
||||||
|
time = "0.1"
|
||||||
|
num-bigint = "0.2"
|
||||||
|
ff = { version = "0.4", features = ["derive"] }
|
||||||
|
bellman = { path = "../.." }
|
||||||
|
pairing = { git = 'https://github.com/matterinc/pairing' }
|
42
src/demo/README.md
Normal file
42
src/demo/README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Demo circuits
|
||||||
|
|
||||||
|
This project contains usage demonstration for `bellman` zkSNARK proving framework.
|
||||||
|
We use elliptic curve BN256, for which pairings can be efficiently performed in Ethereum Virtual Machine.
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
│
|
||||||
|
├── examples
|
||||||
|
│ └── xor.rs: simple XOR circuit demo
|
||||||
|
└── src
|
||||||
|
└── lib.rs: demo contract rendering
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage:
|
||||||
|
|
||||||
|
```$bash
|
||||||
|
cargo run --example xor
|
||||||
|
cargo run --bin circuit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification in EVM contract:
|
||||||
|
|
||||||
|
```$bash
|
||||||
|
cargo run --example xor > demo.sol
|
||||||
|
```
|
||||||
|
|
||||||
|
Now deploy `DemoVerifier` contract from `demo.sol` (e.g. in [remix](https://remix.ethereum.org)) and run method `verify()`.
|
||||||
|
|
||||||
|
## Benchmarking
|
||||||
|
|
||||||
|
```$bash
|
||||||
|
BELLMAN_VERBOSE=1 cargo run --release [num_constraints]
|
||||||
|
```
|
||||||
|
|
||||||
|
`num_constraints` is decimal:
|
||||||
|
|
||||||
|
```$bash
|
||||||
|
BELLMAN_VERBOSE=1 cargo run --release 1000000
|
||||||
|
```
|
112
src/demo/examples/xor.rs
Normal file
112
src/demo/examples/xor.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
extern crate bellman;
|
||||||
|
extern crate pairing;
|
||||||
|
extern crate rand;
|
||||||
|
extern crate ff;
|
||||||
|
extern crate bellman_demo;
|
||||||
|
|
||||||
|
use bellman::{Circuit, ConstraintSystem, SynthesisError};
|
||||||
|
use ff::{Field, PrimeField};
|
||||||
|
use pairing::{Engine};
|
||||||
|
use pairing::bn256::{Bn256, Fr};
|
||||||
|
|
||||||
|
trait OptionExt<T> {
|
||||||
|
fn grab(&self) -> Result<T, SynthesisError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Copy> OptionExt<T> for Option<T> {
|
||||||
|
fn grab(&self) -> Result<T, SynthesisError> {
|
||||||
|
self.ok_or(SynthesisError::AssignmentMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct XorCircuit<E: Engine> {
|
||||||
|
a: Option<E::Fr>,
|
||||||
|
b: Option<E::Fr>,
|
||||||
|
c: Option<E::Fr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of our circuit:
|
||||||
|
// Given a bit `c`, prove that we know bits `a` and `b` such that `c = a xor b`
|
||||||
|
impl<E: Engine> Circuit<E> for XorCircuit<E> {
|
||||||
|
fn synthesize<CS: ConstraintSystem<E>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
|
||||||
|
|
||||||
|
// public input: c
|
||||||
|
// variables (witness): a, b
|
||||||
|
|
||||||
|
// constraint system:
|
||||||
|
// a * a = a
|
||||||
|
// b * b = b
|
||||||
|
// 2a * b = a + b - c
|
||||||
|
|
||||||
|
let a = cs.alloc(|| "a", || self.a.grab())?;
|
||||||
|
|
||||||
|
// a * a = a
|
||||||
|
cs.enforce(|| "a is a boolean", |lc| lc + a, |lc| lc + a, |lc| lc + a);
|
||||||
|
|
||||||
|
let b = cs.alloc(|| "b", || self.b.grab())?;
|
||||||
|
|
||||||
|
// b * b = b
|
||||||
|
cs.enforce(|| "b is a boolean", |lc| lc + b, |lc| lc + b, |lc| lc + b);
|
||||||
|
|
||||||
|
// c = a xor b
|
||||||
|
let c = cs.alloc_input(|| "c", || self.c.grab())?;
|
||||||
|
|
||||||
|
// 2a * b = a + b - c
|
||||||
|
cs.enforce(
|
||||||
|
|| "xor constraint",
|
||||||
|
|lc| lc + (E::Fr::from_str("2").unwrap(), a),
|
||||||
|
|lc| lc + b,
|
||||||
|
|lc| lc + a + b - c,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create some parameters, create a proof, and verify the proof.
|
||||||
|
fn main() {
|
||||||
|
use rand::thread_rng;
|
||||||
|
|
||||||
|
use bellman::groth16::{
|
||||||
|
create_random_proof, generate_random_parameters, prepare_verifying_key, verify_proof
|
||||||
|
};
|
||||||
|
|
||||||
|
let rng = &mut thread_rng();
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
let c = XorCircuit::<Bn256> {
|
||||||
|
a: None,
|
||||||
|
b: None,
|
||||||
|
c: None,
|
||||||
|
};
|
||||||
|
generate_random_parameters(c, rng).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pvk = prepare_verifying_key(¶ms.vk);
|
||||||
|
|
||||||
|
// here we allocate actual variables
|
||||||
|
let c = XorCircuit {
|
||||||
|
a: Some(Fr::one()),
|
||||||
|
b: Some(Fr::zero()),
|
||||||
|
c: Some(Fr::one()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a groth16 proof with our parameters.
|
||||||
|
let proof = create_random_proof(c, ¶ms, rng).unwrap();
|
||||||
|
|
||||||
|
// `inputs` slice contains public parameters encoded as field elements Fr
|
||||||
|
|
||||||
|
// incorrect input tests
|
||||||
|
let inputs = &[Fr::from_str("5").unwrap()];
|
||||||
|
let success = verify_proof(&pvk, &proof, inputs).unwrap();
|
||||||
|
assert!(!success); // fails, because 5 is not 1 or 0
|
||||||
|
|
||||||
|
let inputs = &[Fr::zero()];
|
||||||
|
let success = verify_proof(&pvk, &proof, inputs).unwrap();
|
||||||
|
assert!(!success); // fails because 0 != 0 xor 1
|
||||||
|
|
||||||
|
// correct input test
|
||||||
|
let inputs = &[Fr::one()];
|
||||||
|
let success = verify_proof(&pvk, &proof, inputs).unwrap();
|
||||||
|
assert!(success);
|
||||||
|
println!("{}", bellman_demo::verifier_contract::generate_demo_contract(¶ms.vk, &proof, inputs, ""));
|
||||||
|
}
|
9
src/demo/src/lib.rs
Normal file
9
src/demo/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#![allow(unused_imports)]
|
||||||
|
#![allow(unused_variables)]
|
||||||
|
extern crate bellman;
|
||||||
|
extern crate pairing;
|
||||||
|
extern crate rand;
|
||||||
|
extern crate hex;
|
||||||
|
extern crate ff;
|
||||||
|
|
||||||
|
pub mod verifier_contract;
|
164
src/demo/src/verifier_contract.rs
Normal file
164
src/demo/src/verifier_contract.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
// Library to generate a demo EVM verifier contract
|
||||||
|
|
||||||
|
use bellman::{Circuit, ConstraintSystem, SynthesisError};
|
||||||
|
use ff::{Field, PrimeField};
|
||||||
|
use pairing::{Engine, CurveAffine, EncodedPoint};
|
||||||
|
use bellman::groth16;
|
||||||
|
use pairing::bn256::{Bn256, Fr};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub fn generate_demo_contract<E: Engine>(vk: &groth16::VerifyingKey<E>, proof: &groth16::Proof<E>, inputs: &[E::Fr], inputs_extra: &str) -> String {
|
||||||
|
format!("{}{}", STANDARD_VERIFIER, demo_verifier(vk, proof, inputs, inputs_extra))
|
||||||
|
}
|
||||||
|
|
||||||
|
const STANDARD_VERIFIER: &str =
|
||||||
|
r#"pragma solidity ^0.4.24;
|
||||||
|
|
||||||
|
// from https://github.com/HarryR/ethsnarks/blob/master/contracts/Verifier.sol
|
||||||
|
contract Verifier {
|
||||||
|
|
||||||
|
function NegateY( uint256 Y ) internal pure returns (uint256) {
|
||||||
|
uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||||
|
return q - (Y % q);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Verify ( uint256[14] in_vk, uint256[] vk_gammaABC, uint256[8] in_proof, uint256[] proof_inputs ) internal view returns (bool) {
|
||||||
|
require( ((vk_gammaABC.length / 2) - 1) == proof_inputs.length );
|
||||||
|
|
||||||
|
// Compute the linear combination vk_x
|
||||||
|
uint256[3] memory mul_input;
|
||||||
|
uint256[4] memory add_input;
|
||||||
|
bool success;
|
||||||
|
uint m = 2;
|
||||||
|
|
||||||
|
// First two fields are used as the sum
|
||||||
|
add_input[0] = vk_gammaABC[0];
|
||||||
|
add_input[1] = vk_gammaABC[1];
|
||||||
|
|
||||||
|
// Performs a sum of gammaABC[0] + sum[ gammaABC[i+1]^proof_inputs[i] ]
|
||||||
|
for (uint i = 0; i < proof_inputs.length; i++) {
|
||||||
|
mul_input[0] = vk_gammaABC[m++];
|
||||||
|
mul_input[1] = vk_gammaABC[m++];
|
||||||
|
mul_input[2] = proof_inputs[i];
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
// ECMUL, output to last 2 elements of `add_input`
|
||||||
|
success := staticcall(sub(gas, 2000), 7, mul_input, 0x80, add(add_input, 0x40), 0x60)
|
||||||
|
}
|
||||||
|
require( success );
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
// ECADD
|
||||||
|
success := staticcall(sub(gas, 2000), 6, add_input, 0xc0, add_input, 0x60)
|
||||||
|
}
|
||||||
|
require( success );
|
||||||
|
}
|
||||||
|
|
||||||
|
uint[24] memory input = [
|
||||||
|
// (proof.A, proof.B)
|
||||||
|
in_proof[0], in_proof[1], // proof.A (G1)
|
||||||
|
in_proof[2], in_proof[3], in_proof[4], in_proof[5], // proof.B (G2)
|
||||||
|
|
||||||
|
// (-vk.alpha, vk.beta)
|
||||||
|
in_vk[0], NegateY(in_vk[1]), // -vk.alpha (G1)
|
||||||
|
in_vk[2], in_vk[3], in_vk[4], in_vk[5], // vk.beta (G2)
|
||||||
|
|
||||||
|
// (-vk_x, vk.gamma)
|
||||||
|
add_input[0], NegateY(add_input[1]), // -vk_x (G1)
|
||||||
|
in_vk[6], in_vk[7], in_vk[8], in_vk[9], // vk.gamma (G2)
|
||||||
|
|
||||||
|
// (-proof.C, vk.delta)
|
||||||
|
in_proof[6], NegateY(in_proof[7]), // -proof.C (G1)
|
||||||
|
in_vk[10], in_vk[11], in_vk[12], in_vk[13] // vk.delta (G2)
|
||||||
|
];
|
||||||
|
|
||||||
|
uint[1] memory out;
|
||||||
|
assembly {
|
||||||
|
success := staticcall(sub(gas, 2000), 8, input, 768, out, 0x20)
|
||||||
|
}
|
||||||
|
require(success);
|
||||||
|
return out[0] != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fn demo_verifier<E: Engine>(vk: &groth16::VerifyingKey<E>, proof: &groth16::Proof<E>, inputs: &[E::Fr], inputs_extra: &str) -> String {
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
contract DemoVerifier is Verifier {{
|
||||||
|
|
||||||
|
function getVk() internal view returns (uint256[14] memory vk, uint256[] memory gammaABC) {{
|
||||||
|
{vk}
|
||||||
|
}}
|
||||||
|
|
||||||
|
function getProof() internal view returns (uint256[8] memory proof) {{
|
||||||
|
{proof}
|
||||||
|
}}
|
||||||
|
|
||||||
|
function getInputs() internal view returns (uint256[] memory inputs) {{
|
||||||
|
{inputs}
|
||||||
|
{inputs_extra}
|
||||||
|
}}
|
||||||
|
|
||||||
|
function verify( ) public view returns (bool) {{
|
||||||
|
var (vk, gammaABC) = getVk();
|
||||||
|
return Verify(vk, gammaABC, getProof(), getInputs());
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
vk = hardcode_vk(vk),
|
||||||
|
proof = hardcode_proof(proof),
|
||||||
|
inputs = hardcode_inputs::<E>(inputs),
|
||||||
|
inputs_extra = inputs_extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack<T: CurveAffine>(t: &T) -> Vec<String>
|
||||||
|
{
|
||||||
|
t.into_uncompressed().as_ref().chunks(32).map(|c| "0x".to_owned() + &hex::encode(c)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHIFT: &str = " ";
|
||||||
|
|
||||||
|
fn render_array(name: &str, allocate: bool, values: &[Vec<String>]) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
out.push('\n');
|
||||||
|
let flattened: Vec<&String> = values.into_iter().flatten().collect();
|
||||||
|
if allocate {
|
||||||
|
out.push_str(&format!("{}{} = new uint256[]({});\n", SHIFT, name, flattened.len()));
|
||||||
|
}
|
||||||
|
for (i, s) in flattened.iter().enumerate() {
|
||||||
|
out.push_str(&format!("{}{}[{}] = {};\n", SHIFT, name, i, s));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hardcode_vk<E: Engine>(vk: &groth16::VerifyingKey<E>) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
let values = &[
|
||||||
|
unpack(&vk.alpha_g1),
|
||||||
|
unpack(&vk.beta_g2),
|
||||||
|
unpack(&vk.gamma_g2),
|
||||||
|
unpack(&vk.delta_g2),
|
||||||
|
];
|
||||||
|
out.push_str(&render_array("vk", false, values));
|
||||||
|
|
||||||
|
let ic: Vec<Vec<String>> = vk.ic.iter().map(unpack).collect();
|
||||||
|
out.push_str(&render_array("gammaABC", true, ic.as_slice()));
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hardcode_proof<E: Engine>(proof: &groth16::Proof<E>) -> String {
|
||||||
|
let values = &[
|
||||||
|
unpack(&proof.a),
|
||||||
|
unpack(&proof.b),
|
||||||
|
unpack(&proof.c),
|
||||||
|
];
|
||||||
|
render_array("proof", false, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hardcode_inputs<E: Engine>(inputs: &[E::Fr]) -> String {
|
||||||
|
let values: Vec<Vec<String>> = inputs.iter().map(|i| {vec!(format!("{}", inputs[0].into_repr()))}).collect();
|
||||||
|
render_array("inputs", true, values.as_slice())
|
||||||
|
}
|
@ -436,7 +436,7 @@ use std::env;
|
|||||||
fn verbose_flag() -> bool {
|
fn verbose_flag() -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
if VERBOSE_SWITCH < 0 {
|
if VERBOSE_SWITCH < 0 {
|
||||||
VERBOSE_SWITCH = FromStr::from_str(&env::var("BELLMAN_VERBOSE").unwrap_or(String::new())).unwrap_or(0);
|
VERBOSE_SWITCH = FromStr::from_str(&env::var("BELLMAN_VERBOSE").unwrap_or(String::new())).unwrap_or(1);
|
||||||
}
|
}
|
||||||
match VERBOSE_SWITCH {
|
match VERBOSE_SWITCH {
|
||||||
1 => true,
|
1 => true,
|
||||||
|
Loading…
Reference in New Issue
Block a user