demo added, docs updated

This commit is contained in:
Alex 2018-12-21 11:24:33 +08:00
parent e544678a6a
commit d9be906ed7
8 changed files with 343 additions and 2 deletions

@ -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

@ -0,0 +1,2 @@
/target
**/*.rs.bk

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

@ -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

@ -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(&params.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, &params, 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(&params.vk, &proof, inputs, ""));
}

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;

@ -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,