diff --git a/Cargo.toml b/Cargo.toml index 7a52f370..5076b458 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,20 +20,22 @@ rand = "0.6" byteorder = "1" serde = "1" serde_derive = "1" +bincode = "1" failure = "0.1" merlin = "1.1" clear_on_drop = "0.2" +zkinterface = { version = "1.3.3", optional = true } [dev-dependencies] hex = "0.3" criterion = "0.2" -bincode = "1" rand_chacha = "0.1" [features] +default = ["yoloproofs"] avx2_backend = ["curve25519-dalek/avx2_backend"] # Disable the yoloproofs feature for the released crate, so that it's not possible for someone to publish a crate using R1CS proofs yet. -# yoloproofs = [] +yoloproofs = ["zkinterface"] [[test]] name = "range_proof" diff --git a/src/bin/zkif_bulletproofs.rs b/src/bin/zkif_bulletproofs.rs new file mode 100644 index 00000000..3f0a03df --- /dev/null +++ b/src/bin/zkif_bulletproofs.rs @@ -0,0 +1,73 @@ +extern crate bincode; +extern crate bulletproofs; +extern crate zkinterface; + +use std::env; +use std::io::{stdin, Read, Write}; +use std::fs::{File, create_dir_all}; +use std::path::Path; +use self::zkinterface::{Result, Reader}; +use bulletproofs::r1cs::{zkinterface_backend, R1CSProof}; + +const USAGE: &str = "Bulletproofs proving system. + + zkif_bulletproofs prove [proof_path] + zkif_bulletproofs verify [proof_path] + +"; + +const DEFAULT_PROOF_PATH: &str = "bulletproofs-proof"; + +pub fn main() -> Result<()> { + let args: Vec = env::args().collect(); + let args: Vec<&str> = args.iter().map(|a| &a[..]).collect(); + if args.len() <= 1 { + eprintln!("{}", USAGE); + return Err("Missing command.".into()); + } + + let command = args[1]; + + let proof_path = if args.len() == 2 { DEFAULT_PROOF_PATH } else { args[2] }; + let proof_path = Path::new(proof_path); + if let Some(parent) = proof_path.parent() { + create_dir_all(parent)?; + } + + let read = || -> Result { + let mut messages = Reader::new(); + messages.read_from(&mut stdin())?; + Ok(messages) + }; + + match &command[..] { + "prove" => main_prove(read()?, proof_path), + "verify" => main_verify(read()?, proof_path), + _ => { + eprintln!("{}", USAGE); + Err(format!("Unknown command {}", command).into()) + } + } +} + +fn main_prove(messages: Reader, proof_path: &Path) -> Result<()> { + let proof = zkinterface_backend::prove(&messages)?; + + // Save to file. + let proof_ser = bincode::serialize(&proof)?; + File::create(proof_path)?.write_all(&proof_ser)?; + + eprintln!("Saved proof into {}", proof_path.display()); + Ok(()) +} + +fn main_verify(messages: Reader, proof_path: &Path) -> Result<()> { + eprintln!("Verifying proof in {}", proof_path.display()); + + // Load from file. + let mut proof_ser = Vec::new(); + File::open(&proof_path)?.read_to_end(&mut proof_ser)?; + let proof: R1CSProof = bincode::deserialize(&proof_ser)?; + + zkinterface_backend::verify(&messages, &proof) +} \ No newline at end of file diff --git a/src/inner_product_proof.rs b/src/inner_product_proof.rs index c1387403..680e6427 100644 --- a/src/inner_product_proof.rs +++ b/src/inner_product_proof.rs @@ -12,7 +12,7 @@ use merlin::Transcript; use errors::ProofError; use transcript::TranscriptProtocol; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct InnerProductProof { pub(crate) L_vec: Vec, pub(crate) R_vec: Vec, diff --git a/src/r1cs/mod.rs b/src/r1cs/mod.rs index 21b8a176..569014e9 100644 --- a/src/r1cs/mod.rs +++ b/src/r1cs/mod.rs @@ -1,6 +1,6 @@ -#![doc(include = "../docs/r1cs-docs-example.md")] +#![doc(include = "../../docs/r1cs-docs-example.md")] -#[doc(include = "../docs/cs-proof.md")] +#[doc(include = "../../docs/cs-proof.md")] mod notes {} mod constraint_system; @@ -8,6 +8,7 @@ mod linear_combination; mod proof; mod prover; mod verifier; +pub mod zkinterface_backend; pub use self::constraint_system::ConstraintSystem; pub use self::linear_combination::{LinearCombination, Variable}; diff --git a/src/r1cs/proof.rs b/src/r1cs/proof.rs index 8ed9272f..1c3904dc 100644 --- a/src/r1cs/proof.rs +++ b/src/r1cs/proof.rs @@ -21,7 +21,7 @@ use inner_product_proof::InnerProductProof; /// the constraint system using /// [`VerifierCS::verify`](::r1cs::VerifierCS::verify) to verify the /// proof. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct R1CSProof { /// Commitment to the values of input wires diff --git a/src/r1cs/zkinterface_backend.rs b/src/r1cs/zkinterface_backend.rs new file mode 100644 index 00000000..3e8de5be --- /dev/null +++ b/src/r1cs/zkinterface_backend.rs @@ -0,0 +1,285 @@ +//! A zkInterface backend using Bulletproofs. + +extern crate curve25519_dalek; +extern crate merlin; +extern crate rand; +extern crate zkinterface; + +use self::zkinterface::{ + Result, Reader, consumers::reader::Term, +}; +use curve25519_dalek::scalar::Scalar; +use errors::R1CSError; +use failure::Fail; +use merlin::Transcript; +use r1cs::ConstraintSystem; +use r1cs::LinearCombination; +use r1cs::Prover; +use r1cs::R1CSProof; +use r1cs::Variable; +use r1cs::Verifier; +use std::cmp::min; +use std::collections::HashMap; +use BulletproofGens; +use PedersenGens; + +/// Generate a proof using zkInterface messages: +/// - `Circuit` contains the public inputs. +/// - `R1CSConstraints` contains an R1CS which we convert to an arithmetic circuit on the fly. +/// - `Witness` contains the values to assign to all variables. +pub fn prove(messages: &Reader) -> Result { + // Common + let pc_gens = PedersenGens::default(); + let bp_gens = BulletproofGens::new(128, 1); + let mut transcript = Transcript::new(b"zkInterfaceGadget"); + // /Common + + // 1. Create a prover + let prover = Prover::new(&bp_gens, &pc_gens, &mut transcript); + + // 2. There are no high-level variables. + + // 3. Build a CS + let mut cs = prover.finalize_inputs(); + + gadget_from_messages(&mut cs, messages, true)?; + + // 4. Make a proof + let proof = cs.prove().map_err(|e| e.compat())?; + + Ok(proof) +} + +/// Verify a proof using zkInterface messages: +/// - `Circuit` contains the public inputs. +/// - `R1CSConstraints` contains an R1CS which we convert to an arithmetic circuit on the fly. +pub fn verify(messages: &Reader, proof: &R1CSProof) -> Result<()> { + // Common + let pc_gens = PedersenGens::default(); + let bp_gens = BulletproofGens::new(128, 1); + let mut transcript = Transcript::new(b"zkInterfaceGadget"); + // /Common + + // 1. Create a verifier + let verifier = Verifier::new(&bp_gens, &pc_gens, &mut transcript); + + // 2. There are no high-level variables. + + // 3. Build a CS + let mut cs = verifier.finalize_inputs(); + + gadget_from_messages(&mut cs, messages, false)?; + + // 4. Verify the proof + cs.verify(&proof) + .map_err(|_| R1CSError::VerificationError.compat().into()) +} + +/// A gadget using a circuit in zkInterface messages. +pub fn gadget_from_messages( + cs: &mut CS, + messages: &Reader, + prover: bool, +) -> Result<()> { + let public_vars = messages + .instance_variables() + .ok_or("Missing Circuit.connections")?; + + let private_vars = messages + .private_variables() + .ok_or("Missing Circuit.connections")?; + + // Map zkif variables to Bulletproofs's equivalent, LinearCombination. + let mut id_to_lc = HashMap::::new(); + + // Prover tracks the values assigned to zkif variables in order to evaluate the gates. + let mut id_to_value = HashMap::::new(); + + // Map constant one. + id_to_lc.insert(0, Variable::One().into()); + if prover { + id_to_value.insert(0, Scalar::one()); + } + + // Map public inputs. + for var in public_vars { + let val = scalar_from_zkif(var.value)?; + id_to_lc.insert(var.id, val.into()); + + if prover { + id_to_value.insert(var.id, val); + } + + // eprintln!("public{} = {:?}", var.id, val); + } + // eprintln!(); + + // Map witness (if prover). + if prover { + for var in private_vars.iter() { + let val = scalar_from_zkif(var.value)?; + id_to_value.insert(var.id, val); + + // eprintln!("private{} = {:?}", var.id, val); + } + // eprintln!(); + } + + // Step 1: Allocate one mult gate per R1CS constraint. + let mut gates_a = vec![]; + let mut gates_b = vec![]; + let mut gates_c = vec![]; + + for constraint in messages.iter_constraints() { + let (gate_a, gate_b, gate_c) = cs + .allocate(|| { + Ok(( + // Prover evaluates the incoming linear combinations using the witness. + eval_zkif_lc(&id_to_value, &constraint.a), + eval_zkif_lc(&id_to_value, &constraint.b), + eval_zkif_lc(&id_to_value, &constraint.c), + )) + }) + .map_err(|e| e.compat())?; + + gates_a.push(gate_a); + gates_b.push(gate_b); + gates_c.push(gate_c); + + // XXX: If constraint.a/b/c is just x, insert id_to_lc[x.id] = gate_var + } + + // Step 2: Allocate extra gates for variables that are not yet defined. + for circuit_var in private_vars.iter() { + if !id_to_lc.contains_key(&circuit_var.id) { + let (gate_var, _, _) = cs + .allocate(|| { + // Prover takes the value from witness. + let val = id_to_value.get(&circuit_var.id); + Ok(( + val.unwrap().clone(), + Scalar::zero(), // Dummy. + Scalar::zero(), // Dummy. + )) + }) + .map_err(|e| e.compat())?; + + id_to_lc.insert(circuit_var.id, gate_var.into()); + // eprintln!("private{} allocated to {:?}", circuit_var.id, gate_var); + } + } + // eprintln!(); + + // Step 3: Add linear constraints into each wire of each gate. + for (i, constraint) in messages.iter_constraints().enumerate() { + // eprintln!("constraint {}:", i); + + let lc_a = convert_zkif_lc(&id_to_lc, &constraint.a)?; + // eprintln!(" A = {:?}", lc_a); + cs.constrain(lc_a - gates_a[i]); + + let lc_b = convert_zkif_lc(&id_to_lc, &constraint.b)?; + // eprintln!(" B = {:?}", lc_b); + cs.constrain(lc_b - gates_b[i]); + + let lc_c = convert_zkif_lc(&id_to_lc, &constraint.c)?; + // eprintln!(" C = {:?}", lc_c); + cs.constrain(lc_c - gates_c[i]); + + // eprintln!(); + // XXX: Skip trivial constraints where the lc was defined as just the gate var. + } + + // XXX: optimize gate allocation. + // - Detect trivial LC wires = 1 * x. Then use the gate wire as variable x. + // Skip dummy allocation in step 2, and skip constraint in step 3. + // - Detect when the LC going into a gate contains a single new variable 1.x, + // set x = wire - (other terms in existing variables). + // - Allocate two variables at once (left, right, ignore output)? + // - Try to reorder the constraints to minimize dummy gates allocations. + + Ok(()) +} + +/// This is a gadget equivalent to the zkinterface example circuit: x^2 + y^2 = zz +fn _example_gadget(cs: &mut CS) -> Result<()> { + let x = LinearCombination::from(3 as u64); + let y = LinearCombination::from(4 as u64); + let zz = LinearCombination::from(25 as u64); + + let (_, _, xx) = cs.multiply(x.clone(), x); + let (_, _, yy) = cs.multiply(y.clone(), y); + + cs.constrain(xx + yy - zz); + + Ok(()) +} + +/// Convert zkInterface little-endian bytes to Dalek Scalar. +fn scalar_from_zkif(le_bytes: &[u8]) -> Result { + let mut bytes32 = [0; 32]; + let l = min(le_bytes.len(), 32); + bytes32[..l].copy_from_slice(&le_bytes[..l]); + Scalar::from_canonical_bytes(bytes32).ok_or("Invalid scalar encoding".into()) +} + +fn convert_zkif_lc( + id_to_lc: &HashMap, + zkif_terms: &[Term], +) -> Result { + let mut lc = LinearCombination::default(); + + for term in zkif_terms { + let var = id_to_lc + .get(&term.id) + .ok_or(format!("Unknown var {}", term.id))?; + let coeff = scalar_from_zkif(term.value)?; + lc = lc + (var.clone() * coeff); + } + + Ok(lc) +} + +fn eval_zkif_lc(id_to_value: &HashMap, terms: &[Term]) -> Scalar { + terms + .iter() + .map(|term| { + let val = match id_to_value.get(&term.id) { + Some(s) => s.clone(), + None => Scalar::zero(), + }; + let coeff = scalar_from_zkif(term.value).unwrap(); + coeff * val + }) + .sum() +} + +#[test] +fn test_zkinterface_backend() { + use self::zkinterface::producers::examples; + + // Load test messages common to the prover and verifier: Circuit and Constraints. + let verifier_messages = { + let mut buf = Vec::::new(); + examples::example_circuit_header().write_into(&mut buf).unwrap(); + examples::example_constraints().write_into(&mut buf).unwrap(); + let mut msg = Reader::new(); + msg.push_message(buf).unwrap(); + msg + }; + + // Prover uses an additional message: Witness. + let prover_messages = { + let mut msg = verifier_messages.clone(); + let mut buf = Vec::::new(); + examples::example_witness().write_into(&mut buf).unwrap(); + msg.push_message(buf).unwrap(); + msg + }; + + // Prove using the witness. + let proof = prove(&prover_messages).unwrap(); + + // Verify using the circuit and the proof. + verify(&verifier_messages, &proof).unwrap(); +} diff --git a/src/range_proof/mod.rs b/src/range_proof/mod.rs index f4e0918e..c680ae78 100644 --- a/src/range_proof/mod.rs +++ b/src/range_proof/mod.rs @@ -1,5 +1,5 @@ #![allow(non_snake_case)] -#![doc(include = "../docs/range-proof-protocol.md")] +#![doc(include = "../../docs/range-proof-protocol.md")] use rand;