From 00daecce8cdf4262ab1e3cb537b8ffaa85a5c2a3 Mon Sep 17 00:00:00 2001 From: lovesh Date: Mon, 2 Oct 2023 17:29:15 +0530 Subject: [PATCH] Add range proof protocols Signed-off-by: lovesh --- Cargo.toml | 4 +- bbs_plus/Cargo.toml | 10 +- benches/Cargo.toml | 10 +- benches/benches/dkls19_batch_mul_2p.rs | 2 +- bulletproofs_plus_plus/Cargo.toml | 33 + bulletproofs_plus_plus/src/error.rs | 12 + bulletproofs_plus_plus/src/lib.rs | 18 + .../src/range_proof_arbitrary_range.rs | 263 ++++ bulletproofs_plus_plus/src/rangeproof.rs | 1195 +++++++++++++++++ bulletproofs_plus_plus/src/setup.rs | 117 ++ bulletproofs_plus_plus/src/util.rs | 4 + .../src/weighted_norm_linear_argument.rs | 421 ++++++ coconut/Cargo.toml | 8 +- compressed_sigma/Cargo.toml | 4 +- compressed_sigma/src/utils.rs | 17 +- delegatable_credentials/Cargo.toml | 6 +- kvac/Cargo.toml | 4 +- legogroth16/Cargo.toml | 4 +- legogroth16/src/circom/circuit.rs | 2 +- oblivious_transfer/Cargo.toml | 6 +- proof_system/Cargo.toml | 22 +- proof_system/src/derived_params.rs | 31 +- proof_system/src/error.rs | 18 + proof_system/src/proof_spec.rs | 62 +- proof_system/src/prover.rs | 164 ++- proof_system/src/setup_params.rs | 27 +- proof_system/src/statement/bound_check_bpp.rs | 69 + .../src/statement/bound_check_legogroth16.rs | 22 +- proof_system/src/statement/bound_check_smc.rs | 127 ++ .../src/statement/bound_check_smc_with_kv.rs | 184 +++ proof_system/src/statement/mod.rs | 27 +- proof_system/src/statement_proof.rs | 217 ++- .../src/sub_protocols/bound_check_bpp.rs | 207 +++ .../sub_protocols/bound_check_legogroth16.rs | 48 +- .../src/sub_protocols/bound_check_smc.rs | 258 ++++ .../sub_protocols/bound_check_smc_with_kv.rs | 269 ++++ proof_system/src/sub_protocols/mod.rs | 56 +- proof_system/src/sub_protocols/saver.rs | 2 + proof_system/src/verifier.rs | 428 +++--- proof_system/src/witness.rs | 14 +- proof_system/tests/bound_check_bpp.rs | 179 +++ .../{bounds.rs => bound_check_legogroth16.rs} | 82 +- proof_system/tests/bound_check_smc.rs | 255 ++++ proof_system/tests/bound_check_smc_with_kv.rs | 277 ++++ saver/Cargo.toml | 6 +- schnorr_pok/Cargo.toml | 4 +- secret_sharing_and_dkg/Cargo.toml | 6 +- smc_range_proof/Cargo.toml | 28 + smc_range_proof/README.md | 22 + smc_range_proof/src/bb_sig.rs | 227 ++++ .../src/ccs_range_proof/arbitrary_range.rs | 577 ++++++++ .../src/ccs_range_proof/kv_arbitrary_range.rs | 439 ++++++ .../src/ccs_range_proof/kv_perfect_range.rs | 299 +++++ smc_range_proof/src/ccs_range_proof/mod.rs | 11 + .../src/ccs_range_proof/perfect_range.rs | 372 +++++ smc_range_proof/src/ccs_range_proof/util.rs | 129 ++ .../src/ccs_set_membership/batch_members.rs | 302 +++++ .../src/ccs_set_membership/kv_single.rs | 212 +++ smc_range_proof/src/ccs_set_membership/mod.rs | 6 + .../src/ccs_set_membership/setup.rs | 205 +++ .../src/ccs_set_membership/single_member.rs | 251 ++++ .../src/cls_range_proof/kv_range_proof.rs | 435 ++++++ smc_range_proof/src/cls_range_proof/mod.rs | 8 + .../src/cls_range_proof/range_proof.rs | 555 ++++++++ smc_range_proof/src/cls_range_proof/util.rs | 163 +++ smc_range_proof/src/common.rs | 135 ++ smc_range_proof/src/error.rs | 22 + smc_range_proof/src/lib.rs | 45 + test_utils/Cargo.toml | 8 +- utils/Cargo.toml | 2 +- utils/src/ff.rs | 143 +- utils/src/transcript.rs | 2 +- vb_accumulator/Cargo.toml | 6 +- 73 files changed, 9346 insertions(+), 459 deletions(-) create mode 100644 bulletproofs_plus_plus/Cargo.toml create mode 100644 bulletproofs_plus_plus/src/error.rs create mode 100644 bulletproofs_plus_plus/src/lib.rs create mode 100644 bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs create mode 100644 bulletproofs_plus_plus/src/rangeproof.rs create mode 100644 bulletproofs_plus_plus/src/setup.rs create mode 100644 bulletproofs_plus_plus/src/util.rs create mode 100644 bulletproofs_plus_plus/src/weighted_norm_linear_argument.rs create mode 100644 proof_system/src/statement/bound_check_bpp.rs create mode 100644 proof_system/src/statement/bound_check_smc.rs create mode 100644 proof_system/src/statement/bound_check_smc_with_kv.rs create mode 100644 proof_system/src/sub_protocols/bound_check_bpp.rs create mode 100644 proof_system/src/sub_protocols/bound_check_smc.rs create mode 100644 proof_system/src/sub_protocols/bound_check_smc_with_kv.rs create mode 100644 proof_system/tests/bound_check_bpp.rs rename proof_system/tests/{bounds.rs => bound_check_legogroth16.rs} (91%) create mode 100644 proof_system/tests/bound_check_smc.rs create mode 100644 proof_system/tests/bound_check_smc_with_kv.rs create mode 100644 smc_range_proof/Cargo.toml create mode 100644 smc_range_proof/README.md create mode 100644 smc_range_proof/src/bb_sig.rs create mode 100644 smc_range_proof/src/ccs_range_proof/arbitrary_range.rs create mode 100644 smc_range_proof/src/ccs_range_proof/kv_arbitrary_range.rs create mode 100644 smc_range_proof/src/ccs_range_proof/kv_perfect_range.rs create mode 100644 smc_range_proof/src/ccs_range_proof/mod.rs create mode 100644 smc_range_proof/src/ccs_range_proof/perfect_range.rs create mode 100644 smc_range_proof/src/ccs_range_proof/util.rs create mode 100644 smc_range_proof/src/ccs_set_membership/batch_members.rs create mode 100644 smc_range_proof/src/ccs_set_membership/kv_single.rs create mode 100644 smc_range_proof/src/ccs_set_membership/mod.rs create mode 100644 smc_range_proof/src/ccs_set_membership/setup.rs create mode 100644 smc_range_proof/src/ccs_set_membership/single_member.rs create mode 100644 smc_range_proof/src/cls_range_proof/kv_range_proof.rs create mode 100644 smc_range_proof/src/cls_range_proof/mod.rs create mode 100644 smc_range_proof/src/cls_range_proof/range_proof.rs create mode 100644 smc_range_proof/src/cls_range_proof/util.rs create mode 100644 smc_range_proof/src/common.rs create mode 100644 smc_range_proof/src/error.rs create mode 100644 smc_range_proof/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index e79235f5..760cf61d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,10 @@ members = [ "secret_sharing_and_dkg", "legogroth16", "oblivious_transfer", + "kvac", "merlin", - "kvac" + "bulletproofs_plus_plus", + "smc_range_proof" ] resolver = "2" diff --git a/bbs_plus/Cargo.toml b/bbs_plus/Cargo.toml index 830eb3d1..580a9f35 100644 --- a/bbs_plus/Cargo.toml +++ b/bbs_plus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bbs_plus" -version = "0.16.0" +version = "0.17.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -19,10 +19,10 @@ ark-std.workspace = true digest.workspace = true rayon = {workspace = true, optional = true} itertools.workspace = true -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } -oblivious_transfer_protocols = { version = "0.3.0", default-features = false, path = "../oblivious_transfer" } -secret_sharing_and_dkg = { version = "0.7.0", default-features = false, path = "../secret_sharing_and_dkg" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } +oblivious_transfer_protocols = { version = "0.4.0", default-features = false, path = "../oblivious_transfer" } +secret_sharing_and_dkg = { version = "0.8.0", default-features = false, path = "../secret_sharing_and_dkg" } sha3 = { version = "0.10.6", default-features = false } serde.workspace = true serde_with.workspace = true diff --git a/benches/Cargo.toml b/benches/Cargo.toml index e41f3739..e2f45fa8 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -6,9 +6,9 @@ authors.workspace = true license.workspace = true [dependencies] -bbs_plus = { version = "0.16.0", default-features = false, path = "../bbs_plus" } -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } -vb_accumulator = { version = "0.17.0", default-features = false, path = "../vb_accumulator" } +bbs_plus = { version = "0.17.0", default-features = false, path = "../bbs_plus" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } +vb_accumulator = { version = "0.18.0", default-features = false, path = "../vb_accumulator" } test_utils = { default-features = false, path = "../test_utils" } ark-ff.workspace = true ark-ec.workspace = true @@ -18,8 +18,8 @@ serde.workspace = true serde_with.workspace = true blake2 = { version = "0.10", default-features = false } itertools.workspace = true -coconut-crypto = { version = "0.5.0", default-features = false, path = "../coconut" } -oblivious_transfer_protocols = { version = "0.3.0", default-features = false, path = "../oblivious_transfer" } +coconut-crypto = { version = "0.6.0", default-features = false, path = "../coconut" } +oblivious_transfer_protocols = { version = "0.4.0", default-features = false, path = "../oblivious_transfer" } dock_crypto_utils = { default-features = false, path = "../utils" } zeroize.workspace = true diff --git a/benches/benches/dkls19_batch_mul_2p.rs b/benches/benches/dkls19_batch_mul_2p.rs index 14447c63..5edd396d 100644 --- a/benches/benches/dkls19_batch_mul_2p.rs +++ b/benches/benches/dkls19_batch_mul_2p.rs @@ -82,7 +82,7 @@ fn batch_multiplication(c: &mut Criterion) { ) .unwrap(); - let (party2, U, kos_rlc, gamma_b) = Party2::new( + let (party2, _, kos_rlc, gamma_b) = Party2::new( &mut rng, beta.clone(), base_ot_sender_keys.clone(), diff --git a/bulletproofs_plus_plus/Cargo.toml b/bulletproofs_plus_plus/Cargo.toml new file mode 100644 index 00000000..affd77c0 --- /dev/null +++ b/bulletproofs_plus_plus/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "bulletproofs_plus_plus" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description = "Bulletproofs++" + +[dependencies] +ark-ff.workspace = true +ark-ec.workspace = true +ark-std.workspace = true +ark-serialize.workspace = true +ark-poly.workspace = true +digest.workspace = true +serde.workspace = true +serde_with.workspace = true +zeroize.workspace = true +rayon = { workspace = true, optional = true } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } + +[dev-dependencies] +blake2.workspace = true +ark-bls12-381.workspace = true +ark-ed25519 = { version = "^0.4.0", default-features = false } +ark-curve25519 = { version = "^0.4.0", default-features = false } +ark-secp256k1 = { version = "^0.4.0", default-features = false } + +[features] +default = [ "parallel"] +std = [ "ark-ff/std", "ark-ec/std", "ark-std/std", "ark-serialize/std", "serde/std", "ark-poly/std", "dock_crypto_utils/std"] +parallel = [ "std", "ark-ff/parallel", "ark-ec/parallel", "ark-std/parallel", "rayon", "ark-poly/parallel", "dock_crypto_utils/parallel"] \ No newline at end of file diff --git a/bulletproofs_plus_plus/src/error.rs b/bulletproofs_plus_plus/src/error.rs new file mode 100644 index 00000000..ebef7d58 --- /dev/null +++ b/bulletproofs_plus_plus/src/error.rs @@ -0,0 +1,12 @@ +use ark_std::string::String; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub enum BulletproofsPlusPlusError { + UnexpectedLengthOfVectors(String), + WeightedNormLinearArgumentVerificationFailed, + ExpectedPowerOfTwo(String), + ValueIncompatibleWithBase(String), + IncorrectBounds(String), + IncorrectNumberOfCommitments(usize, usize), +} diff --git a/bulletproofs_plus_plus/src/lib.rs b/bulletproofs_plus_plus/src/lib.rs new file mode 100644 index 00000000..941e5296 --- /dev/null +++ b/bulletproofs_plus_plus/src/lib.rs @@ -0,0 +1,18 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(non_snake_case)] + +pub mod error; +pub mod range_proof_arbitrary_range; +pub mod rangeproof; +pub mod setup; +pub mod util; +pub mod weighted_norm_linear_argument; + +pub mod prelude { + pub use crate::{ + error::BulletproofsPlusPlusError, + range_proof_arbitrary_range::ProofArbitraryRange, + rangeproof::{Proof, Prover}, + setup::SetupParams, + }; +} diff --git a/bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs b/bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs new file mode 100644 index 00000000..d341db65 --- /dev/null +++ b/bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs @@ -0,0 +1,263 @@ +use ark_ec::{AffineRepr, CurveGroup}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +use ark_std::{format, rand::RngCore, vec::Vec}; + +use crate::rangeproof::{Proof, Prover}; + +use crate::error::BulletproofsPlusPlusError; +use dock_crypto_utils::transcript::Transcript; + +use crate::setup::SetupParams; +use dock_crypto_utils::msm::WindowTable; + +/// Range proof for values in arbitrary ranges where each value `v_i` belongs to interval `[min_i, max_i)` +/// Uses the range proof for perfect ranges of form `[0, base^l)` where upper bound is a power of the base. +/// It splits a single range check of the form `min_i <= v_i < max_i` into 2 as `0 <= v_i - min_i` and `0 <= max_i - 1 - v_i` +/// and creates proofs both both checks. Along the proofs, it outputs commitments to `v_i - min_i` and `max_i - 1 - v_i` as +/// `g * (v_i - min_i) + h * {r_i}_1` and `g * (max_i - 1 - v_i) + h * {r_i}_2` respectively and both which can be +/// transformed to `g * v_i + h * {r_i}_1`, `g * v_i + h * {r_i}_2` by the verifier and the prover proves that +/// `v_i` in `g * v_i + h * r_i` in is same as `v_i` in `g * v_i + h * {r_i}_1`, `g * v_i + h * {r_i}_2` +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct ProofArbitraryRange { + pub V: Vec, + pub proof: Proof, +} + +impl ProofArbitraryRange { + pub fn new( + rng: &mut R, + num_bits: u16, + values_and_bounds: Vec<(u64, u64, u64)>, + randomness: Vec, + setup_params: SetupParams, + transcript: &mut impl Transcript, + ) -> Result { + // TODO: Fx base + let base = 2; + Self::new_with_given_base( + rng, + base, + num_bits, + values_and_bounds, + randomness, + setup_params, + transcript, + ) + } + + pub fn new_with_given_base( + rng: &mut R, + base: u16, + num_bits: u16, + values_and_bounds: Vec<(u64, u64, u64)>, + randomness: Vec, + setup_params: SetupParams, + transcript: &mut impl Transcript, + ) -> Result { + if values_and_bounds.len() * 2 != randomness.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of randomness={} should be double of length of values_and_bounds={}", + values_and_bounds.len(), + randomness.len() + ), + )); + } + let mut V = Vec::::with_capacity(randomness.len()); + let mut v = Vec::::with_capacity(randomness.len()); + for (i, (v_i, min, max)) in values_and_bounds.iter().enumerate() { + if min > v_i { + return Err(BulletproofsPlusPlusError::IncorrectBounds(format!( + "value={} should be >= min={}", + v_i, min + ))); + } + if v_i >= max { + return Err(BulletproofsPlusPlusError::IncorrectBounds(format!( + "value={} should be < max={}", + v_i, max + ))); + } + // Commit to `v_i - min` as `g * (v_i - min) + h * randomness[2 * i]` + V.push(setup_params.compute_pedersen_commitment(v_i - min, &randomness[2 * i])); + // Commit to `max - 1 - v_i` as `g * (max -1 - v_i) + h * randomness[2 * i]` + V.push(setup_params.compute_pedersen_commitment(max - 1 - v_i, &randomness[2 * i + 1])); + v.push(v_i - min); + v.push(max - 1 - v_i); + } + let prover = Prover::new_with_given_base(base, num_bits, V.clone(), v, randomness)?; + let proof = prover.prove(rng, setup_params, transcript)?; + Ok(Self { V, proof }) + } + + pub fn verify( + &self, + num_bits: u16, + setup_params: &SetupParams, + transcript: &mut impl Transcript, + ) -> Result<(), BulletproofsPlusPlusError> { + self.proof + .verify(num_bits, &self.V, setup_params, transcript) + } + + pub fn num_proofs(&self) -> u32 { + self.V.len() as u32 / 2 + } + + /// Returns a vector of tuples where each tuple is a pair of commitments as (`(v_i - min_i)`, `(max_i - v_i)`) + pub fn get_split_commitments(&self) -> Vec<(G, G)> { + let mut comms = Vec::with_capacity(self.num_proofs() as usize); + for i in (0..self.V.len()).step_by(2) { + comms.push((self.V[i], self.V[i + 1])); + } + comms + } + + /// Returns a vector of tuples where each tuple is a pair of commitments to the `v_i` but with different randomnesses + pub fn get_commitments_to_values( + &self, + bounds: Vec<(u64, u64)>, + setup_params: &SetupParams, + ) -> Result, BulletproofsPlusPlusError> { + self.get_commitments_to_values_given_g(bounds, &setup_params.G) + } + + /// Same as `Self::get_commitments_to_values` but accepts the generator `g` from the setup params + pub fn get_commitments_to_values_given_g( + &self, + bounds: Vec<(u64, u64)>, + g: &G, + ) -> Result, BulletproofsPlusPlusError> { + if bounds.len() != self.num_proofs() as usize { + return Err(BulletproofsPlusPlusError::IncorrectNumberOfCommitments( + bounds.len(), + self.num_proofs() as usize, + )); + } + let table = WindowTable::new(self.num_proofs() as usize * 2, g.into_group()); + let mut comms = Vec::with_capacity(self.num_proofs() as usize); + for i in (0..self.V.len()).step_by(2) { + let (min, max) = (bounds[i / 2].0, bounds[i / 2].1); + if max <= min { + return Err(BulletproofsPlusPlusError::IncorrectBounds(format!( + "max={} should be > min={}", + max, min + ))); + } + // `V[i]` is a commitment to `value - min` and `V[i+1]` is a commitment to `max - 1 - value`. Generate commitments + // to value by `V[i] + g * min` and `g * (max - 1) - V[i+1]` + comms.push(( + (self.V[i] + table.multiply(&G::ScalarField::from(min))).into_affine(), + (table.multiply(&G::ScalarField::from(max - 1)) - self.V[i + 1]).into_affine(), + )); + } + Ok(comms) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_std::{ + rand::{prelude::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use dock_crypto_utils::transcript::new_merlin_transcript; + use std::time::{Duration, Instant}; + + fn test_rangeproof_for_arbitrary_range( + base: u16, + num_bits: u16, + values_and_bounds: Vec<(u64, u64, u64)>, + ) -> (Duration, Duration) { + let mut rng = StdRng::seed_from_u64(0u64); + + let mut gamma = vec![]; + for _ in 0..values_and_bounds.len() * 2 { + gamma.push(G::ScalarField::rand(&mut rng)); + } + + let setup_params = SetupParams::::new_for_arbitrary_range_proof::( + b"test", + base, + num_bits, + values_and_bounds.len() as u32, + ); + + let start = Instant::now(); + let mut transcript = new_merlin_transcript(b"BPP/tests"); + let proof = ProofArbitraryRange::new_with_given_base( + &mut rng, + base, + num_bits, + values_and_bounds.clone(), + gamma.clone(), + setup_params.clone(), + &mut transcript, + ) + .unwrap(); + let proving_time = start.elapsed(); + + let bounds = values_and_bounds + .clone() + .into_iter() + .map(|(_, min, max)| (min, max)) + .collect(); + let split_comms = proof.get_split_commitments(); + let comms = proof + .get_commitments_to_values(bounds, &setup_params) + .unwrap(); + + let start = Instant::now(); + let mut transcript = new_merlin_transcript(b"BPP/tests"); + proof + .verify(num_bits, &setup_params, &mut transcript) + .unwrap(); + let verifying_time = start.elapsed(); + + for (i, (v, min, max)) in values_and_bounds.into_iter().enumerate() { + let (comm_min, comm_max) = split_comms[i]; + assert_eq!( + comm_min + setup_params.compute_pedersen_commitment(min, &-gamma[2 * i]), + setup_params.G * G::ScalarField::from(v) + ); + assert_eq!( + setup_params + .compute_pedersen_commitment(max - 1, &gamma[2 * i + 1]) + .into_group() + - comm_max, + setup_params.G * G::ScalarField::from(v) + ); + assert_eq!( + comms[i].0 + setup_params.H_vec[0].mul(-gamma[2 * i]), + setup_params.G * G::ScalarField::from(v) + ); + assert_eq!( + comms[i].1 + setup_params.H_vec[0].mul(gamma[2 * i + 1]), + setup_params.G * G::ScalarField::from(v) + ); + } + + (proving_time, verifying_time) + } + + fn check_for_arbitrary_range() { + for (base, num_bits, val_bounds) in [ + (2, 4, vec![(7, 3, 10)]), + (2, 4, vec![(0, 0, 15), (14, 0, 15)]), + (16, 8, vec![(60, 40, 80), (15, 10, 20)]), + (16, 8, vec![(100, 50, 150)]), + ] { + let size = val_bounds.len(); + let (p, v) = test_rangeproof_for_arbitrary_range::(base, num_bits, val_bounds); + println!("For base={}, max value bits={} for {} checks, proving time = {:?} and verifying time = {:?}", base, num_bits, size, p, v); + } + } + + #[test] + fn rangeproof_bls12381() { + check_for_arbitrary_range::() + } +} diff --git a/bulletproofs_plus_plus/src/rangeproof.rs b/bulletproofs_plus_plus/src/rangeproof.rs new file mode 100644 index 00000000..d6b55524 --- /dev/null +++ b/bulletproofs_plus_plus/src/rangeproof.rs @@ -0,0 +1,1195 @@ +//! Copied from bulletproofs source mentioned in the paper - +//! Some of the duplicate computation has been removed. +//! Rangeproofs: +//! +//! Notation: +//! +//! Notation follows the bulletproofs++ paper. + +use ark_ec::{AffineRepr, CurveGroup, VariableBaseMSM}; +use ark_ff::{batch_inversion, Field, PrimeField, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{ + cfg_into_iter, cfg_iter, cfg_iter_mut, format, ops::Neg, rand::RngCore, vec, vec::Vec, + UniformRand, +}; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::{ + error::BulletproofsPlusPlusError, setup::SetupParams, util, + weighted_norm_linear_argument::WeightedNormLinearArgument, +}; +use dock_crypto_utils::{ + ff::{add_vecs, hadamard_product, inner_product, powers, powers_starting_from, scale}, + join, + transcript::Transcript, +}; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +struct Round1Commitments { + /// Round 1 output: D (commitment to the number of digits) + D: G, + /// Round 1 output: M (commitment to the number of multiplicities) + M: G, +} + +#[derive(Debug, Clone)] +struct Round1Secrets { + /// Vector of digits committed with G_vec + d_vec: Vec, + /// Blinding factor for d in G + r_d0: F, + /// Blinding factor for d in H_vec + r_d1_vec: Vec, + /// Vector of multiplicities + m_vec: Vec, + /// Blinding factor for m in G + r_m0: F, + /// Blinding factor for m in H_vec + r_m1_vec: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +struct Round2Commitments { + /// Reciprocal commitment: R + R: G, +} + +#[derive(Debug, Clone)] +struct Round2Secrets { + /// Reciprocal vector. This is non-zero, but having zero helps in code-dedup + r_vec: Vec, + /// Blinding factor for r in G + r_r0: F, + /// Blinding factor for r in H_vec + r_r1_vec: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +struct Round3Commitments { + /// Round 3 blinding commitment S + S: G, +} + +#[derive(Debug, Clone)] +struct Round3Secrets { + /// Round 3 blinding factor b_s + #[allow(dead_code)] + r_s0: F, + /// Final w_v(T) polynomial + w_poly: Poly, + /// Final l_v(T) polynomial + l_poly: Poly, +} + +impl Round1Commitments { + fn challenge( + &self, + base: u16, + num_bits: u16, + V: &[G], + transcript: &mut impl Transcript, + ) -> G::ScalarField { + transcript.append_message(b"base", &base.to_le_bytes()); + transcript.append_message(b"num_bits", &num_bits.to_le_bytes()); + for V_i in V { + transcript.append(b"V", V_i); + } + transcript.append(b"D", &self.D); + transcript.append(b"M", &self.M); + transcript.challenge_scalar(b"e") + } +} + +impl Round2Commitments { + fn challenges( + &self, + transcript: &mut impl Transcript, + ) -> ( + G::ScalarField, + G::ScalarField, + G::ScalarField, + G::ScalarField, + G::ScalarField, + G::ScalarField, + ) { + transcript.append(b"R", &self.R); + let x = transcript.challenge_scalar(b"x"); + let y = transcript.challenge_scalar(b"y"); + let r = transcript.challenge_scalar(b"r"); + let lambda = transcript.challenge_scalar(b"lambda"); + let delta = transcript.challenge_scalar(b"delta"); + (x, y, r, r.square(), lambda, delta) + } +} + +impl Round3Commitments { + fn challenge(&self, transcript: &mut impl Transcript) -> G::ScalarField { + transcript.append(b"S", &self.S); + transcript.challenge_scalar(b"t") + } +} + +/// BP++ Rangeproof Prover state. +/// The prover state across rounds of the protocol. +/// +/// # Notation +/// +/// In each round of the protocol, the prover computes a commitment of the following form: +/// +/// X = r_x0 * G + + . Here +/// - G is the base generator. G_i generators are associated with n_vec in norm argument +/// while H_i generators are associated with r1_vec, l_vec. +/// - X is the output commitment. (in our case: D, M, R, S) for each round +/// - x_i is the witness vector. (in our case: x = {d, m, r, s}) +/// - r_x_i is the blinding vector. The blinding for 0 is considered along the G dimension +/// while the blinding from 1 onwards are considered along the H dimension. +#[derive(Debug, Clone)] +pub struct Prover { + /// `b` base representation of the value to be proven(2, 4, 8, 16) + base: u16, + /// `n` number of bits in the value to be proven(32, 64, 128) in base 2 + num_bits: u16, + /// The commitments to the values being proven. One commitment each per aggregated proof + V: Vec, + /// The corresponding values committed in V. One value each per aggregated proof + v: Vec, + /// Corresponding blinding factors for the commitments in V. One blinding factor each per aggregated proof + gamma: Vec, + /// Round 1 commitments + r1_comm: Option>, + /// Round 1 secrets + r1_sec: Option>, + /// Round 2 commitments + r2_comm: Option>, + /// Round 2 secrets + r2_sec: Option>, + /// Round 3 commitments + r3_comm: Option>, + /// Round 3 secrets + r3_sec: Option>, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct Proof { + /// `b` base representation of the value to be proven(2, 4, 8, 16) + base: u16, + /// Round 1 commitments + r1_comm: Round1Commitments, + /// Round 2 commitments + r2_comm: Round2Commitments, + /// Round 3 commitments + r3_comm: Round3Commitments, + /// norm proof + norm_proof: WeightedNormLinearArgument, +} + +impl Prover { + pub fn new( + num_bits: u16, + V: Vec, + v: Vec, + gamma: Vec, + ) -> Result { + let base = 2; + Self::new_with_given_base(base, num_bits, V, v, gamma) + } + + /// Creates a new prover instance. + pub fn new_with_given_base( + base: u16, + num_bits: u16, + V: Vec, + v: Vec, + gamma: Vec, + ) -> Result { + if !base.is_power_of_two() { + return Err(BulletproofsPlusPlusError::ExpectedPowerOfTwo(format!( + "base={} but should be a power of 2", + base + ))); + } + if !num_bits.is_power_of_two() { + return Err(BulletproofsPlusPlusError::ExpectedPowerOfTwo(format!( + "num_bits={} but should be a power of 2", + num_bits + ))); + } + if num_bits < util::base_bits(base) { + return Err(BulletproofsPlusPlusError::ValueIncompatibleWithBase(format!("number of bits in value={} which should not be less than number of bits in base={}", num_bits, base))); + } + if v.len() != V.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of values={} not equal to length of commitments={}", + v.len(), + V.len() + ), + )); + } + if v.len() != gamma.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of values={} not equal to length of randomness={}", + v.len(), + gamma.len() + ), + )); + } + Ok(Self { + base, + num_bits, + V, + v, + gamma, + r1_comm: None, + r1_sec: None, + r2_comm: None, + r2_sec: None, + r3_comm: None, + r3_sec: None, + }) + } + + /// Number of proofs to aggregate + pub fn num_proofs(&self) -> usize { + self.V.len() + } + + /// Obtain the number of digits in the base representation + /// Num of digits in single proof times the number of proofs + fn total_num_digits(&self) -> usize { + self.num_digits_per_proof() as usize * self.num_proofs() + } + + /// Obtain the number of digits in the base representation + fn num_digits_per_proof(&self) -> u16 { + self.num_bits / util::base_bits(self.base) + } + + /// Round 1: Commit to the base representation of the value and multiplicities + /// + /// # The digits commitment: D + /// + /// The prover first computes the base representation of the value to be proven. + /// It first computes the commitment D = r_d0 * G + + where d_i + /// is the i-th digit of the base b representation of the value to be proven. The values + /// r_d0 is chosen randomly, while r_d1_vec_i is chosen according to _some_ constraint that we will + /// explain later. Informally, r_d0 being random is sufficient to prove that the commitment is hiding. + /// + /// When aggregating proofs, d_i is the concatenation of the base b representation of all values. + /// For example, base 4, num_bits = 4, v = [9, 13] (two digits per value). + /// d_vec = [1, 2, 1, 3] + /// (1, 2) (1, 3) + /// (4*2 + 1) (4*3 + 1) + /// 9 13 + /// + /// # The multiplicity commitment: M + /// + /// The prover computes the commitment M = r_m0 * G + + where m_i + /// is the multiplicity of the i-th digit in the base b representation of the value to be proven. + /// The values r_m0, and r_m1vec_i are chosen uniformly at random. Similar to the digits commitment, + /// r_m0 being random is sufficient to prove that the commitment is hiding. Multiplicity denotes + /// the number of times a digit appears in the base b representation of the value to be proven. + /// + /// Now, there are two choices for how we want to commit the multiplicities when aggregating proofs. + /// 1) Inline multiplicity mode: In this mode, the prover commits to the multiplicities of all digits + /// one after another by concatenating the base b representation of all values. + /// For the above example, this would be: a) m_vec for 9 = [0, 1, 1, 0] and m_vec for 13 = [0, 1, 0, 1] + /// The final m_vec would be [0, 1, 1, 0, 0, 1, 0, 1]. + /// 2) Shared multiplicity mode: In this mode, the prover commits to the multiplicities of all digits + /// in the base b representation of all values. For example, base 4, num_bits = 4, v = [9, 13] (two digits per value). + /// For the above example, the m_vec would be [0, 1, 1, 0] + [0, 1, 0, 1] = [0, 2, 1, 1]. + /// + /// For the implementation, we use the shared multiplicity mode. The current implementation is not + /// compatible for multi-party proving, since the prover needs to know the multiplicities of all + /// digits in the base b representation of all values. We do not concern with this for now. + fn round_1(&mut self, rng: &mut R, setup_params: &SetupParams) { + let num_base_bits = util::base_bits(self.base); + let num_digits_per_proof = self.num_digits_per_proof(); + let total_num_digits = self.total_num_digits(); + + // d is a vector containing digits of `base`-representation of all `v`s + let mut d = Vec::with_capacity(total_num_digits); + // Shared multiplicity mode for now. + let mut m = vec![0; self.base as usize]; + + // For each `v`, create its `base`-representation and append to `d` + for v in self.v.iter() { + let mut v1 = *v; + for _ in 0..num_digits_per_proof { + let dig = v1 % self.base as u64; + d.push(dig); + // Increase multiplicity by 1 + m[dig as usize] += 1u64; + v1 = v1 >> num_base_bits; + } + } + + let d = d + .into_iter() + .map(|x| G::ScalarField::from(x)) + .collect::>(); + let m = m + .into_iter() + .map(|x| G::ScalarField::from(x)) + .collect::>(); + + let mut r_m1_vec = (0..8) + .map(|_| G::ScalarField::rand(rng)) + .collect::>(); + let mut r_d1_vec = ark_std::iter::repeat(G::ScalarField::zero()) + .take(8) + .collect::>(); + // Restrict some values so that the verification equation holds for all T. + // All other powers can be cancelled by choosing r_s_i adaptively. We only + // need to worry about T^3 and T^7. + { + // Additional t^7 term which cannot be cancelled out: + // delta*lm_v[0, 6] + ld_v[0, 5] + lr_v[0, 4] => lm_v[6] = 0 && ld_v[5] = -lr_v[4] + r_m1_vec[6] = G::ScalarField::zero(); + r_d1_vec[5] = G::ScalarField::rand(rng); + + // Additional t^3 term which cannot be cancelled out: + // delta*lm_v[0, 3] + ld_v[0, 2] + lr_v[0, 1] => lm_v[3] = 0 && ld_v[2] = -lr_v[1] + r_m1_vec[3] = G::ScalarField::zero(); + r_d1_vec[2] = G::ScalarField::rand(rng); + } + + let r_d0 = G::ScalarField::rand(rng); + let r_m0 = G::ScalarField::rand(rng); + let (D, M) = join!( + setup_params.compute_commitment(&r_d0, &r_d1_vec, &d), + setup_params.compute_commitment(&r_m0, &r_m1_vec, &m) + ); + + self.r1_sec = Some(Round1Secrets { + d_vec: d, + r_d0, + r_d1_vec, + m_vec: m, + r_m0, + r_m1_vec, + }); + self.r1_comm = Some(Round1Commitments { D, M }); + } + + /// Prover Round 2: Prover has committed to d_vec and m_vec in the previous round. Received challenge e. + /// + /// # The reciprocal commitment: R + /// + /// The prover computes the commitment R = r_r0 * G + + where r_i = (1/ (e + d_i)) + /// r_r_i are chosen to be all zeros. As before, the values r_r0 being random is sufficient to prove that the commitment is hiding. + /// + fn round_2( + &mut self, + rng: &mut R, + e: G::ScalarField, + setup_params: &SetupParams, + ) { + // compute r_i = (1/ (e + d_i)) + let mut r = cfg_iter!(self.r1_sec.as_ref().unwrap().d_vec) + .map(|x| (e + x)) + .collect::>(); + batch_inversion(&mut r); + + let mut r_r1_vec = ark_std::iter::repeat(G::ScalarField::zero()) + .take(8) + .collect::>(); + { + // Balance out remaining terms in final l_vec + r_r1_vec[4] = -self.r1_sec.as_ref().unwrap().r_d1_vec[5].clone(); // T^7 + r_r1_vec[1] = -self.r1_sec.as_ref().unwrap().r_d1_vec[2].clone(); // T^3 + } + let (r_r0, R) = setup_params.gen_randomness_and_compute_commitment(rng, &r_r1_vec, &r); + self.r2_sec = Some(Round2Secrets { + r_vec: r, + r_r0, + r_r1_vec, + }); + self.r2_comm = Some(Round2Commitments { R }); + } + + /// Prover Round 3: Prover has committed to r_vec in the previous round. + /// Received challenge (x, y, q, lambda, delta). Already has e from round 1 + /// lambda is used for aggregation. We skip lambda in this explanation for simplicity. + /// # Witness algebraic relations: + /// + /// There are three relations of interest that we need to prove amongst the committed values. We will first + /// explain the protocol without aggregation, and then explain how to aggregate the proofs. + /// 1) v = // We refer to this as "Sum value constraint" where b is the base + /// 2) r_i = (1/ (e + d_i)) // We refer to this as "Reciprocal value constraint" + /// 3) = // We refer to this as "Range check constraint" + /// + /// 3) is the most interesting one and a core contribution of BP++ paper. This proves that + /// all the digits are in the range [0, b-1]. This can intuitively seen as follows: + /// Sum_j(m_j/e + i) = Sum_i(1/(e + d_i)) where j = 0..b-1, i = 0..num_digits + /// + /// Since e is a random challenge, the above equation is true with high probability for all X. + /// Sum_j(m_j/X + i) = Sum_i(1/(X + d_i)). Meaning, that d_i are representable using only + /// (1/X + i) poles where i = 0..b-1. Therefore, d_i must be in the range [0, b-1]. + /// + /// # Mapping to norm argument: + /// + /// To reduce this to norm argument, we construct n and l as follows: + /// n_vec = s_vec/T + delta*m_vec + d_vec*T + r_vec*T^2 + alpha_m_vec*T^3 + alpha_d_vec*T^2 + alpha_r_vec*T + /// l_vec = r_s1_vec/T + delta*r_m1_vec + r_d1_vec_vec*T + r_r1_vec*T^2 + 2*gamma*T^3 (blinding factor) + /// C = S/T + delta*M + D*T + R*T^2 + 2*V*T^3 + _P_ (P is some public value that we will compute as we proceed) + /// + /// P = 0 + /// P += + + (We will update P as we balance other constraints) + /// The values t denote concrete challenges, while T denotes the unknown challenge. Prover does not know `t` and + /// must make sure that the above equation holds for all `t`. + /// + /// There are a few important points to note here: + /// 1) All of the vectors are parameterized over unknown T. We want the norm argument to hold for all T, + /// and in the co-efficient of T^3, is where we will check all our constraints. All other co-efficients + /// of T^i will be made zero by choosing the r_s_i vector adaptively. In detail, we will choose r_s_i + /// such that C, n_vec, l_vec following the relation in norm argument. + /// C = + + (|n_vec|_q + ) G for all T. + /// Here c_vec = y*[1/y, 1/T T, T^2, T^3, T^5, T^6, 0]. Crucially, this is missing T^4 (T^3 constraint), which is where we + /// will check our constraints. + /// Because we don't know the value of T(verifier chosen challenge from next round), we must choose r_s_i + /// such that C, n_vec, l_vec following the relation in norm argument for all T. We can do this by expanding the expression + /// and solving for r_s_i. But crucially, r_s_i cannot interfere with the co-efficients of T^5. r_s_i also cannot + /// balance co-efficients above T^7. This is not an issue, because this simply translates into some constraints in + /// choosing our blinding values. Referring back to Round 1, we can now see why we needed r_d1_vec(4) = -l_m(5). Skipping + /// some calculation, if we expand n_vec here, we can see that co-eff of T^8 can only be zero is r_d1_vec(4) = -l_m(5). + /// + /// 2) We have also added public constants alpha_m_vec, alpha_d_vec, alpha_r_vec to the n_vec. These would be where + /// we would check our relations. m_vec which has a T power 1, has a corresponding alpha_m_vec with T power 4 so that + /// when multiplied, the result is in T^5 is alpha_m_vec. Similarly, d_vec has a T power 2, and alpha_d_vec has a + /// T power 3. This is because we want to check the relations in T^5. We will see how this is done in the next step. + /// + /// # Combining constraints with multiple challenges: + /// + /// This is a general principle in cryptography and snarks. We can combine multiple constraints into one by + /// using challenges. If C1 and C2 are two constraints, we can combine them into one constraint C by using + /// a challenge x. C = C1 + x*C2. This can be extended to multiple constraints. If we have C1, C2, .. Ci.. Cn, + /// we can use a single challenge q to combine all of them into one constraint C. + /// C = C1 + q*C2 + q^2*C3 + ... + q^(n-1)*Cn. In the next section, we describe which challenges separate + /// the constraints. + /// + /// # Diving into constraints: + /// + /// 1) Sum value constraint: We want to check that v = . If we choose alpha_d_vec = [b^0/q^1, b^1/q^2, b^2/q^3, ...], then + /// we can check this by checking that _q (q weighted norm) = v. This nicely cancels out the q^i + /// that would have resulted from q weighted norm and brings everything without a power of Q. challenge constraints: + /// (Q^0, X^0, Y^0) + /// + /// 2) Reciprocal constraint: We want to check that 1/(e + d_i) = r_i. We choose alpha_r1 = [e, e, e, ..e_n]. + /// When computing |n_vec|_q = |d_vec*T^2 + r_vec*T^3 + alpha_r_vec*T^2 + alpha_d_vec*T^3 + ....|_q. + /// + /// Let's consider the co-eff of q^i and x^0 = 2(d_i*r_i + e*r_i) = 2. + /// (As per definition of r_i = 1/(e + d_i) => r_i*e + r_i*d_i = 1). To check against the constant 2, Verifier adds + /// a commitment P += 2*T^5*<1_vec, q_pows_vec>G (We will keep on adding more terms to P later). + /// + /// So, challenges constraints at Q^i, X^0, Y^0 ensure all the n reciprocal constraints are satisfied. + /// + /// 3) Range check constraint: (Check each d_i in [0 b-1]) + /// + /// Using the main theorem of set membership, we want to check the following: + /// + /// Sum_j(m_j/X + i) = Sum_i(1/(X + d_i)) = Sum_i(r_i) where j = 0..b-1, i = 0..n-1. + /// To do this, we choose alpha_m_vec = [1/(e + 0), 1/(e + 1), 1/(e + 2), ... 1/(e + b-1)]. + /// and alpha_r2_vec = x*[1/q^1, 1/q^2, 1/q^3, ...]. + /// + /// So, the challenge constraints in Q^0, X^1, Y^0 ensures these constraints are satisfied. Note that the challenge + /// Y is not really used in these constraints. Y is used to separate out the terms coming in from the linear side(l_vec) + /// into the the verification equation. + /// + /// + /// # Balancing out everything else: + /// + /// We only need to deal with co-effs of T^0, T^5, and T^8 onwards. The co-effs of T^1, T^2, T^3, T^4, T^6, T^7 are + /// can easily be made zero by choosing r_s_i adaptively. We simply state the constraints here, the constraints are + /// computed by making sure (n_vec, l_vec and C, c_vec) follow the norm relation for all T's. + /// + /// T^0: Choose b_s = |s|^2_q. + /// T^8: ld_vec(4) = -lm_vec(5) + /// T^5: ld_vec(2) = -lm_vec(3) + /// + /// In our construction, all of the witness values that we want to enforce constraints are in n_vec. We have to + /// make sure none of the terms from l_vec interfere with the co-efficients of T^5. This is done by choosing + /// challenge y and making c_vec = y*[T^1, T^2, T^3, T^4, T^6, T^7]. This ensure that resultant co-effs that + /// can interfere with T^5 coming from linear side(l_vec side) are always multiplied by y. Overall, our verification + /// equation looks something like: + /// + /// T^5 = Q^0X^0Y^0(a) + Q^iX^0Y^0(b) + Q^0X^1Y^0(c) + Q^0X^0Y^1(d) + /// (a) = Sum-value constraint (1 total constraint) + /// (b) = Reciprocal constraint in Q^i (n total constraints) + /// (c) = Range check constraint in Q^0, X^1, Y^0 (1 total constraints) + /// (d) = Linear side (l_vec side) in Y^1 (1 total constraints) + /// + /// The separation of these constraints by different challenges and using the schwartz-zippel lemma, we can + /// say that all of (a), (b), (c) and (d) are satisfied with high probability. Which is some rough intuition as to why the + /// protocol is sound. Reasoning about Zk is slightly complicated and we skip that for now. + /// + /// Lastly, we also need to add cross public terms to P, which are: (Restating all terms again) + /// P = 0 + /// P += + + + // Commitments to alpha_i in G_vec + /// P += 2*T^5**G // Reciprocal constraint public term i G // Referred as v_hat1 in code + /// P += 2*T^5*x // Range check constraint public term in G // Referred as v_hat2 in code + /// P += 2*T^5*<1_vec, q_pows_vec>G // Sum value constant in G // Referred as v_hat3 in code + /// P += 2*x^2T^8*|alpha_m|_q*G // T^8 public term in G // Referred as v_hat4 in code + /// + fn round_3( + &mut self, + rng: &mut R, + x: G::ScalarField, + y: G::ScalarField, + q: G::ScalarField, + e: G::ScalarField, + lambda: G::ScalarField, + delta: G::ScalarField, + setup_params: &SetupParams, + ) { + let d = self.r1_sec.as_ref().unwrap().d_vec.clone(); + let m = scale(&self.r1_sec.as_ref().unwrap().m_vec, &delta); + let r = self.r2_sec.as_ref().unwrap().r_vec.clone(); + let r_d1_vec = self.r1_sec.as_ref().unwrap().r_d1_vec.clone(); + let l_m = self.r1_sec.as_ref().unwrap().r_m1_vec.clone(); + let l_r = self.r2_sec.as_ref().unwrap().r_r1_vec.clone(); + // q_inv_pows = (q-1, q^-2, q^-3, ..., q^{-g_vec.len()}) + let q_inv = q.inverse().unwrap(); + let q_inv_pows = + powers_starting_from(q_inv.clone(), &q_inv, setup_params.G_vec.len() as u32); + + let (alpha_r, alpha_d, alpha_m) = join!( + alpha_r_q_inv_pow(self.total_num_digits(), x, e, &q_inv_pows, delta), + alpha_d_q_inv_pow( + self.base, + self.num_digits_per_proof(), + self.num_proofs(), + &q_inv_pows, + lambda + ), + alpha_m_q_inv_pows(e, x, self.base as usize, &q_inv_pows) + ); + + // let alpha_r = alpha_r_q_inv_pow(self.total_num_digits(), x, e, &q_inv_pows, delta); + // let alpha_d = alpha_d_q_inv_pow(self.base, self.num_digits_per_proof(), self.num_proofs(), &q_inv_pows, lambda); + // let alpha_m = alpha_m_q_inv_pows(e, x, self.base as usize, &q_inv_pows); + + let t_2 = add_vecs(&d, &alpha_r); + let t_3 = add_vecs(&r, &alpha_d); + + let s = (0..setup_params.G_vec.len()) + .map(|_| G::ScalarField::rand(rng)) + .collect::>(); + + let w_vec = Poly { + coeffs: vec![s.clone(), m.clone(), t_2, t_3, alpha_m], + }; + let (r_m0, b_d, b_r) = ( + &self.r1_sec.as_ref().unwrap().r_m0, + &self.r1_sec.as_ref().unwrap().r_d0, + &self.r2_sec.as_ref().unwrap().r_r0, + ); + let w_w_q = w_vec.w_q_norm(q); + // w_w_q here starts from T^-2 and goes till T^6. + let y_inv = y.inverse().unwrap(); + let c = c_poly(y); + + // gamma_v = \sum_i(2 * lambda_powers_i * gamma_i) + // double_lambda_powers = (2, 2 * lambda, 2 * lambda^2, 2 * lambda^3, ...) + let double_lambda_powers = + powers_starting_from(G::ScalarField::from(2u64), &lambda, self.gamma.len() as u32); + let gamma_v = inner_product(&self.gamma, &double_lambda_powers); + + let (mut lm1, mut ld1, mut lr1) = + (vec![-r_m0.clone()], vec![-b_d.clone()], vec![-b_r.clone()]); + lm1.extend(l_m); + ld1.extend(r_d1_vec); + lr1.extend(l_r); + + cfg_iter_mut!(lm1).for_each(|elem| *elem = *elem * delta); + + // Question: Does the following assume that h_vec is always going to be of length 8? + let mut l_vec = Poly { + coeffs: vec![ + Vec::new(), + lm1, + ld1, + lr1, + vec![G::ScalarField::zero(), gamma_v], + Vec::new(), + Vec::new(), + Vec::new(), + ], + }; + let l_vec_w_q = l_vec.multiply_with_poly_of_constants(&c); + + // l_s = (- w_w_q_i - l_vec_w_q_i) + let mut l_s = cfg_into_iter!(0..setup_params.H_vec.len() + 1) + .zip(cfg_iter!(w_w_q).zip(cfg_iter!(l_vec_w_q))) + .map(|(_, (w_w_q_i, l_vec_w_q_i))| w_w_q_i.neg() + l_vec_w_q_i.neg()) + .collect::>(); + + // let arr = [r_m0, b_d, b_r]; + // for (i, b_i) in arr.into_iter().enumerate() { + // let r_s_i = &l_s[i + 2]; + // l_s[i + 2] = s!(r_s_i + b_i); + // } + l_s.remove(5); + let b_s = l_s.remove(1); + l_s.push(G::ScalarField::zero()); + cfg_iter_mut!(l_s).for_each(|elem| *elem = *elem * y_inv); + + l_vec.coeffs[0] = l_s.clone(); + let minus_b_s = -b_s; + // Compute S = s*G_vec + l_s*H_vec - b_s*G + let S = setup_params.compute_commitment(&minus_b_s, &l_s, &s); + + // Recompute the secret w + l_vec.coeffs[1].remove(0); + l_vec.coeffs[2].remove(0); + l_vec.coeffs[3].remove(0); + l_vec.coeffs[4].remove(0); + self.r3_sec = Some(Round3Secrets { + r_s0: minus_b_s, + w_poly: w_vec, + l_poly: l_vec, + }); + self.r3_comm = Some(Round3Commitments { S }); + } + + /// Round 4: + /// Run the norm argument on the obtained challenge t. If we have sent the correct commitments, we only + /// need to evaluate the poly w_vec at t and the poly l_vec at t. and run the norm argument on them + fn round_4( + self, + y: G::ScalarField, + t: G::ScalarField, + r: G::ScalarField, + setup_params: SetupParams, + transcript: &mut impl Transcript, + ) -> Result, BulletproofsPlusPlusError> { + let r3_sec = self.r3_sec.unwrap(); + let t_pows = TPowers::new(t, setup_params.H_vec.len() as u32); + let w_eval = r3_sec.w_poly.eval_given_t_powers(&t_pows); + let l_eval = r3_sec.l_poly.eval_given_t_powers(&t_pows); + + let c_vec = create_c_vec(y, &t_pows); + let norm_prf = WeightedNormLinearArgument::new( + l_eval.clone(), + w_eval.clone(), + c_vec, + r, + setup_params, + transcript, + )?; + Ok(Proof { + base: self.base, + r1_comm: self.r1_comm.unwrap(), + r2_comm: self.r2_comm.unwrap(), + r3_comm: self.r3_comm.unwrap(), + norm_proof: norm_prf, + }) + } + + pub fn prove( + mut self, + rng: &mut R, + setup_params: SetupParams, + transcript: &mut impl Transcript, + ) -> Result, BulletproofsPlusPlusError> { + // Round 1 + self.round_1(rng, &setup_params); + let e = + self.r1_comm + .as_ref() + .unwrap() + .challenge(self.base, self.num_bits, &self.V, transcript); + + // Round 2 + self.round_2(rng, e, &setup_params); + let (x, y, r, q, lambda, delta) = self.r2_comm.as_ref().unwrap().challenges(transcript); + + // Round 3 + self.round_3(rng, x, y, q, e, lambda, delta, &setup_params); + let t = self.r3_comm.as_ref().unwrap().challenge(transcript); + + // Round 4 + self.round_4(y, t, r, setup_params, transcript) + } +} + +impl Proof { + /// Compute the public offsets for P in along G_vec. + /// This computes + /// P = alpha_d_vec * t^3 + alpha_r1_vec * t^2 + alpha_r2_vec * t^2 + alpha_m_vec * t^4 + fn g_vec_pub_offsets( + &self, + e: G::ScalarField, + x: G::ScalarField, + alpha_r_q_inv_pows: &[G::ScalarField], + t_pows: &TPowers, + q_inv_pows: &[G::ScalarField], + alpha_d_q_inv_pows: &[G::ScalarField], + ) -> Vec { + let alpha_m = alpha_m_q_inv_pows(e, x, self.base as usize, &q_inv_pows); + + let alpha_d_t_3 = scale(&alpha_d_q_inv_pows, t_pows.nth_power(2)); + let alpha_r_t_2 = scale(alpha_r_q_inv_pows, t_pows.nth_power(1)); + let alpha_m_t_4 = scale(&alpha_m, t_pows.nth_power(3)); + + let res = add_vecs(&alpha_d_t_3, &alpha_r_t_2); + add_vecs(&res, &alpha_m_t_4) + } + + /// Compute the public offsets for P in along G + /// This computes v_hat as (explained in prover round 3) + /// P += 2*T^5**G // Reciprocal constraint public term i G // Referred as v_hat1 in code + /// P += 2*T^5*x // Range check constraint public term in G // Referred as v_hat2 in code + /// P += 2*T^5*<1_vec, q_pows_vec>G // Sum value constant in G // Referred as v_hat3 in code + /// P += 2*x^2T^8*|alpha_m|_q*G // T^8 public term in G // Referred as v_hat4 in code + fn g_offset( + &self, + alpha_r: &[G::ScalarField], + alpha_r2: &[G::ScalarField], + t3: &G::ScalarField, + q_pows: &[G::ScalarField], + alpha_d_q_inv_pows: &[G::ScalarField], + alpha_d: &[G::ScalarField], + total_num_digits: usize, + ) -> G::ScalarField { + let two_t_3 = t3.double(); + let two_t_3_v = vec![two_t_3; total_num_digits]; + + let v_hat_1 = inner_product(&two_t_3_v, q_pows); + let v_hat_2 = inner_product(&alpha_d, alpha_r2) * two_t_3; + let v_hat_3 = inner_product(&alpha_d_q_inv_pows, alpha_r) * two_t_3; + + v_hat_1 + v_hat_2 + v_hat_3 + } + + /// Compute the commitment C and run the norm arg on it + /// C = S + t*M + t^2*D + t^3*R + 2t^5*V + P + /// P = + g_offset*G + pub fn verify( + &self, + num_bits: u16, + V: &[G], + setup_params: &SetupParams, + transcript: &mut impl Transcript, + ) -> Result<(), BulletproofsPlusPlusError> { + let base_bits = util::base_bits(self.base); + if num_bits < base_bits { + return Err(BulletproofsPlusPlusError::ValueIncompatibleWithBase(format!("number of bits in value={} which should not be less than number of bits in base={}", num_bits, self.base))); + } + // number of digits for 1 proof + let num_digits_per_proof = num_bits / base_bits; + let num_proofs = V.len(); + let total_num_digits = num_digits_per_proof as usize * num_proofs; + let e = self.r1_comm.challenge(self.base, num_bits, V, transcript); + let (x, y, r, q, lambda, delta) = self.r2_comm.challenges(transcript); + let t = self.r3_comm.challenge(transcript); + let t_pows = TPowers::new(t, setup_params.H_vec.len() as u32); + + let c_vec = create_c_vec(y, &t_pows); + let (t_inv, t2, t3) = ( + t_pows.nth_power(-1), + t_pows.nth_power(2), + t_pows.nth_power(3), + ); + + // q_pows = (q, q^2, q^3, ..., q^{g_vec.len()}) + let q_pows = powers_starting_from(q.clone(), &q, setup_params.G_vec.len() as u32); + + // q_inv_pows = (q^-1, q^-2, q^-3, ..., q^{-g_vec.len()}) + let mut q_inv_pows = q_pows.clone(); + batch_inversion(&mut q_inv_pows); + + let lambda_powers = powers(&lambda, num_proofs as u32); + let alpha_d = alpha_d_given_lambda_powers(self.base, num_digits_per_proof, &lambda_powers); + let alpha_d_q_inv_pow = hadamard_product(&alpha_d, &q_inv_pows); + + let alpha_r2 = alpha_r2(total_num_digits, e); + let alpha_r = alpha_r(total_num_digits, x, delta); + let alpha_r_q_inv_pows = alpha_r_q_inv_pow_given_alpha_r(&alpha_r, &alpha_r2, &q_inv_pows); + + // Compute the commitment to the public values + let g_offset = self.g_offset( + &alpha_r, + &alpha_r2, + t3, + &q_pows, + &alpha_d_q_inv_pow, + &alpha_d, + total_num_digits, + ); + let g_vec_pub_offsets = self.g_vec_pub_offsets( + e, + x, + &alpha_r_q_inv_pows, + &t_pows, + &q_inv_pows, + &alpha_d_q_inv_pow, + ); + + // let (r1_comm, r2_comm, r3_comm, norm_proof) = + // (self.r1_comm, self.r2_comm, self.r3_comm, self.norm_proof); + let (S, M, D, R) = ( + self.r3_comm.S, + self.r1_comm.M, + self.r1_comm.D, + self.r2_comm.R, + ); + + let two_t3 = t3.double(); + + // \sum_i(V_i * lambda_powers_i * t3 * 2) + let V = G::Group::msm_unchecked(V, &scale(&lambda_powers, &two_t3)); + // TODO: C can be created using an MSM + let C = S * t_inv + M * delta + D * t + R * t2 + V; + let P = G::Group::msm_unchecked(&setup_params.G_vec, &g_vec_pub_offsets); + let C = C + P + (setup_params.G * g_offset); + + self.norm_proof + .verify(c_vec, r, &C.into_affine(), setup_params, transcript) + } +} + +/// Powers of a scalar `t` as `(t^-1, 1, t, t^2, t^3, ...)` +struct TPowers(pub Vec); + +impl TPowers { + fn new(t: F, n: u32) -> Self { + let t_inv = t.inverse().unwrap(); + Self(powers_starting_from(t_inv, &t, n + 1)) + } + + fn nth_power(&self, i: i32) -> &F { + assert!(i < (self.0.len() - 1) as i32); + &self.0[(i + 1) as usize] + } +} + +/// Compute a vector as result of alpha_d X q_inv_pows +/// Size must be number of digits in all proofs combined +fn alpha_d_q_inv_pow( + base: u16, + num_digits_per_proof: u16, + num_proofs: usize, + q_inv_pows: &[F], + lambda: F, +) -> Vec { + let res = alpha_d(base, num_digits_per_proof, num_proofs, lambda); + hadamard_product(&res, &q_inv_pows) +} + +/// Compute a vector of powers of `b` multiplied by powers of `lambda` like this: `(1, b, b^2, b^3, ..., b^{num_digits-1}, lambda, lambda*b, lambda*b^2, lambda*b^3, ..., lambda*b^{num_digits-1}, ..., lambda^{num_proofs-1}, {lambda^{num_proofs-1}}*b, {lambda^{num_proofs-1}}*b^2, {lambda^{num_proofs-1}}*b^3, ..., {lambda^{num_proofs-1}}*b^{num_digits-1})` +/// Size must be number of digits in all proofs combined. +fn alpha_d( + base: u16, + num_digits_per_proof: u16, + num_proofs: usize, + lambda: F, +) -> Vec { + let base = F::from(base as u64); + let lambda_powers = powers(&lambda, num_proofs as u32); + let base_powers = powers(&base, num_digits_per_proof as u32); + cfg_into_iter!(lambda_powers) + .flat_map(|lambda_pow_i| scale(&base_powers, &lambda_pow_i)) + .collect() +} + +/// Same as `alpha_d` except that it accepts lambda powers +fn alpha_d_given_lambda_powers( + base: u16, + num_digits_per_proof: u16, + lambda_powers: &[F], +) -> Vec { + let base = F::from(base as u64); + let base_powers = powers(&base, num_digits_per_proof as u32); + cfg_into_iter!(lambda_powers) + .flat_map(|lambda_pow_i| scale(&base_powers, lambda_pow_i)) + .collect() +} + +/// Compute alpha_m = vec![x/e, x/(e + 1), x/(e + 2), ...] X q_inv_pows +fn alpha_m_q_inv_pows(e: F, x: F, n: usize, q_inv_pows: &[F]) -> Vec { + let res = alpha_m(e, x, n); + hadamard_product(&res, q_inv_pows) +} + +/// Compute alpha_m = vec![x/e, x/(e + 1), x/(e + 2), ...] +fn alpha_m(e: F, x: F, n: usize) -> Vec { + cfg_into_iter!(0..n) + .map(|i| x * (e + F::from(i as u64)).inverse().unwrap()) + .collect() +} + +/// Compute a vector of scalar ((-x * delta)/((q_inv_pows)_i) + e) +fn alpha_r_q_inv_pow(n: usize, x: F, e: F, q_inv_pows: &[F], delta: F) -> Vec { + let res = alpha_r(n, x, delta); + let alpha_r = hadamard_product(&res, q_inv_pows); + add_vecs(&alpha_r, &alpha_r2(n, e)) +} + +fn alpha_r_q_inv_pow_given_alpha_r( + alpha_r: &[F], + alpha_r2: &[F], + q_inv_pows: &[F], +) -> Vec { + add_vecs(&hadamard_product(&alpha_r, q_inv_pows), alpha_r2) +} + +/// Compute a vector of scalar -x * delta +fn alpha_r(n: usize, x: F, delta: F) -> Vec { + cfg_into_iter!(0..n).map(|_| (x * delta).neg()).collect() +} + +/// Compute a vector of [e, e, e, e] +fn alpha_r2(n: usize, e: F) -> Vec { + ark_std::iter::repeat(e).map(|e| e).take(n).collect() +} + +/// obtain the c poly +fn c_poly(y: F) -> Poly { + let zero = F::zero(); + let one = F::one(); + Poly { + coeffs: vec![ + vec![one], + vec![y], + vec![y], + vec![y], + vec![y], + vec![y], + vec![y], + vec![y], + vec![zero], + ], + } +} + +/// Obtain the non-zero at i position +fn t_pow_in_c(i: usize) -> usize { + match i { + 0 => 1, + 1 => 0, + 2 => 2, + 3 => 3, + 4 => 4, + 5 => 6, + 6 => 7, + 7 => 8, + 8 => 9, + _ => unreachable!("i must be in [0, 7]"), + } +} + +fn create_c_vec(y: F, t_pows: &TPowers) -> Vec { + let (t_inv, t, t2, t3, t5, t6, t7) = ( + t_pows.nth_power(-1), + t_pows.nth_power(1), + t_pows.nth_power(2), + t_pows.nth_power(3), + t_pows.nth_power(5), + t_pows.nth_power(6), + t_pows.nth_power(7), + ); + vec![ + y * t_inv, + y * t, + y * t2, + y * t3, + y * t5, + y * t6, + y * t7, + F::zero(), + ] +} + +/// Vector valued polynomial +#[derive(Debug, Clone)] +struct Poly { + coeffs: Vec>, +} + +impl Poly { + // evaluate the poly at t + #[cfg(test)] + fn eval(&self, t: F) -> Vec { + let mut res = vec![F::zero(); self.coeffs[0].len()]; + let mut t_pow = t.inverse().unwrap(); + for coeffs in self.coeffs.iter() { + for (i, coeff) in coeffs.iter().enumerate() { + res[i] += t_pow * coeff; + } + t_pow = t_pow * t; + } + res + } + + // evaluate the poly at t + fn eval_given_t_powers(&self, t_pows: &TPowers) -> Vec { + let mut res = vec![F::zero(); self.coeffs[0].len()]; + for (j, coeffs) in self.coeffs.iter().enumerate() { + for (i, coeff) in coeffs.iter().enumerate() { + res[i] += *t_pows.nth_power(j as i32 - 1) * coeff; + } + } + res + } + + // Compute the inner product of two polynomials + fn w_q_norm(&self, q: F) -> Vec { + let mut res = vec![F::zero(); 2 * self.coeffs.len() - 1]; + // q_powers = (q, q^2, q^3, q^4, ...) + let mut q_powers = vec![q]; + for i in 0..self.coeffs.len() { + for j in 0..self.coeffs.len() { + let a = &self.coeffs[i]; + let b = &self.coeffs[j]; + let min_len = a.len().min(b.len()); + while q_powers.len() < min_len { + q_powers.push(q * q_powers.last().unwrap()) + } + // inner_prod = \sum_k{a_k * b_k * q_powers_k} + let mut inner_prod = F::zero(); + for k in 0..min_len { + let (a_k, b_k) = (&a[k], &b[k]); + inner_prod += *a_k * b_k * q_powers[k]; + } + res[i + j] += inner_prod; + } + } + res + } + + // multiply a vector polynomial `c` whose coefficients are constants, i.e. coefficient vectors have only 1 coefficient + fn multiply_with_poly_of_constants(&self, c: &Poly) -> Vec { + let mut res = vec![F::zero(); self.coeffs.len() + c.coeffs.len() - 1]; + for l in 0..self.coeffs.len() { + let l_vec = &self.coeffs[l]; + for i in 0..l_vec.len() { + let t_pow_in_c = t_pow_in_c(i); + if t_pow_in_c >= c.coeffs.len() { + continue; + } + let inner_prod = l_vec[i] * c.coeffs[i][0]; // c_vec has exactly one element + res[l + t_pow_in_c] += inner_prod; + } + } + res + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::setup::SetupParams; + use ark_bls12_381::Fr; + use ark_std::{ + rand::{prelude::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use dock_crypto_utils::transcript::new_merlin_transcript; + + // Test prove and verify + fn test_rangeproof_for_perfect_range(base: u16, num_bits: u16, v: Vec) { + let mut rng = StdRng::seed_from_u64(0u64); + + let mut gamma = vec![]; + for _ in 0..v.len() { + gamma.push(G::ScalarField::rand(&mut rng)); + } + + let setup_params = SetupParams::::new_for_perfect_range_proof::( + b"test", + base, + num_bits, + v.len() as u32, + ); + + let mut V = vec![]; + for (v_i, gamma_i) in v.iter().zip(gamma.iter()) { + V.push(setup_params.compute_pedersen_commitment(*v_i, gamma_i)); + } + let prover = Prover::new_with_given_base(base, num_bits, V.clone(), v, gamma).unwrap(); + let mut transcript = new_merlin_transcript(b"BPP/tests"); + let prf = prover + .prove(&mut rng, setup_params.clone(), &mut transcript) + .unwrap(); + + let mut transcript = new_merlin_transcript(b"BPP/tests"); + prf.verify(num_bits, &V, &setup_params, &mut transcript) + .unwrap(); + } + + fn check_for_perfect_range() { + test_rangeproof_for_perfect_range::(2, 2, vec![0]); + for i in 0..16 { + test_rangeproof_for_perfect_range::(2, 4, vec![i]); + test_rangeproof_for_perfect_range::(2, 4, vec![i, 15 - i]); + } + test_rangeproof_for_perfect_range::(16, 4, vec![7]); + test_rangeproof_for_perfect_range::(16, 8, vec![243]); + test_rangeproof_for_perfect_range::(16, 16, vec![12431]); + test_rangeproof_for_perfect_range::(2, 16, vec![12431]); + test_rangeproof_for_perfect_range::(4, 16, vec![12431]); + test_rangeproof_for_perfect_range::(8, 16, vec![12431]); + test_rangeproof_for_perfect_range::(16, 32, vec![134132, 14354, 981643, 875431]); + let mut rng = StdRng::seed_from_u64(1u64); + for _ in 0..10 { + let mut v = vec![]; + for _ in 0..8 { + v.push(u64::rand(&mut rng)); + } + test_rangeproof_for_perfect_range::(16, 64, v); + } + for _ in 0..10 { + let v = u64::rand(&mut rng); + test_rangeproof_for_perfect_range::(16, 64, vec![v]); + } + } + + #[test] + fn rangeproof_bls12381() { + check_for_perfect_range::() + } + + #[test] + fn rangeproof_curve25519() { + check_for_perfect_range::() + } + + #[test] + fn rangeproof_ed25519() { + check_for_perfect_range::() + } + + #[test] + fn rangeproof_secp256k1() { + check_for_perfect_range::() + } + + #[test] + fn poly() { + let q = Fr::from(2); + let a = Poly { + coeffs: vec![ + vec![Fr::from(1), Fr::from(2)], + vec![Fr::from(1), Fr::from(2)], + ], + }; + let _b = Poly { + coeffs: vec![ + vec![Fr::from(3), Fr::from(4)], + vec![Fr::from(3), Fr::from(4)], + ], + }; + let res = a.w_q_norm(q); + assert_eq!(res, vec![Fr::from(18), Fr::from(36), Fr::from(18)]); + + let t = Fr::from(101); + let t_pows = TPowers::new(t, 2); + assert_eq!(a.eval(t), a.eval_given_t_powers(&t_pows)); + } +} diff --git a/bulletproofs_plus_plus/src/setup.rs b/bulletproofs_plus_plus/src/setup.rs new file mode 100644 index 00000000..ca926c1f --- /dev/null +++ b/bulletproofs_plus_plus/src/setup.rs @@ -0,0 +1,117 @@ +use ark_ec::{AffineRepr, CurveGroup, VariableBaseMSM}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, rand::RngCore, vec::Vec, UniformRand}; +use digest::Digest; +use dock_crypto_utils::{concat_slices, hashing_utils::affine_group_elem_from_try_and_incr}; + +use crate::util::base_bits; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetupParams { + pub G: Gr, + pub G_vec: Vec, + pub H_vec: Vec, +} + +impl SetupParams { + pub fn new(label: &[u8], g_count: u32, h_count: u32) -> Self { + let g = affine_group_elem_from_try_and_incr::(&concat_slices![label, b" : G"]); + let g_vec = cfg_into_iter!((0..g_count)) + .map(|i| { + affine_group_elem_from_try_and_incr::(&concat_slices![ + label, + b" : g_", + i.to_le_bytes() + ]) + }) + .collect::>(); + let h_vec = cfg_into_iter!((0..h_count)) + .map(|i| { + affine_group_elem_from_try_and_incr::(&concat_slices![ + label, + b" : h_", + i.to_le_bytes() + ]) + }) + .collect::>(); + Self { + G: g, + G_vec: g_vec, + H_vec: h_vec, + } + } + + /// Create setup params for perfect range, i.e a range of form `[0, base^l)` + pub fn new_for_perfect_range_proof( + label: &[u8], + base: u16, + num_value_bits: u16, + num_proofs: u32, + ) -> Self { + Self::new::( + label, + Self::get_no_of_G(base, num_value_bits, num_proofs), + 8, + ) + } + + /// Create setup params for an arbitrary range, i.e a range of form `[a, b)` + pub fn new_for_arbitrary_range_proof( + label: &[u8], + base: u16, + num_value_bits: u16, + num_proofs: u32, + ) -> Self { + Self::new_for_perfect_range_proof::(label, base, num_value_bits, num_proofs * 2) + } + + /// Create Pedersen commitment as `C = v*G + gamma*H_vec[0]` + pub fn compute_pedersen_commitment(&self, v: u64, gamma: &Gr::ScalarField) -> Gr { + ((self.G * Gr::ScalarField::from(v)) + self.H_vec[0] * gamma).into_affine() + } + + /// Returns `v*g + + ` + pub fn compute_commitment( + &self, + v: &Gr::ScalarField, + l: &[Gr::ScalarField], + n: &[Gr::ScalarField], + ) -> Gr { + Self::compute_commitment_given_bases(v, l, n, &self.G, &self.G_vec, &self.H_vec) + } + + /// Returns `v*g + + ` + pub fn compute_commitment_given_bases( + v: &Gr::ScalarField, + l: &[Gr::ScalarField], + n: &[Gr::ScalarField], + g: &Gr, + g_vec: &[Gr], + h_vec: &[Gr], + ) -> Gr { + (g.mul(v) + Gr::Group::msm_unchecked(g_vec, n) + Gr::Group::msm_unchecked(h_vec, l)) + .into_affine() + } + + /// Generates random `v` and returns pair `(v, v*g + + )` + pub fn gen_randomness_and_compute_commitment( + &self, + rng: &mut R, + l: &[Gr::ScalarField], + n: &[Gr::ScalarField], + ) -> (Gr::ScalarField, Gr) { + let v = Gr::ScalarField::rand(rng); + (v, self.compute_commitment(&v, l, n)) + } + + pub fn get_pedersen_commitment_key(&self) -> (Gr, Gr) { + (self.G, self.H_vec[0]) + } + + /// Get number of generators `G_i` required for creating proofs + pub fn get_no_of_G(base: u16, num_value_bits: u16, num_proofs: u32) -> u32 { + ark_std::cmp::max(num_value_bits as u32 / base_bits(base) as u32, base as u32) * num_proofs + } +} diff --git a/bulletproofs_plus_plus/src/util.rs b/bulletproofs_plus_plus/src/util.rs new file mode 100644 index 00000000..f713ad17 --- /dev/null +++ b/bulletproofs_plus_plus/src/util.rs @@ -0,0 +1,4 @@ +/// Number of bits in `base` +pub fn base_bits(base: u16) -> u16 { + base.ilog2() as u16 +} diff --git a/bulletproofs_plus_plus/src/weighted_norm_linear_argument.rs b/bulletproofs_plus_plus/src/weighted_norm_linear_argument.rs new file mode 100644 index 00000000..f2943816 --- /dev/null +++ b/bulletproofs_plus_plus/src/weighted_norm_linear_argument.rs @@ -0,0 +1,421 @@ +//! Weighted Norm Linear argument for + +use crate::error::BulletproofsPlusPlusError; +use ark_ec::{AffineRepr, CurveGroup, VariableBaseMSM}; +use ark_ff::{Field, One}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, cfg_iter, format, vec, vec::Vec}; +use dock_crypto_utils::{ + ff::{inner_product, weighted_inner_product, weighted_norm}, + transcript::Transcript, +}; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::setup::SetupParams; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct WeightedNormLinearArgument { + pub X: Vec, + pub R: Vec, + pub l: Vec, + pub n: Vec, +} + +impl WeightedNormLinearArgument { + pub fn new( + mut l: Vec, + mut n: Vec, + mut c: Vec, + mut rho: G::ScalarField, + setup_params: SetupParams, + transcript: &mut impl Transcript, + ) -> Result { + let SetupParams { + G: g, + G_vec: mut g_vec, + H_vec: mut h_vec, + } = setup_params; + if l.len() != c.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of l={} not equal to length of c={}", + l.len(), + c.len() + ), + )); + } + if c.len() != h_vec.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of c={} not equal to length of H_vec={}", + c.len(), + h_vec.len() + ), + )); + } + if n.len() != g_vec.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of n={} not equal to length of G_vec={}", + n.len(), + g_vec.len() + ), + )); + } + if !l.len().is_power_of_two() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!("length of l={} must be power of 2", l.len()), + )); + } + if !n.len().is_power_of_two() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!("length of n={} must be power of 2", n.len()), + )); + } + + let mut mu = rho.square(); + let mut X = Vec::new(); + let mut R = Vec::new(); + + while l.len() > 1 || n.len() > 1 { + let (l_0, l_1) = Self::split_vec(&l); + let (n_0, n_1) = Self::split_vec(&n); + let (c_0, c_1) = Self::split_vec(&c); + let (g_0, g_1) = Self::split_vec(&g_vec); + let (h_0, h_1) = Self::split_vec(&h_vec); + let rho_inv = rho.inverse().unwrap(); + let mu_sqr = mu.square(); + + let v_x = rho_inv.double() * weighted_inner_product(&n_0, &n_1, &mu_sqr) + + inner_product(&c_0, &l_1) + + inner_product(&c_1, &l_0); + let v_r = weighted_norm(&n_1, &mu_sqr) + inner_product(&c_1, &l_1); + + let scaled_n_0 = cfg_iter!(n_0).map(|n| *n * rho_inv).collect::>(); + let scaled_n_1 = cfg_iter!(n_1).map(|n| *n * rho).collect::>(); + // TODO: Create one big MSM + let X_i = g.mul(v_x) + + G::Group::msm_unchecked(&h_0, &l_1) + + G::Group::msm_unchecked(&h_1, &l_0) + + G::Group::msm_unchecked(&g_0, &scaled_n_1) + + G::Group::msm_unchecked(&g_1, &scaled_n_0); + + // TODO: Create one big MSM + let R_i = g.mul(v_r) + + G::Group::msm_unchecked(&h_1, &l_1) + + G::Group::msm_unchecked(&g_1, &n_1); + + transcript.append(b"X", &X_i); + transcript.append(b"R", &R_i); + let gamma = transcript.challenge_scalar::(b"gamma"); + + if l.len() > 1 { + l = cfg_into_iter!(l_0) + .zip(l_1) + .map(|(_0, _1)| _0 + gamma * _1) + .collect(); + c = cfg_into_iter!(c_0) + .zip(c_1) + .map(|(_0, _1)| _0 + gamma * _1) + .collect(); + h_vec = G::Group::normalize_batch( + &cfg_iter!(h_0) + .zip(h_1) + .map(|(_0, _1)| *_0 + _1 * gamma) + .collect::>(), + ); + } + if n.len() > 1 { + n = cfg_iter!(n_0) + .zip(&n_1) + .map(|(_0, _1)| *_0 * rho_inv + gamma * _1) + .collect(); + g_vec = G::Group::normalize_batch( + &cfg_iter!(g_0) + .zip(g_1) + .map(|(_0, _1)| *_0 * rho + _1 * gamma) + .collect::>(), + ); + } + + rho = mu; + mu = mu_sqr; + X.push(X_i); + R.push(R_i); + } + Ok(Self { + X: G::Group::normalize_batch(&X), + R: G::Group::normalize_batch(&R), + l, + n, + }) + } + + pub fn verify( + &self, + c: Vec, + rho: G::ScalarField, + commitment: &G, + setup_params: &SetupParams, + transcript: &mut impl Transcript, + ) -> Result<(), BulletproofsPlusPlusError> { + let SetupParams { + G: g, + G_vec: g_vec, + H_vec: h_vec, + } = setup_params; + if c.len() != h_vec.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of c={} not equal to length of H_vec={}", + c.len(), + h_vec.len() + ), + )); + } + if self.X.len() != self.R.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of X={} not equal to length of R={}", + self.X.len(), + self.X.len() + ), + )); + } + let mut mu = rho.square(); + let mut gamma = Vec::with_capacity(self.X.len()); + let mut gamma_sq_minus_1 = Vec::with_capacity(self.X.len()); + for i in 0..self.X.len() { + transcript.append(b"X", &self.X[i]); + transcript.append(b"R", &self.R[i]); + gamma.push(transcript.challenge_scalar::(b"gamma")); + gamma_sq_minus_1.push(gamma[i].square() - G::ScalarField::one()); + } + for _ in 0..g_vec.len().ilog2() { + mu.square_in_place(); + } + let g_multiples = Self::get_g_multiples(g_vec.len(), &rho, &gamma); + let h_multiples = Self::get_h_multiples(h_vec.len(), &gamma); + + let v = self.l[0] * inner_product(&c, &h_multiples) + weighted_norm(&self.n, &mu); + // TODO: Replace following 2 lines with a single MSM + let C_prime = *g * v + + (G::Group::msm_unchecked(h_vec, &h_multiples) * self.l[0]) + + (G::Group::msm_unchecked(g_vec, &g_multiples) * self.n[0]); + if (commitment.into_group() + + G::Group::msm_unchecked(&self.X, &gamma) + + G::Group::msm_unchecked(&self.R, &gamma_sq_minus_1)) + != C_prime + { + return Err(BulletproofsPlusPlusError::WeightedNormLinearArgumentVerificationFailed); + } + Ok(()) + } + + pub fn verify_recursively( + &self, + mut c: Vec, + mut rho: G::ScalarField, + commitment: &G, + setup_params: SetupParams, + transcript: &mut impl Transcript, + ) -> Result<(), BulletproofsPlusPlusError> { + let SetupParams { + G: g, + G_vec: mut g_vec, + H_vec: mut h_vec, + } = setup_params; + if c.len() != h_vec.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of c={} not equal to length of H_vec={}", + c.len(), + h_vec.len() + ), + )); + } + if self.X.len() != self.R.len() { + return Err(BulletproofsPlusPlusError::UnexpectedLengthOfVectors( + format!( + "length of X={} not equal to length of R={}", + self.X.len(), + self.X.len() + ), + )); + } + let mut mu = rho.square(); + let mut commitment = commitment.into_group(); + for i in 0..self.X.len() { + transcript.append(b"X", &self.X[i]); + transcript.append(b"R", &self.R[i]); + let gamma = transcript.challenge_scalar::(b"gamma"); + let (c_0, c_1) = Self::split_vec(&c); + let (g_0, g_1) = Self::split_vec(&g_vec); + let (h_0, h_1) = Self::split_vec(&h_vec); + c = cfg_into_iter!(c_0) + .zip(c_1) + .map(|(_0, _1)| _0 + gamma * _1) + .collect(); + h_vec = G::Group::normalize_batch( + &cfg_iter!(h_0) + .zip(h_1) + .map(|(_0, _1)| *_0 + _1 * gamma) + .collect::>(), + ); + g_vec = G::Group::normalize_batch( + &cfg_iter!(g_0) + .zip(g_1) + .map(|(_0, _1)| *_0 * rho + _1 * gamma) + .collect::>(), + ); + commitment += self.X[i] * gamma + self.R[i] * (gamma.square() - G::ScalarField::one()); + rho = mu; + mu.square_in_place(); + } + let v = Self::compute_v(&self.l, &self.n, &c, &mu); + if commitment.into_affine() + != SetupParams::compute_commitment_given_bases(&v, &self.l, &self.n, &g, &g_vec, &h_vec) + { + return Err(BulletproofsPlusPlusError::WeightedNormLinearArgumentVerificationFailed); + } + Ok(()) + } + + /// Returns + {|n|^2}_mu + fn compute_v( + l: &[G::ScalarField], + n: &[G::ScalarField], + c: &[G::ScalarField], + mu: &G::ScalarField, + ) -> G::ScalarField { + inner_product(c, l) + weighted_norm(n, mu) + } + + /// Split a vector into 2 vectors of even and odd indices elements respectively. + fn split_vec(original: &[T]) -> (Vec, Vec) { + let mut odd = Vec::with_capacity(original.len() / 2); + let mut even = Vec::with_capacity(original.len() / 2); + for (i, v) in original.iter().enumerate() { + if i % 2 == 0 { + even.push(v.clone()); + } else { + odd.push(v.clone()); + } + } + (even, odd) + } + + fn get_h_multiples(n: usize, gamma: &[G::ScalarField]) -> Vec { + let mut multiples = vec![G::ScalarField::one(); n]; + let len = (n as u32).ilog2() as usize; + for i in 0..len { + let partition_size = 1 << i; + let partitions = n / partition_size; + for j in 0..partitions { + if j % 2 == 1 { + for l in 0..partition_size { + multiples[j * partition_size + l] *= gamma[i]; + } + } + } + } + multiples + } + + fn get_g_multiples( + n: usize, + rho: &G::ScalarField, + gamma: &[G::ScalarField], + ) -> Vec { + let mut multiples = vec![G::ScalarField::one(); n]; + let len = (n as u32).ilog2() as usize; + let mut rho_i = *rho; + for i in 0..len { + let partition_size = 1 << i; + let partitions = n / partition_size; + for j in 0..partitions { + if j % 2 == 0 { + for l in 0..partition_size { + multiples[j * partition_size + l] *= rho_i; + } + } else { + for l in 0..partition_size { + multiples[j * partition_size + l] *= gamma[i]; + } + } + } + rho_i.square_in_place(); + } + multiples + } +} + +#[cfg(test)] +mod test { + use super::*; + use ark_bls12_381::{Fr, G1Affine}; + + use ark_std::{ + rand::{prelude::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use dock_crypto_utils::{misc::n_rand, transcript::new_merlin_transcript}; + use std::time::Instant; + + #[test] + fn check_when_powers_of_2() { + let mut rng = StdRng::seed_from_u64(0u64); + + for count in [1, 2, 4, 8, 16, 32, 64] { + let setup_params = SetupParams::new::(b"test", count, count); + let c = n_rand(&mut rng, count).collect::>(); + + let l = n_rand(&mut rng, count).collect::>(); + let n = n_rand(&mut rng, count).collect::>(); + + let rho = Fr::rand(&mut rng); + + let v = WeightedNormLinearArgument::::compute_v(&l, &n, &c, &rho.square()); + let commitment = setup_params.compute_commitment(&v, &l, &n); + + let start = Instant::now(); + let mut prover_transcript = new_merlin_transcript(b"test"); + let arg = WeightedNormLinearArgument::::new( + l, + n, + c.clone(), + rho.clone(), + setup_params.clone(), + &mut prover_transcript, + ) + .unwrap(); + let prover_time = start.elapsed(); + + let start = Instant::now(); + let mut verifier_transcript = new_merlin_transcript(b"test"); + arg.verify( + c.clone(), + rho.clone(), + &commitment, + &setup_params, + &mut verifier_transcript, + ) + .unwrap(); + let verifying_non_recursively_time = start.elapsed(); + + let start = Instant::now(); + let mut verifier_transcript = new_merlin_transcript(b"test"); + arg.verify_recursively(c, rho, &commitment, setup_params, &mut verifier_transcript) + .unwrap(); + let verifying_time = start.elapsed(); + + println!( + "For {} elements, proving time is {:?}, recursively verifying time is {:?} and non-recursively verifying time is {:?}", + count, prover_time, verifying_time, verifying_non_recursively_time + ); + } + } +} diff --git a/coconut/Cargo.toml b/coconut/Cargo.toml index 84ee710f..aa4864c3 100644 --- a/coconut/Cargo.toml +++ b/coconut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coconut-crypto" -version = "0.5.0" +version = "0.6.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -22,9 +22,9 @@ itertools.workspace = true zeroize.workspace = true serde_with.workspace = true rayon = { workspace = true, optional = true } -utils = { package = "dock_crypto_utils", version = "0.15.0", default-features = false, path = "../utils" } -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } -secret_sharing_and_dkg = { version = "0.7.0", default-features = false, path = "../secret_sharing_and_dkg" } +utils = { package = "dock_crypto_utils", version = "0.16.0", default-features = false, path = "../utils" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } +secret_sharing_and_dkg = { version = "0.8.0", default-features = false, path = "../secret_sharing_and_dkg" } [dev-dependencies] blake2.workspace = true diff --git a/compressed_sigma/Cargo.toml b/compressed_sigma/Cargo.toml index 4d50c49f..b943a622 100644 --- a/compressed_sigma/Cargo.toml +++ b/compressed_sigma/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "compressed_sigma" -version = "0.0.7" +version = "0.0.8" edition.workspace = true authors.workspace = true license.workspace = true @@ -15,7 +15,7 @@ ark-std.workspace = true ark-poly.workspace = true rayon = {workspace = true, optional = true} digest.workspace = true -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } [dev-dependencies] blake2.workspace = true diff --git a/compressed_sigma/src/utils.rs b/compressed_sigma/src/utils.rs index eee8d658..51a54324 100644 --- a/compressed_sigma/src/utils.rs +++ b/compressed_sigma/src/utils.rs @@ -63,7 +63,7 @@ pub fn get_n_powers(elem: F, n: usize) -> Vec { powers } -/// Returns vector [g * i * coeff, g * i^2 * coeff, g * i^3 * coeff, ..., g * i^n * coeff] +/// Returns vector `[g * i * coeff, g * i^2 * coeff, g * i^3 * coeff, ..., g * i^n * coeff]` pub fn multiples_with_n_powers_of_i( g: &G, i: G::ScalarField, @@ -80,9 +80,9 @@ pub fn multiples_with_n_powers_of_i( )) } -/// In each round i, current g is split in 2 halves, g_l and g_r and new g is created as c_i*g_l + g_r -/// where g_l and g_r are left and right halves respectively and c_i is the challenge for that round. -/// This is done until g is of size 2. This means that all elements of the original g that are +/// In each round `i`, current `g` is split in 2 halves, `g_l` and `g_r` and new `g` is created as `c_i*g_l + g_r` +/// where `g_l` and `g_r` are left and right halves respectively and `c_i` is the challenge for that round. +/// This is done until `g` is of size 2. This means that all elements of the original `g` that are /// on the left side in that round would be multiplied by the challenge of that round pub fn get_g_multiples_for_verifying_compression( g_len: usize, @@ -95,9 +95,10 @@ pub fn get_g_multiples_for_verifying_compression( // For each round, divide g into an even number of equal sized partitions and each even // numbered (left) partition's elements are multiplied by challenge of that round for i in 0..challenges.len() { - let partition = 1 << (i + 1); - let partition_size = g_len / partition; - for j in (0..partition).step_by(2) { + let partitions = 1 << (i + 1); + let partition_size = g_len / partitions; + // Only multiply the even-indexed partition elements by the challenge + for j in (0..partitions).step_by(2) { for l in 0..partition_size { g_multiples[j * partition_size + l] *= challenges[i]; } @@ -116,7 +117,7 @@ pub fn get_g_multiples_for_verifying_compression( g_multiples } -/// Convert field element vector from [c_1, c_2, c_3, ..., c_n] to [c_1*c_2*...*c_n, c_2*c_3*...*c_n, c_3*...*c_n, ..., c_{n-1}*c_n, c_n, 1] +/// Convert field element vector from `[c_1, c_2, c_3, ..., c_n]` to `[c_1*c_2*...*c_n, c_2*c_3*...*c_n, c_3*...*c_n, ..., c_{n-1}*c_n, c_n, 1]` pub fn elements_to_element_products(mut elements: Vec) -> Vec { for i in (1..elements.len()).rev() { let c = elements[i - 1] * elements[i]; diff --git a/delegatable_credentials/Cargo.toml b/delegatable_credentials/Cargo.toml index f0439a81..524135f6 100644 --- a/delegatable_credentials/Cargo.toml +++ b/delegatable_credentials/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "delegatable_credentials" -version = "0.5.0" +version = "0.6.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -17,10 +17,10 @@ digest.workspace = true rayon = {workspace = true, optional = true} serde.workspace = true serde_with.workspace = true -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } zeroize.workspace = true num-bigint = { version = "0.4.0", default-features = false } -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } [dependencies.num-integer] version = "0.1.42" features = ["i128"] diff --git a/kvac/Cargo.toml b/kvac/Cargo.toml index fcc27cc6..089a1899 100644 --- a/kvac/Cargo.toml +++ b/kvac/Cargo.toml @@ -14,8 +14,8 @@ ark-std.workspace = true ark-serialize.workspace = true digest.workspace = true zeroize.workspace = true -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } rayon = {workspace = true, optional = true} [dev-dependencies] diff --git a/legogroth16/Cargo.toml b/legogroth16/Cargo.toml index 54729972..55f53ba8 100644 --- a/legogroth16/Cargo.toml +++ b/legogroth16/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "legogroth16" -version = "0.10.0" +version = "0.11.0" authors = [ "arkworks contributors", "Dock.io" ] description = "An implementation of the LegoGroth16, the Legosnark variant of Groth16 zkSNARK proof system" repository.workspace = true @@ -29,7 +29,7 @@ num-bigint = { version = "0.4", default-features = false, optional = true } log = "0.4" ark-groth16 = { workspace = true, optional = true } ark-snark = { version = "^0.4.0", default-features = false, optional = true } -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } [dev-dependencies] csv = { version = "1" } diff --git a/legogroth16/src/circom/circuit.rs b/legogroth16/src/circom/circuit.rs index 00634b1d..c5236f53 100644 --- a/legogroth16/src/circom/circuit.rs +++ b/legogroth16/src/circom/circuit.rs @@ -42,7 +42,7 @@ impl CircomCircuit { } /// Does the zk-SNARK setup and returns the proving key. `commit_witness_count` is the number of private inputs - /// wires that should be committed in the Pedersen commitment included in the proof. + /// wires that should be committed in the Pedersen commitment included in the proof. pub fn generate_proving_key( self, commit_witness_count: u32, diff --git a/oblivious_transfer/Cargo.toml b/oblivious_transfer/Cargo.toml index f3a0d878..5fbbeff9 100644 --- a/oblivious_transfer/Cargo.toml +++ b/oblivious_transfer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oblivious_transfer_protocols" -version = "0.3.0" +version = "0.4.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -16,8 +16,8 @@ digest.workspace = true serde.workspace = true serde_with.workspace = true zeroize.workspace = true -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } cipher = { version = "0.4.4", default-features = false, features = ["alloc"] } rayon = {workspace = true, optional = true} sha3 = { version = "0.10.6", default-features = false } diff --git a/proof_system/Cargo.toml b/proof_system/Cargo.toml index b4fa5a2f..469c48a2 100644 --- a/proof_system/Cargo.toml +++ b/proof_system/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proof_system" -version = "0.22.0" +version = "0.23.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -18,20 +18,22 @@ ark-ec.workspace = true ark-std.workspace = true digest.workspace = true rayon = {workspace = true, optional = true} -bbs_plus = { version = "0.16.0", default-features = false, path = "../bbs_plus" } -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } -vb_accumulator = { version = "0.17.0", default-features = false, path = "../vb_accumulator" } -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } -saver = { version = "0.13.0", default-features = false, path = "../saver" } +bbs_plus = { version = "0.17.0", default-features = false, path = "../bbs_plus" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } +vb_accumulator = { version = "0.18.0", default-features = false, path = "../vb_accumulator" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } +saver = { version = "0.14.0", default-features = false, path = "../saver" } serde.workspace = true serde_with.workspace = true ark-groth16.workspace = true ark-r1cs-std.workspace = true ark-relations.workspace = true zeroize.workspace = true -coconut-crypto = { version = "0.5.0", default-features = false, path = "../coconut" } +coconut-crypto = { version = "0.6.0", default-features = false, path = "../coconut" } merlin = { package = "dock_merlin", version = "2.0", default-features = false, path = "../merlin" } -legogroth16 = { version = "0.10.0", default-features = false, features = ["circom", "aggregation"], path = "../legogroth16" } +legogroth16 = { version = "0.11.0", default-features = false, features = ["circom", "aggregation"], path = "../legogroth16" } +bulletproofs_plus_plus = { version = "0.1.0", default-features = false, path = "../bulletproofs_plus_plus" } +smc_range_proof = { version = "0.1.0", default-features = false, path = "../smc_range_proof" } itertools.workspace = true [dev-dependencies] @@ -43,8 +45,8 @@ test_utils = { default-features = false, path = "../test_utils" } [features] default = ["parallel"] -std = ["ark-ff/std", "ark-ec/std", "ark-std/std", "ark-serialize/std", "schnorr_pok/std", "dock_crypto_utils/std", "serde/std", "saver/std", "ark-groth16/std", "legogroth16/std", "ark-r1cs-std/std", "ark-relations/std", "merlin/std", "coconut-crypto/std"] +std = ["ark-ff/std", "ark-ec/std", "ark-std/std", "ark-serialize/std", "schnorr_pok/std", "dock_crypto_utils/std", "serde/std", "saver/std", "ark-groth16/std", "legogroth16/std", "ark-r1cs-std/std", "ark-relations/std", "merlin/std", "coconut-crypto/std", "bulletproofs_plus_plus/std", "smc_range_proof/std"] print-trace = ["ark-std/print-trace", "schnorr_pok/print-trace", "bbs_plus/print-trace", "vb_accumulator/print-trace", "dock_crypto_utils/print-trace"] -parallel = ["std", "ark-ff/parallel", "ark-ec/parallel", "ark-std/parallel", "rayon", "schnorr_pok/parallel", "bbs_plus/parallel", "vb_accumulator/parallel", "saver/parallel", "ark-groth16/parallel", "legogroth16/parallel", "ark-r1cs-std/parallel", "dock_crypto_utils/parallel", "coconut-crypto/parallel"] +parallel = ["std", "ark-ff/parallel", "ark-ec/parallel", "ark-std/parallel", "rayon", "schnorr_pok/parallel", "bbs_plus/parallel", "vb_accumulator/parallel", "saver/parallel", "ark-groth16/parallel", "legogroth16/parallel", "ark-r1cs-std/parallel", "dock_crypto_utils/parallel", "coconut-crypto/parallel", "bulletproofs_plus_plus/parallel", "smc_range_proof/parallel"] wasmer-js = ["legogroth16/wasmer-js"] wasmer-sys = ["legogroth16/wasmer-sys"] diff --git a/proof_system/src/derived_params.rs b/proof_system/src/derived_params.rs index 2f2a8bee..d1826a7f 100644 --- a/proof_system/src/derived_params.rs +++ b/proof_system/src/derived_params.rs @@ -1,8 +1,11 @@ //! Parameters derived from other parameters during proof generation and verification. Used to prevent repeatedly //! creating these parameters. -use crate::sub_protocols::saver::SaverProtocol; -use ark_ec::pairing::Pairing; +use crate::{ + statement::bound_check_smc::{SmcParamsAndCommitmentKey, SmcParamsWithPairingAndCommitmentKey}, + sub_protocols::saver::SaverProtocol, +}; +use ark_ec::{pairing::Pairing, AffineRepr}; use ark_std::{collections::BTreeMap, marker::PhantomData, vec, vec::Vec}; use bbs_plus::setup::{ PreparedPublicKeyG2 as PreparedBBSPlusPk, @@ -26,6 +29,7 @@ use saver::{ PreparedVerifyingKey as SaverPreparedVerifyingKey, VerifyingKey as SaverVerifyingKey, }, }; +use smc_range_proof::prelude::MemberCommitmentKey; use vb_accumulator::setup::{ PreparedPublicKey as PreparedAccumPk, PreparedSetupParams as PreparedAccumParams, PublicKey as AccumPk, SetupParams as AccumParams, @@ -138,6 +142,23 @@ impl<'a, E: Pairing> } } +/// To derive commitment key from a Pedersen commitment. Used with generators for Bulletproofs++ +impl<'a, E: Pairing, G: AffineRepr> DerivedParams<'a, (G, G), [G; 2]> + for DerivedParamsTracker<'a, (G, G), [G; 2], E> +{ + fn new_derived(ck: &(G, G)) -> [G; 2] { + [ck.0, ck.1] + } +} + +impl<'a, E: Pairing> DerivedParams<'a, MemberCommitmentKey, [E::G1Affine; 2]> + for DerivedParamsTracker<'a, MemberCommitmentKey, [E::G1Affine; 2], E> +{ + fn new_derived(ck: &MemberCommitmentKey) -> [E::G1Affine; 2] { + [ck.g, ck.h] + } +} + macro_rules! impl_derived_for_prepared_ref { ($(#[$doc:meta])* $unprepared: ident, $prepared: ident) => { @@ -218,3 +239,9 @@ impl_derived_for_prepared_ref!( impl_derived_for_prepared_ref!(AccumParams, PreparedAccumParams); impl_derived_for_prepared_ref!(AccumPk, PreparedAccumPk); + +impl_derived_for_prepared_ref!( + /// To derive params with prepared G2 and pairing from `SetMembershipCheckParams` + SmcParamsAndCommitmentKey, + SmcParamsWithPairingAndCommitmentKey +); diff --git a/proof_system/src/error.rs b/proof_system/src/error.rs index 2f6b5e3f..c0a5e65d 100644 --- a/proof_system/src/error.rs +++ b/proof_system/src/error.rs @@ -83,6 +83,12 @@ pub enum ProofSystemError { InvalidNumberOfAggregateLegoGroth16Proofs(usize, usize), NotFoundAggregateLegoGroth16ProofForRequiredStatements(usize, BTreeSet), PSSignaturePoKError(coconut_crypto::SignaturePoKError), + UnsupportedValue(String), + /// For an arbitrary range proof, the response of both Schnorr protocols should be same + DifferentResponsesForSchnorrProtocolInBpp(usize), + BulletproofsPlusPlus(bulletproofs_plus_plus::prelude::BulletproofsPlusPlusError), + SetMembershipBasedRangeProof(smc_range_proof::prelude::SmcRangeProofError), + SmcParamsNotProvided, } impl From for ProofSystemError { @@ -132,3 +138,15 @@ impl From for ProofSystemError { Self::PSSignaturePoKError(e) } } + +impl From for ProofSystemError { + fn from(e: bulletproofs_plus_plus::prelude::BulletproofsPlusPlusError) -> Self { + Self::BulletproofsPlusPlus(e) + } +} + +impl From for ProofSystemError { + fn from(e: smc_range_proof::prelude::SmcRangeProofError) -> Self { + Self::SetMembershipBasedRangeProof(e) + } +} diff --git a/proof_system/src/proof_spec.rs b/proof_system/src/proof_spec.rs index 08ff9822..3487f19e 100644 --- a/proof_system/src/proof_spec.rs +++ b/proof_system/src/proof_spec.rs @@ -32,6 +32,11 @@ use saver::prelude::{ VerifyingKey as SaverVerifyingKey, }; use serde::{Deserialize, Serialize}; +use smc_range_proof::prelude::MemberCommitmentKey; + +use crate::prelude::bound_check_smc::{ + SmcParamsAndCommitmentKey, SmcParamsWithPairingAndCommitmentKey, +}; use vb_accumulator::setup::{ PreparedPublicKey as PreparedAccumPk, PreparedSetupParams as PreparedAccumParams, PublicKey as AccumPk, SetupParams as AccumParams, @@ -226,10 +231,12 @@ where StatementDerivedParams>, StatementDerivedParams<(Vec, Vec)>, StatementDerivedParams>, + StatementDerivedParams<[G; 2]>, + StatementDerivedParams<[E::G1Affine; 2]>, ), ProofSystemError, > { - let mut derived_bound_check_comm = + let mut derived_bound_check_lego_comm = DerivedParamsTracker::, Vec, E>::new(); let mut derived_ek_comm = DerivedParamsTracker::, Vec, E>::new(); @@ -240,9 +247,14 @@ where >::new(); let mut derived_r1cs_comm = DerivedParamsTracker::, Vec, E>::new(); + let mut derived_bound_check_bpp_comm = DerivedParamsTracker::<(G, G), [G; 2], E>::new(); + let mut derived_bound_check_smc_comm = + DerivedParamsTracker::, [E::G1Affine; 2], E>::new(); // To avoid creating variable with short lifetime - let mut tuple_map = BTreeMap::new(); + let mut saver_comm_keys = BTreeMap::new(); + let mut bpp_comm_keys = BTreeMap::new(); + for (s_idx, statement) in self.statements.0.iter().enumerate() { match statement { Statement::SaverProver(_) | Statement::SaverVerifier(_) => { @@ -257,7 +269,13 @@ where ), _ => panic!("This should never happen"), }; - tuple_map.insert(s_idx, (comm_gens, chunk_bit_size)); + saver_comm_keys.insert(s_idx, (comm_gens, chunk_bit_size)); + } + Statement::BoundCheckBpp(s) => { + let ck = s + .get_setup_params(&self.setup_params, s_idx)? + .get_pedersen_commitment_key(); + bpp_comm_keys.insert(s_idx, ck); } _ => (), } @@ -277,7 +295,7 @@ where derived_ek_comm.on_new_statement_idx(enc_key, s_idx); derived_chunked_comm - .on_new_statement_idx(tuple_map.get(&s_idx).unwrap(), s_idx); + .on_new_statement_idx(saver_comm_keys.get(&s_idx).unwrap(), s_idx); } Statement::BoundCheckLegoGroth16Prover(_) @@ -291,7 +309,7 @@ where } _ => panic!("This should never happen"), }; - derived_bound_check_comm.on_new_statement_idx(verifying_key, s_idx); + derived_bound_check_lego_comm.on_new_statement_idx(verifying_key, s_idx); } Statement::R1CSCircomProver(_) | Statement::R1CSCircomVerifier(_) => { @@ -306,14 +324,35 @@ where }; derived_r1cs_comm.on_new_statement_idx(verifying_key, s_idx); } + Statement::BoundCheckBpp(_) => { + let ck = bpp_comm_keys.get(&s_idx).unwrap(); + derived_bound_check_bpp_comm.on_new_statement_idx(ck, s_idx); + } + Statement::BoundCheckSmc(_) + | Statement::BoundCheckSmcWithKVProver(_) + | Statement::BoundCheckSmcWithKVVerifier(_) => { + let comm_key = match statement { + Statement::BoundCheckSmc(s) => s.get_comm_key(&self.setup_params, s_idx)?, + Statement::BoundCheckSmcWithKVProver(s) => { + s.get_comm_key(&self.setup_params, s_idx)? + } + Statement::BoundCheckSmcWithKVVerifier(s) => { + s.get_comm_key(&self.setup_params, s_idx)? + } + _ => panic!("This should never happen"), + }; + derived_bound_check_smc_comm.on_new_statement_idx(comm_key, s_idx); + } _ => (), } } Ok(( - derived_bound_check_comm.finish(), + derived_bound_check_lego_comm.finish(), derived_ek_comm.finish(), derived_chunked_comm.finish(), derived_r1cs_comm.finish(), + derived_bound_check_bpp_comm.finish(), + derived_bound_check_smc_comm.finish(), )) } @@ -334,6 +373,7 @@ where StatementDerivedParams>, StatementDerivedParams>, StatementDerivedParams>, + StatementDerivedParams>, ), ProofSystemError, > { @@ -357,6 +397,11 @@ where let mut derived_ps_p = DerivedParamsTracker::, PreparedPSSigParams, E>::new(); let mut derived_ps_pk = DerivedParamsTracker::, PreparedPSPk, E>::new(); + let mut derived_smc_p = DerivedParamsTracker::< + SmcParamsAndCommitmentKey, + SmcParamsWithPairingAndCommitmentKey, + E, + >::new(); for (s_idx, statement) in self.statements.0.iter().enumerate() { match statement { @@ -413,6 +458,10 @@ where let pk = s.get_public_key(&self.setup_params, s_idx)?; derived_ps_pk.on_new_statement_idx(pk, s_idx); } + Statement::BoundCheckSmc(s) => { + let params = s.get_params_and_comm_key(&self.setup_params, s_idx)?; + derived_smc_p.on_new_statement_idx(params, s_idx); + } _ => (), } } @@ -428,6 +477,7 @@ where derived_ps_p.finish(), derived_ps_pk.finish(), derived_bbs.finish(), + derived_smc_p.finish(), )) } } diff --git a/proof_system/src/prover.rs b/proof_system/src/prover.rs index b4c81a0e..6c8e012a 100644 --- a/proof_system/src/prover.rs +++ b/proof_system/src/prover.rs @@ -23,7 +23,10 @@ use crate::{ accumulator::{AccumulatorMembershipSubProtocol, AccumulatorNonMembershipSubProtocol}, bbs_23::PoKBBSSigG1SubProtocol, bbs_plus::PoKBBSSigG1SubProtocol as PoKBBSPlusSigG1SubProtocol, - bound_check_legogroth16::BoundCheckProtocol, + bound_check_bpp::BoundCheckBppProtocol, + bound_check_legogroth16::BoundCheckLegoGrothProtocol, + bound_check_smc::BoundCheckSmcProtocol, + bound_check_smc_with_kv::BoundCheckSmcWithKVProtocol, r1cs_legogorth16::R1CSLegogroth16Protocol, saver::SaverProtocol, schnorr::SchnorrProtocol, @@ -80,6 +83,16 @@ impl ProverConfig { } } +macro_rules! err_incompat_witness { + ($s_idx:ident, $s: ident, $witness: ident) => { + return Err(ProofSystemError::WitnessIncompatibleWithStatement( + $s_idx, + format!("{:?}", $witness), + format!("{:?}", $s), + )) + }; +} + impl Proof where E: Pairing, @@ -127,14 +140,25 @@ where } // Prepare commitment keys for running Schnorr protocols of all statements. - let (bound_check_comm, ek_comm, chunked_comm, r1cs_comm_keys) = - proof_spec.derive_commitment_keys()?; + let ( + bound_check_lego_comm, + ek_comm, + chunked_comm, + r1cs_comm_keys, + bound_check_bpp_comm, + bound_check_smc_comm, + ) = proof_spec.derive_commitment_keys()?; let mut sub_protocols = Vec::>::with_capacity(proof_spec.statements.0.len()); + // Randomness used by SAVER and LegoGroth16 proofs. This is tracked and returned so subsequent proofs for + // the same public params and witness can reuse this randomness let mut commitment_randomness = BTreeMap::::new(); + // TODO: Use this for all sub-proofs and not just Bulletproofs++ + let mut transcript = new_merlin_transcript(b"composite-proof"); + // Initialize sub-protocols for each statement for (s_idx, (statement, witness)) in proof_spec .statements @@ -165,13 +189,7 @@ where sp.init(rng, blindings_map, w)?; sub_protocols.push(SubProtocol::PoKBBSSignatureG1(sp)); } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( - s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) - } + _ => err_incompat_witness!(s_idx, s, witness), }, Statement::PoKBBSSignature23G1(s) => match witness { Witness::PoKBBSSignature23G1(w) => { @@ -194,13 +212,7 @@ where sp.init(rng, blindings_map, w)?; sub_protocols.push(SubProtocol::PoKBBSSignature23G1(sp)); } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( - s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) - } + _ => err_incompat_witness!(s_idx, s, witness), }, Statement::AccumulatorMembership(s) => match witness { Witness::AccumulatorMembership(w) => { @@ -218,13 +230,7 @@ where sp.init(rng, blinding, w)?; sub_protocols.push(SubProtocol::AccumulatorMembership(sp)); } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( - s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) - } + _ => err_incompat_witness!(s_idx, s, witness), }, Statement::AccumulatorNonMembership(s) => match witness { Witness::AccumulatorNonMembership(w) => { @@ -242,13 +248,7 @@ where sp.init(rng, blinding, w)?; sub_protocols.push(SubProtocol::AccumulatorNonMembership(sp)); } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( - s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) - } + _ => err_incompat_witness!(s_idx, s, witness), }, Statement::PedersenCommitment(s) => match witness { Witness::PedersenCommitment(w) => { @@ -264,13 +264,7 @@ where sp.init(rng, blindings_map, w)?; sub_protocols.push(SubProtocol::PoKDiscreteLogs(sp)); } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( - s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) - } + _ => err_incompat_witness!(s_idx, s, witness), }, Statement::SaverProver(s) => match witness { Witness::Saver(w) => { @@ -293,6 +287,7 @@ where ); match config.get_saver_proof(&s_idx) { + // Found a proof to reuse. Some(OldSaverProof(v, ct, proof)) => { sp.init_with_ciphertext_and_proof( rng, ck_comm_ct, &cc_keys.0, &cc_keys.1, w, blinding, v, ct, @@ -317,24 +312,23 @@ where sub_protocols.push(SubProtocol::Saver(sp)); } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( - s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) - } + _ => err_incompat_witness!(s_idx, s, witness), }, Statement::BoundCheckLegoGroth16Prover(s) => match witness { Witness::BoundCheckLegoGroth16(w) => { let blinding = blindings.remove(&(s_idx, 0)); let proving_key = s.get_proving_key(&proof_spec.setup_params, s_idx)?; - let comm_key = bound_check_comm.get(s_idx).unwrap(); + let comm_key = bound_check_lego_comm.get(s_idx).unwrap(); - let mut sp = - BoundCheckProtocol::new_for_prover(s_idx, s.min, s.max, proving_key); + let mut sp = BoundCheckLegoGrothProtocol::new_for_prover( + s_idx, + s.min, + s.max, + proving_key, + ); match config.get_legogroth16_proof(&s_idx) { + // Found a proof to reuse. Some(OldLegoGroth16Proof(v, proof)) => sp .init_with_old_randomness_and_proof( rng, comm_key, w, blinding, v, proof, @@ -354,15 +348,9 @@ where .unwrap(), ); - sub_protocols.push(SubProtocol::BoundCheckProtocol(sp)); - } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( - s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) + sub_protocols.push(SubProtocol::BoundCheckLegoGroth16(sp)); } + _ => err_incompat_witness!(s_idx, s, witness), }, Statement::R1CSCircomProver(s) => match witness { Witness::R1CSLegoGroth16(w) => { @@ -408,13 +396,7 @@ where ); sub_protocols.push(SubProtocol::R1CSLegogroth16Protocol(sp)); } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( - s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) - } + _ => err_incompat_witness!(s_idx, s, witness), }, Statement::PoKPSSignature(s) => match witness { Witness::PoKPSSignature(w) => { @@ -433,13 +415,50 @@ where sp.init(rng, blindings_map, w)?; sub_protocols.push(SubProtocol::PSSignaturePoK(sp)); } - _ => { - return Err(ProofSystemError::WitnessIncompatibleWithStatement( + _ => err_incompat_witness!(s_idx, s, witness), + }, + Statement::BoundCheckBpp(s) => match witness { + Witness::BoundCheckBpp(w) => { + let blinding = blindings.remove(&(s_idx, 0)); + let bpp_setup_params = + s.get_setup_params(&proof_spec.setup_params, s_idx)?; + let comm_key = bound_check_bpp_comm.get(s_idx).unwrap(); + let mut sp = + BoundCheckBppProtocol::new(s_idx, s.min, s.max, bpp_setup_params); + sp.init(rng, comm_key.as_slice(), w, blinding, &mut transcript)?; + sub_protocols.push(SubProtocol::BoundCheckBpp(sp)); + } + _ => err_incompat_witness!(s_idx, s, witness), + }, + Statement::BoundCheckSmc(s) => match witness { + Witness::BoundCheckSmc(w) => { + let blinding = blindings.remove(&(s_idx, 0)); + let params_comm_key = + s.get_params_and_comm_key(&proof_spec.setup_params, s_idx)?; + let comm_key_as_slice = bound_check_smc_comm.get(s_idx).unwrap(); + let mut sp = + BoundCheckSmcProtocol::new(s_idx, s.min, s.max, params_comm_key); + sp.init(rng, comm_key_as_slice, w, blinding)?; + sub_protocols.push(SubProtocol::BoundCheckSmc(sp)); + } + _ => err_incompat_witness!(s_idx, s, witness), + }, + Statement::BoundCheckSmcWithKVProver(s) => match witness { + Witness::BoundCheckSmcWithKV(w) => { + let blinding = blindings.remove(&(s_idx, 0)); + let params_comm_key = + s.get_params_and_comm_key(&proof_spec.setup_params, s_idx)?; + let comm_key_as_slice = bound_check_smc_comm.get(s_idx).unwrap(); + let mut sp = BoundCheckSmcWithKVProtocol::new_for_prover( s_idx, - format!("{:?}", witness), - format!("{:?}", s), - )) + s.min, + s.max, + params_comm_key, + ); + sp.init(rng, comm_key_as_slice, w, blinding)?; + sub_protocols.push(SubProtocol::BoundCheckSmcWithKV(sp)); } + _ => err_incompat_witness!(s_idx, s, witness), }, _ => return Err(ProofSystemError::InvalidStatement), } @@ -495,8 +514,9 @@ where }; let prepared_srs = PreparedProverSRS::from(srs); - let mut transcript = new_merlin_transcript(b"aggregation"); - transcript.append(b"challenge", &challenge); + // TODO: Remove it and use outer transcript + let mut aggr_transcript = new_merlin_transcript(b"aggregation"); + aggr_transcript.append(b"challenge", &challenge); if proof_spec.aggregate_groth16.is_some() { let to_aggr = proof_spec.aggregate_groth16.unwrap(); @@ -511,7 +531,7 @@ where } let ag_proof = legogroth16::aggregation::groth16::aggregate_proofs( prepared_srs.clone(), - &mut transcript, + &mut aggr_transcript, &proofs, ) .map_err(|e| ProofSystemError::LegoGroth16Error(e.into()))?; @@ -537,7 +557,7 @@ where let (ag_proof, _) = legogroth16::aggregation::legogroth16::using_groth16::aggregate_proofs( prepared_srs.clone(), - &mut transcript, + &mut aggr_transcript, &proofs, ) .map_err(|e| ProofSystemError::LegoGroth16Error(e.into()))?; diff --git a/proof_system/src/setup_params.rs b/proof_system/src/setup_params.rs index f747bf74..d28401a3 100644 --- a/proof_system/src/setup_params.rs +++ b/proof_system/src/setup_params.rs @@ -5,12 +5,18 @@ //! becomes more important when interacting with the WASM bindings of this crate as the overhead of repeated //! serialization and de-serialization can be avoided. +use crate::{ + prelude::bound_check_smc::SmcParamsAndCommitmentKey, + statement::bound_check_smc_with_kv::SmcParamsAndCommitmentKeyAndSecretKey, +}; use ark_ec::{pairing::Pairing, AffineRepr}; use ark_std::vec::Vec; use bbs_plus::prelude::{ PublicKeyG2 as BBSPublicKeyG2, SignatureParams23G1 as BBSSignatureParams23G1, SignatureParamsG1 as BBSSignatureParamsG1, }; +use bulletproofs_plus_plus::setup::SetupParams as BppSetupParams; +use dock_crypto_utils::serde_utils::ArkObjectBytes; use legogroth16::{ circom::R1CS, data_structures::{ProvingKey as LegoSnarkProvingKey, VerifyingKey as LegoSnarkVerifyingKey}, @@ -19,15 +25,13 @@ use saver::prelude::{ ChunkedCommitmentGens, EncryptionGens, EncryptionKey, ProvingKey as SaverSnarkProvingKey, VerifyingKey as SaverSnarkVerifyingKey, }; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; use vb_accumulator::prelude::{ MembershipProvingKey, NonMembershipProvingKey, PublicKey as AccumPublicKey, SetupParams as AccumParams, }; -use dock_crypto_utils::serde_utils::ArkObjectBytes; -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; - /// Holds (public) setup parameters of different protocols. #[serde_as] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -53,6 +57,11 @@ pub enum SetupParams { PSSignatureParams(coconut_crypto::setup::SignatureParams), PSSignaturePublicKey(coconut_crypto::setup::PublicKey), BBSSignatureParams23(BBSSignatureParams23G1), + BppSetupParams(#[serde_as(as = "ArkObjectBytes")] BppSetupParams), + SmcParamsAndCommKey(#[serde_as(as = "ArkObjectBytes")] SmcParamsAndCommitmentKey), + SmcParamsAndCommKeyAndSk( + #[serde_as(as = "ArkObjectBytes")] SmcParamsAndCommitmentKeyAndSecretKey, + ), } macro_rules! delegate { @@ -78,7 +87,10 @@ macro_rules! delegate { FieldElemVec, PSSignatureParams, PSSignaturePublicKey, - BBSSignatureParams23 + BBSSignatureParams23, + BppSetupParams, + SmcParamsAndCommKey, + SmcParamsAndCommKeyAndSk : $($tt)+ } }}; @@ -107,7 +119,10 @@ macro_rules! delegate_reverse { FieldElemVec, PSSignatureParams, PSSignaturePublicKey, - BBSSignatureParams23 + BBSSignatureParams23, + BppSetupParams, + SmcParamsAndCommKey, + SmcParamsAndCommKeyAndSk : $($tt)+ } diff --git a/proof_system/src/statement/bound_check_bpp.rs b/proof_system/src/statement/bound_check_bpp.rs new file mode 100644 index 00000000..7f3aae6a --- /dev/null +++ b/proof_system/src/statement/bound_check_bpp.rs @@ -0,0 +1,69 @@ +use crate::{ + error::ProofSystemError, setup_params::SetupParams, statement::Statement, + sub_protocols::validate_bounds, +}; +use ark_ec::{pairing::Pairing, AffineRepr}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::vec::Vec; +use bulletproofs_plus_plus::setup::SetupParams as BppSetupParams; +use dock_crypto_utils::serde_utils::ArkObjectBytes; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +/// Proving knowledge of message that satisfies given bounds, i.e. `min <= message < max` using Bulletproofs++. +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +#[serde(bound = "")] +pub struct BoundCheckBpp { + pub min: u64, + pub max: u64, + #[serde_as(as = "Option")] + pub params: Option>, + pub params_ref: Option, +} + +impl BoundCheckBpp { + pub fn new_statement_from_params( + min: u64, + max: u64, + params: BppSetupParams, + ) -> Result, ProofSystemError> { + validate_bounds(min, max)?; + Ok(Statement::BoundCheckBpp(Self { + min, + max, + params: Some(params), + params_ref: None, + })) + } + + pub fn new_statement_from_params_ref( + min: u64, + max: u64, + params_ref: usize, + ) -> Result, ProofSystemError> { + Ok(Statement::BoundCheckBpp(Self { + min, + max, + params: None, + params_ref: Some(params_ref), + })) + } + + pub fn get_setup_params<'a, E: Pairing>( + &'a self, + setup_params: &'a [SetupParams], + st_idx: usize, + ) -> Result<&'a BppSetupParams, ProofSystemError> { + extract_param!( + setup_params, + &self.params, + self.params_ref, + BppSetupParams, + IncompatibleBoundCheckSetupParamAtIndex, + st_idx + ) + } +} diff --git a/proof_system/src/statement/bound_check_legogroth16.rs b/proof_system/src/statement/bound_check_legogroth16.rs index baaa7141..11383368 100644 --- a/proof_system/src/statement/bound_check_legogroth16.rs +++ b/proof_system/src/statement/bound_check_legogroth16.rs @@ -7,12 +7,14 @@ use serde_with::serde_as; pub use legogroth16::{PreparedVerifyingKey, ProvingKey, VerifyingKey}; use crate::{ - error::ProofSystemError, setup_params::SetupParams, statement::Statement, - sub_protocols::bound_check_legogroth16::BoundCheckProtocol, + error::ProofSystemError, + setup_params::SetupParams, + statement::Statement, + sub_protocols::{bound_check_legogroth16::BoundCheckLegoGrothProtocol, validate_bounds}, }; use dock_crypto_utils::serde_utils::ArkObjectBytes; -/// Proving knowledge of message that satisfies given bounds, i.e. `min <= message <= max` using LegoGroth16. +/// Proving knowledge of message that satisfies given bounds [min, max), i.e. `min <= message < max` using LegoGroth16. #[serde_as] #[derive( Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, @@ -26,7 +28,7 @@ pub struct BoundCheckLegoGroth16Prover { pub snark_proving_key_ref: Option, } -/// Proving knowledge of message that satisfies given bounds, i.e. `min <= message <= max` using LegoGroth16 +/// Proving knowledge of message that satisfies given bounds [min, max), i.e. `min <= message < max` using LegoGroth16 #[serde_as] #[derive( Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, @@ -46,8 +48,8 @@ impl BoundCheckLegoGroth16Prover { max: u64, snark_proving_key: ProvingKey, ) -> Result, ProofSystemError> { - BoundCheckProtocol::validate_verification_key(&snark_proving_key.vk)?; - BoundCheckProtocol::::validate_bounds(min, max)?; + BoundCheckLegoGrothProtocol::validate_verification_key(&snark_proving_key.vk)?; + validate_bounds(min, max)?; Ok(Statement::BoundCheckLegoGroth16Prover(Self { min, @@ -62,7 +64,7 @@ impl BoundCheckLegoGroth16Prover { max: u64, snark_proving_key_ref: usize, ) -> Result, ProofSystemError> { - BoundCheckProtocol::::validate_bounds(min, max)?; + validate_bounds(min, max)?; Ok(Statement::BoundCheckLegoGroth16Prover(Self { min, max, @@ -93,8 +95,8 @@ impl BoundCheckLegoGroth16Verifier { max: u64, snark_verifying_key: VerifyingKey, ) -> Result, ProofSystemError> { - BoundCheckProtocol::validate_verification_key(&snark_verifying_key)?; - BoundCheckProtocol::::validate_bounds(min, max)?; + BoundCheckLegoGrothProtocol::validate_verification_key(&snark_verifying_key)?; + validate_bounds(min, max)?; Ok(Statement::BoundCheckLegoGroth16Verifier(Self { min, @@ -109,7 +111,7 @@ impl BoundCheckLegoGroth16Verifier { max: u64, snark_verifying_key_ref: usize, ) -> Result, ProofSystemError> { - BoundCheckProtocol::::validate_bounds(min, max)?; + validate_bounds(min, max)?; Ok(Statement::BoundCheckLegoGroth16Verifier(Self { min, max, diff --git a/proof_system/src/statement/bound_check_smc.rs b/proof_system/src/statement/bound_check_smc.rs new file mode 100644 index 00000000..ec104700 --- /dev/null +++ b/proof_system/src/statement/bound_check_smc.rs @@ -0,0 +1,127 @@ +use crate::{error::ProofSystemError, statement::Statement, sub_protocols::validate_bounds}; +use ark_ec::{pairing::Pairing, AffineRepr}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec::Vec}; +use digest::Digest; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use smc_range_proof::prelude::{ + MemberCommitmentKey, SecretKey, SetMembershipCheckParams, SetMembershipCheckParamsWithPairing, +}; + +use crate::setup_params::SetupParams; +use dock_crypto_utils::serde_utils::ArkObjectBytes; + +/// For ease of use, keeping setup params together but they could be generated independently +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +pub struct SmcParamsAndCommitmentKey { + #[serde_as(as = "ArkObjectBytes")] + pub params: SetMembershipCheckParams, + #[serde_as(as = "ArkObjectBytes")] + pub comm_key: MemberCommitmentKey, +} + +#[serde_as] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize)] +pub struct SmcParamsWithPairingAndCommitmentKey { + #[serde_as(as = "ArkObjectBytes")] + pub params: SetMembershipCheckParamsWithPairing, + #[serde_as(as = "ArkObjectBytes")] + pub comm_key: MemberCommitmentKey, +} + +impl SmcParamsAndCommitmentKey { + pub fn new( + rng: &mut R, + label: &[u8], + base: u16, + ) -> (Self, SecretKey) { + let (params, sk) = SetMembershipCheckParams::new_for_range_proof::(rng, label, base); + let comm_key = MemberCommitmentKey::new::(label); + (Self { params, comm_key }, sk) + } + + pub fn verify(&self) -> Result<(), ProofSystemError> { + self.params.verify()?; + Ok(()) + } +} + +impl From> for SmcParamsWithPairingAndCommitmentKey { + fn from(params: SmcParamsAndCommitmentKey) -> Self { + let comm_key = params.comm_key; + let params = SetMembershipCheckParamsWithPairing::from(params.params); + Self { comm_key, params } + } +} + +/// Proving knowledge of message that satisfies given bounds, i.e. `min <= message < max` using set-membership based check. +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +#[serde(bound = "")] +pub struct BoundCheckSmc { + pub min: u64, + pub max: u64, + #[serde_as(as = "Option")] + pub params_and_comm_key: Option>, + pub params_and_comm_key_ref: Option, +} + +impl BoundCheckSmc { + pub fn new_statement_from_params( + min: u64, + max: u64, + params: SmcParamsAndCommitmentKey, + ) -> Result, ProofSystemError> { + validate_bounds(min, max)?; + + Ok(Statement::BoundCheckSmc(Self { + min, + max, + params_and_comm_key: Some(params), + params_and_comm_key_ref: None, + })) + } + + pub fn new_statement_from_params_ref( + min: u64, + max: u64, + params_ref: usize, + ) -> Result, ProofSystemError> { + validate_bounds(min, max)?; + Ok(Statement::BoundCheckSmc(Self { + min, + max, + params_and_comm_key: None, + params_and_comm_key_ref: Some(params_ref), + })) + } + + pub fn get_params_and_comm_key<'a, G: AffineRepr>( + &'a self, + setup_params: &'a [SetupParams], + st_idx: usize, + ) -> Result<&'a SmcParamsAndCommitmentKey, ProofSystemError> { + extract_param!( + setup_params, + &self.params_and_comm_key, + self.params_and_comm_key_ref, + SmcParamsAndCommKey, + IncompatibleBoundCheckSetupParamAtIndex, + st_idx + ) + } + + pub fn get_comm_key<'a, G: AffineRepr>( + &'a self, + setup_params: &'a [SetupParams], + st_idx: usize, + ) -> Result<&'a MemberCommitmentKey, ProofSystemError> { + Ok(&self.get_params_and_comm_key(setup_params, st_idx)?.comm_key) + } +} diff --git a/proof_system/src/statement/bound_check_smc_with_kv.rs b/proof_system/src/statement/bound_check_smc_with_kv.rs new file mode 100644 index 00000000..25cd369c --- /dev/null +++ b/proof_system/src/statement/bound_check_smc_with_kv.rs @@ -0,0 +1,184 @@ +use crate::{ + error::ProofSystemError, + setup_params::SetupParams, + statement::{bound_check_smc::SmcParamsAndCommitmentKey, Statement}, + sub_protocols::validate_bounds, +}; +use ark_ec::{pairing::Pairing, AffineRepr}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec::Vec}; +use digest::Digest; +use dock_crypto_utils::serde_utils::ArkObjectBytes; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use smc_range_proof::prelude::{MemberCommitmentKey, SecretKey, SetMembershipCheckParams}; + +/// Used by the verifier as it knows the secret key +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +pub struct SmcParamsAndCommitmentKeyAndSecretKey { + pub params_and_comm_key: SmcParamsAndCommitmentKey, + #[serde_as(as = "ArkObjectBytes")] + pub sk: SecretKey, +} + +impl SmcParamsAndCommitmentKeyAndSecretKey { + pub fn new(rng: &mut R, label: &[u8], base: u16) -> Self { + let (params_and_comm_key, sk) = SmcParamsAndCommitmentKey::new::(rng, label, base); + Self { + params_and_comm_key, + sk, + } + } + + pub fn verify(&self) -> Result<(), ProofSystemError> { + self.params_and_comm_key.verify()?; + Ok(()) + } + + pub fn get_smc_params(&self) -> &SetMembershipCheckParams { + &self.params_and_comm_key.params + } + + pub fn get_comm_key(&self) -> &MemberCommitmentKey { + &self.params_and_comm_key.comm_key + } +} + +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +#[serde(bound = "")] +pub struct BoundCheckSmcWithKVProver { + pub min: u64, + pub max: u64, + #[serde_as(as = "Option")] + pub params: Option>, + pub params_ref: Option, +} + +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +#[serde(bound = "")] +pub struct BoundCheckSmcWithKVVerifier { + pub min: u64, + pub max: u64, + #[serde_as(as = "Option")] + pub params: Option>, + pub params_ref: Option, +} + +impl BoundCheckSmcWithKVProver { + pub fn new_statement_from_params( + min: u64, + max: u64, + params: SmcParamsAndCommitmentKey, + ) -> Result, ProofSystemError> { + validate_bounds(min, max)?; + + Ok(Statement::BoundCheckSmcWithKVProver(Self { + min, + max, + params: Some(params), + params_ref: None, + })) + } + + pub fn new_statement_from_params_ref( + min: u64, + max: u64, + params_ref: usize, + ) -> Result, ProofSystemError> { + validate_bounds(min, max)?; + Ok(Statement::BoundCheckSmcWithKVProver(Self { + min, + max, + params: None, + params_ref: Some(params_ref), + })) + } + + pub fn get_params_and_comm_key<'a, G: AffineRepr>( + &'a self, + setup_params: &'a [SetupParams], + st_idx: usize, + ) -> Result<&'a SmcParamsAndCommitmentKey, ProofSystemError> { + extract_param!( + setup_params, + &self.params, + self.params_ref, + SmcParamsAndCommKey, + IncompatibleBoundCheckSetupParamAtIndex, + st_idx + ) + } + + pub fn get_comm_key<'a, G: AffineRepr>( + &'a self, + setup_params: &'a [SetupParams], + st_idx: usize, + ) -> Result<&'a MemberCommitmentKey, ProofSystemError> { + Ok(&self.get_params_and_comm_key(setup_params, st_idx)?.comm_key) + } +} + +impl BoundCheckSmcWithKVVerifier { + pub fn new_statement_from_params( + min: u64, + max: u64, + params: SmcParamsAndCommitmentKeyAndSecretKey, + ) -> Result, ProofSystemError> { + validate_bounds(min, max)?; + + Ok(Statement::BoundCheckSmcWithKVVerifier(Self { + min, + max, + params: Some(params), + params_ref: None, + })) + } + + pub fn new_statement_from_params_ref( + min: u64, + max: u64, + params_ref: usize, + ) -> Result, ProofSystemError> { + validate_bounds(min, max)?; + Ok(Statement::BoundCheckSmcWithKVVerifier(Self { + min, + max, + params: None, + params_ref: Some(params_ref), + })) + } + + pub fn get_params_and_comm_key_and_sk<'a, G: AffineRepr>( + &'a self, + setup_params: &'a [SetupParams], + st_idx: usize, + ) -> Result<&'a SmcParamsAndCommitmentKeyAndSecretKey, ProofSystemError> { + extract_param!( + setup_params, + &self.params, + self.params_ref, + SmcParamsAndCommKeyAndSk, + IncompatibleBoundCheckSetupParamAtIndex, + st_idx + ) + } + + pub fn get_comm_key<'a, G: AffineRepr>( + &'a self, + setup_params: &'a [SetupParams], + st_idx: usize, + ) -> Result<&'a MemberCommitmentKey, ProofSystemError> { + Ok(self + .get_params_and_comm_key_and_sk(setup_params, st_idx)? + .get_comm_key()) + } +} diff --git a/proof_system/src/statement/mod.rs b/proof_system/src/statement/mod.rs index f68aaab3..5e7750a8 100644 --- a/proof_system/src/statement/mod.rs +++ b/proof_system/src/statement/mod.rs @@ -10,7 +10,10 @@ pub mod accumulator; pub mod bbs_23; #[macro_use] pub mod bbs_plus; +pub mod bound_check_bpp; pub mod bound_check_legogroth16; +pub mod bound_check_smc; +pub mod bound_check_smc_with_kv; pub mod ped_comm; pub mod ps_signature; pub mod r1cs_legogroth16; @@ -34,9 +37,9 @@ pub enum Statement { SaverProver(saver::SaverProver), /// Used by verifier to verify proof of verifiable encryption using SAVER SaverVerifier(saver::SaverVerifier), - /// Used by prover to create proof that witness satisfies publicly known bounds inclusively (<=, >=) using LegoGroth16 + /// Used by prover to create proof that witness satisfies publicly known bounds [min, max) using LegoGroth16 BoundCheckLegoGroth16Prover(bound_check_legogroth16::BoundCheckLegoGroth16Prover), - /// Used by verifier to verify proof that witness satisfies publicly known bounds inclusively (<=, >=) using LegoGroth16 + /// Used by verifier to verify proof that witness satisfies publicly known bounds [min, max) using LegoGroth16 BoundCheckLegoGroth16Verifier(bound_check_legogroth16::BoundCheckLegoGroth16Verifier), /// Used by prover to create proof that witness satisfies constraints given by an R1CS (generated by Circom), using LegoGroth16 R1CSCircomProver(r1cs_legogroth16::R1CSCircomProver), @@ -46,6 +49,14 @@ pub enum Statement { PoKPSSignature(ps_signature::PoKPSSignatureStatement), /// For proof of knowledge of BBS signature PoKBBSSignature23G1(bbs_23::PoKBBSSignature23G1), + /// For bound check using Bulletproofs++ protocol + BoundCheckBpp(bound_check_bpp::BoundCheckBpp), + /// For bound check using set-membership check based protocols + BoundCheckSmc(bound_check_smc::BoundCheckSmc), + /// Used by the prover for bound check using set-membership check with keyed verification based protocols + BoundCheckSmcWithKVProver(bound_check_smc_with_kv::BoundCheckSmcWithKVProver), + /// Used by the verifier for bound check using set-membership check with keyed verification based protocols + BoundCheckSmcWithKVVerifier(bound_check_smc_with_kv::BoundCheckSmcWithKVVerifier), } /// A collection of statements @@ -96,7 +107,11 @@ macro_rules! delegate { R1CSCircomProver, R1CSCircomVerifier, PoKPSSignature, - PoKBBSSignature23G1 + PoKBBSSignature23G1, + BoundCheckBpp, + BoundCheckSmc, + BoundCheckSmcWithKVProver, + BoundCheckSmcWithKVVerifier : $($tt)+ } }} @@ -117,7 +132,11 @@ macro_rules! delegate_reverse { R1CSCircomProver, R1CSCircomVerifier, PoKPSSignature, - PoKBBSSignature23G1 + PoKBBSSignature23G1, + BoundCheckBpp, + BoundCheckSmc, + BoundCheckSmcWithKVProver, + BoundCheckSmcWithKVVerifier : $($tt)+ } diff --git a/proof_system/src/statement_proof.rs b/proof_system/src/statement_proof.rs index 06b6d7c8..b859c5dc 100644 --- a/proof_system/src/statement_proof.rs +++ b/proof_system/src/statement_proof.rs @@ -5,6 +5,7 @@ use ark_std::{ vec::Vec, }; use bbs_plus::prelude::{PoKOfSignature23G1Proof, PoKOfSignatureG1Proof}; +use bulletproofs_plus_plus::prelude::ProofArbitraryRange; use coconut_crypto::SignaturePoK as PSSignaturePoK; use dock_crypto_utils::serde_utils::*; use saver::encryption::Ciphertext; @@ -32,6 +33,9 @@ pub enum StatementProof { R1CSLegoGroth16WithAggregation(R1CSLegoGroth16ProofWhenAggregatingSnarks), PoKPSSignature(PSSignaturePoK), PoKBBSSignature23G1(PoKOfSignature23G1Proof), + BoundCheckBpp(BoundCheckBppProof), + BoundCheckSmc(BoundCheckSmcProof), + BoundCheckSmcWithKV(BoundCheckSmcWithKVProof), } macro_rules! delegate { @@ -49,7 +53,10 @@ macro_rules! delegate { BoundCheckLegoGroth16WithAggregation, R1CSLegoGroth16WithAggregation, PoKPSSignature, - PoKBBSSignature23G1 + PoKBBSSignature23G1, + BoundCheckBpp, + BoundCheckSmc, + BoundCheckSmcWithKV : $($tt)+ } }}; @@ -70,7 +77,10 @@ macro_rules! delegate_reverse { BoundCheckLegoGroth16WithAggregation, R1CSLegoGroth16WithAggregation, PoKPSSignature, - PoKBBSSignature23G1 + PoKBBSSignature23G1, + BoundCheckBpp, + BoundCheckSmc, + BoundCheckSmcWithKV : $($tt)+ } @@ -250,11 +260,86 @@ impl R1CSLegoGroth16ProofWhenAggregatingSnarks { } } +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +#[serde(bound = "")] +pub struct BoundCheckBppProof { + #[serde_as(as = "ArkObjectBytes")] + pub bpp_proof: ProofArbitraryRange, + pub sp1: PedersenCommitmentProof, + pub sp2: PedersenCommitmentProof, +} + +impl BoundCheckBppProof { + pub fn get_schnorr_response_for_message(&self) -> Result<&G::ScalarField, ProofSystemError> { + self.sp1.response.get_response(0).map_err(|e| e.into()) + } + + /// For the proof to be correct, both responses of Schnorr protocols should be correct as both + /// are proving the knowledge of same committed message + pub fn check_schnorr_responses_consistency(&self) -> Result { + Ok(self.sp1.response.get_response(0)? == self.sp2.response.get_response(0)?) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum BoundCheckSmcInnerProof { + CCS(smc_range_proof::prelude::CCSArbitraryRangeProof), + CLS(smc_range_proof::prelude::CLSRangeProof), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum BoundCheckSmcWithKVInnerProof { + CCS(smc_range_proof::prelude::CCSArbitraryRangeWithKVProof), + CLS(smc_range_proof::prelude::CLSRangeProofWithKV), +} + +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +#[serde(bound = "")] +pub struct BoundCheckSmcProof { + #[serde_as(as = "ArkObjectBytes")] + pub proof: BoundCheckSmcInnerProof, + #[serde_as(as = "ArkObjectBytes")] + pub comm: E::G1Affine, + pub sp: PedersenCommitmentProof, +} + +impl BoundCheckSmcProof { + pub fn get_schnorr_response_for_message(&self) -> Result<&E::ScalarField, ProofSystemError> { + self.sp.response.get_response(0).map_err(|e| e.into()) + } +} + +#[serde_as] +#[derive( + Clone, Debug, PartialEq, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, +)] +#[serde(bound = "")] +pub struct BoundCheckSmcWithKVProof { + #[serde_as(as = "ArkObjectBytes")] + pub proof: BoundCheckSmcWithKVInnerProof, + #[serde_as(as = "ArkObjectBytes")] + pub comm: E::G1Affine, + pub sp: PedersenCommitmentProof, +} + +impl BoundCheckSmcWithKVProof { + pub fn get_schnorr_response_for_message(&self) -> Result<&E::ScalarField, ProofSystemError> { + self.sp.response.get_response(0).map_err(|e| e.into()) + } +} + mod serialization { use super::{ AffineRepr, CanonicalDeserialize, CanonicalSerialize, Pairing, Read, SerializationError, StatementProof, Write, }; + use crate::statement_proof::{BoundCheckSmcInnerProof, BoundCheckSmcWithKVInnerProof}; use ark_serialize::{Compress, Valid, Validate}; impl Valid for StatementProof { @@ -297,4 +382,132 @@ mod serialization { ) } } + + macro_rules! impl_serz_for_bound_check_inner { + ( $name:ident) => { + impl Valid for $name { + fn check(&self) -> Result<(), SerializationError> { + match self { + Self::CCS(c) => c.check(), + Self::CLS(c) => c.check(), + } + } + } + + impl CanonicalSerialize for $name { + fn serialize_with_mode( + &self, + mut writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + match self { + Self::CCS(c) => { + CanonicalSerialize::serialize_with_mode(&0u8, &mut writer, compress)?; + CanonicalSerialize::serialize_with_mode(c, &mut writer, compress) + } + Self::CLS(c) => { + CanonicalSerialize::serialize_with_mode(&1u8, &mut writer, compress)?; + CanonicalSerialize::serialize_with_mode(c, &mut writer, compress) + } + } + } + + fn serialized_size(&self, compress: Compress) -> usize { + match self { + Self::CCS(c) => 0u8.serialized_size(compress) + c.serialized_size(compress), + Self::CLS(c) => 1u8.serialized_size(compress) + c.serialized_size(compress), + } + } + } + + impl CanonicalDeserialize for $name { + fn deserialize_with_mode( + mut reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + let t: u8 = CanonicalDeserialize::deserialize_with_mode( + &mut reader, + compress, + validate, + )?; + match t { + 0u8 => Ok(Self::CCS(CanonicalDeserialize::deserialize_with_mode( + &mut reader, + compress, + validate, + )?)), + 1u8 => Ok(Self::CLS(CanonicalDeserialize::deserialize_with_mode( + &mut reader, + compress, + validate, + )?)), + _ => Err(SerializationError::InvalidData), + } + } + } + }; + } + + impl_serz_for_bound_check_inner!(BoundCheckSmcInnerProof); + impl_serz_for_bound_check_inner!(BoundCheckSmcWithKVInnerProof); + + /*impl Valid for BoundCheckSmcInnerProof { + fn check(&self) -> Result<(), SerializationError> { + match self { + Self::CCS(c) => c.check(), + Self::CLS(c) => c.check(), + } + } + } + + impl CanonicalSerialize for BoundCheckSmcInnerProof { + fn serialize_with_mode( + &self, + mut writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + match self { + Self::CCS(c) => { + CanonicalSerialize::serialize_with_mode(&0u8, &mut writer, compress)?; + CanonicalSerialize::serialize_with_mode(c, &mut writer, compress) + } + Self::CLS(c) => { + CanonicalSerialize::serialize_with_mode(&1u8, &mut writer, compress)?; + CanonicalSerialize::serialize_with_mode(c, &mut writer, compress) + } + } + } + + fn serialized_size(&self, compress: Compress) -> usize { + match self { + Self::CCS(c) => 0u8.serialized_size(compress) + c.serialized_size(compress), + Self::CLS(c) => 1u8.serialized_size(compress) + c.serialized_size(compress), + } + } + } + + impl CanonicalDeserialize for BoundCheckSmcInnerProof { + fn deserialize_with_mode( + mut reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + let t: u8 = + CanonicalDeserialize::deserialize_with_mode(&mut reader, compress, validate)?; + match t { + 0u8 => Ok(Self::CCS(CanonicalDeserialize::deserialize_with_mode( + &mut reader, + compress, + validate, + )?)), + 1u8 => Ok(Self::CLS(CanonicalDeserialize::deserialize_with_mode( + &mut reader, + compress, + validate, + )?)), + _ => Err(SerializationError::InvalidData), + } + } + }*/ } diff --git a/proof_system/src/sub_protocols/bound_check_bpp.rs b/proof_system/src/sub_protocols/bound_check_bpp.rs new file mode 100644 index 00000000..2d57ab42 --- /dev/null +++ b/proof_system/src/sub_protocols/bound_check_bpp.rs @@ -0,0 +1,207 @@ +use crate::{ + error::ProofSystemError, + prelude::StatementProof, + statement_proof::BoundCheckBppProof, + sub_protocols::{enforce_and_get_u64, schnorr::SchnorrProtocol}, +}; +use ark_ec::{pairing::Pairing, AffineRepr}; +use ark_serialize::CanonicalSerialize; +use ark_std::{collections::BTreeMap, io::Write, rand::RngCore, vec, UniformRand}; +use bulletproofs_plus_plus::{prelude::ProofArbitraryRange, setup::SetupParams}; +use dock_crypto_utils::transcript::Transcript; + +/// Runs the Bulletproofs++ protocol for proving bounds of a witness and a Schnorr protocol for proving +/// knowledge of the witness committed in the commitments accompanying the proof. +#[derive(Clone, Debug, PartialEq)] +pub struct BoundCheckBppProtocol<'a, G: AffineRepr> { + pub id: usize, + pub min: u64, + pub max: u64, + pub setup_params: &'a SetupParams, + pub bpp_proof: Option>, + pub sp1: Option>, + pub sp2: Option>, +} + +impl<'a, G: AffineRepr> BoundCheckBppProtocol<'a, G> { + pub fn new(id: usize, min: u64, max: u64, setup_params: &'a SetupParams) -> Self { + Self { + id, + min, + max, + setup_params, + bpp_proof: None, + sp1: None, + sp2: None, + } + } + + pub fn init( + &mut self, + rng: &mut R, + comm_key: &'a [G], + message: G::ScalarField, + blinding: Option, + transcript: &mut impl Transcript, + ) -> Result<(), ProofSystemError> { + if self.sp1.is_some() || self.sp2.is_some() { + return Err(ProofSystemError::SubProtocolAlreadyInitialized(self.id)); + } + let msg_as_u64 = enforce_and_get_u64::(&message)?; + + // blindings for the commitments in the Bulletproofs++ proof, there will be 2 Bulletproofs++ proofs, for ranges `(message - min)` and `(max - message)` + let bpp_randomness = vec![G::ScalarField::rand(rng), G::ScalarField::rand(rng)]; + let proof = ProofArbitraryRange::new( + rng, + Self::get_num_bits(self.max), + vec![(msg_as_u64, self.min, self.max)], + bpp_randomness.clone(), + self.setup_params.clone(), + transcript, + )?; + assert_eq!(proof.num_proofs(), 1); + self.init_schnorr_protocol( + rng, + comm_key, + message, + blinding, + (bpp_randomness[0], bpp_randomness[1]), + proof, + ) + } + + fn init_schnorr_protocol( + &mut self, + rng: &mut R, + comm_key: &'a [G], + message: G::ScalarField, + blinding: Option, + blindings_for_bpp: (G::ScalarField, G::ScalarField), + bpp_proof: ProofArbitraryRange, + ) -> Result<(), ProofSystemError> { + // blinding used to prove knowledge of message in `snark_proof.d`. The caller of this method ensures + // that this will be same as the one used proving knowledge of the corresponding message in BBS+ + // signature, thus allowing them to be proved equal. + let blinding = if blinding.is_none() { + G::ScalarField::rand(rng) + } else { + blinding.unwrap() + }; + let mut blindings = BTreeMap::new(); + blindings.insert(0, blinding); + + let (r1, r2) = blindings_for_bpp; + let (comm_1, comm_2) = self.get_commitments_to_values(&bpp_proof)?; + // NOTE: value of id is dummy + let mut sp1 = SchnorrProtocol::new(10000, comm_key, comm_1); + let mut sp2 = SchnorrProtocol::new(10000, comm_key, comm_2); + sp1.init(rng, blindings.clone(), vec![message, r1])?; + sp2.init(rng, blindings, vec![message, -r2])?; + self.bpp_proof = Some(bpp_proof); + self.sp1 = Some(sp1); + self.sp2 = Some(sp2); + Ok(()) + } + + /// Generate challenge contribution for both the Schnorr protocols + pub fn challenge_contribution(&self, mut writer: W) -> Result<(), ProofSystemError> { + if self.sp1.is_none() || self.sp2.is_none() { + return Err(ProofSystemError::SubProtocolNotReadyToGenerateChallenge( + self.id, + )); + } + self.sp1 + .as_ref() + .unwrap() + .challenge_contribution(&mut writer)?; + self.sp2 + .as_ref() + .unwrap() + .challenge_contribution(&mut writer)?; + Ok(()) + } + + /// Generate responses for both the Schnorr protocols + pub fn gen_proof_contribution( + &mut self, + challenge: &G::ScalarField, + ) -> Result, ProofSystemError> { + if self.sp1.is_none() || self.sp2.is_none() { + return Err(ProofSystemError::SubProtocolNotReadyToGenerateProof( + self.id, + )); + } + Ok(StatementProof::BoundCheckBpp(BoundCheckBppProof { + bpp_proof: self.bpp_proof.take().unwrap(), + sp1: self + .sp1 + .take() + .unwrap() + .gen_proof_contribution_as_struct(challenge)?, + sp2: self + .sp2 + .take() + .unwrap() + .gen_proof_contribution_as_struct(challenge)?, + })) + } + + pub fn verify_proof_contribution( + &self, + challenge: &G::ScalarField, + proof: &BoundCheckBppProof, + comm_key: &[G], + transcript: &mut impl Transcript, + ) -> Result<(), ProofSystemError> { + proof + .bpp_proof + .verify(Self::get_num_bits(self.max), &self.setup_params, transcript)?; + if !proof.check_schnorr_responses_consistency()? { + return Err(ProofSystemError::DifferentResponsesForSchnorrProtocolInBpp( + self.id, + )); + } + let (comm_1, comm_2) = self.get_commitments_to_values(&proof.bpp_proof)?; + + // NOTE: value of id is dummy + let sp1 = SchnorrProtocol::new(10000, comm_key, comm_1); + let sp2 = SchnorrProtocol::new(10000, comm_key, comm_2); + + sp1.verify_proof_contribution_as_struct(challenge, &proof.sp1)?; + sp2.verify_proof_contribution_as_struct(challenge, &proof.sp2) + } + + pub fn compute_challenge_contribution( + min: u64, + max: u64, + comm_key: &[G], + proof: &BoundCheckBppProof, + mut writer: W, + ) -> Result<(), ProofSystemError> { + let mut comms = proof + .bpp_proof + .get_commitments_to_values_given_g(vec![(min, max)], &comm_key[0])?; + let (comm_1, comm_2) = comms.remove(0); + comm_key.serialize_compressed(&mut writer)?; + comm_1.serialize_compressed(&mut writer)?; + proof.sp1.t.serialize_compressed(&mut writer)?; + // Serializing `comm_key` twice to match what happens in `Self::challenge_contribution` + comm_key.serialize_compressed(&mut writer)?; + comm_2.serialize_compressed(&mut writer)?; + proof.sp2.t.serialize_compressed(&mut writer)?; + Ok(()) + } + + fn get_commitments_to_values( + &self, + proof: &ProofArbitraryRange, + ) -> Result<(G, G), ProofSystemError> { + let mut comms = + proof.get_commitments_to_values(vec![(self.min, self.max)], &self.setup_params)?; + Ok(comms.remove(0)) + } + + fn get_num_bits(_max: u64) -> u16 { + 64 + } +} diff --git a/proof_system/src/sub_protocols/bound_check_legogroth16.rs b/proof_system/src/sub_protocols/bound_check_legogroth16.rs index 337db4e6..86b0abc6 100644 --- a/proof_system/src/sub_protocols/bound_check_legogroth16.rs +++ b/proof_system/src/sub_protocols/bound_check_legogroth16.rs @@ -31,7 +31,7 @@ use legogroth16::{ /// Runs the LegoGroth16 protocol for proving bounds of a witness and a Schnorr protocol for proving /// knowledge of the witness committed in the LegoGroth16 proof. #[derive(Clone, Debug, PartialEq)] -pub struct BoundCheckProtocol<'a, E: Pairing> { +pub struct BoundCheckLegoGrothProtocol<'a, E: Pairing> { pub id: usize, pub min: u64, pub max: u64, @@ -43,7 +43,7 @@ pub struct BoundCheckProtocol<'a, E: Pairing> { pub sp: Option>, } -impl<'a, E: Pairing> BoundCheckProtocol<'a, E> { +impl<'a, E: Pairing> BoundCheckLegoGrothProtocol<'a, E> { /// Create an instance of this protocol for the prover. pub fn new_for_prover(id: usize, min: u64, max: u64, proving_key: &'a ProvingKey) -> Self { Self { @@ -104,6 +104,7 @@ impl<'a, E: Pairing> BoundCheckProtocol<'a, E> { self.init_schnorr_protocol(rng, comm_key, message, blinding, v, snark_proof) } + /// Reuse the old randomization and proof to create a new proof. pub fn init_with_old_randomness_and_proof( &mut self, rng: &mut R, @@ -135,6 +136,7 @@ impl<'a, E: Pairing> BoundCheckProtocol<'a, E> { self.init_schnorr_protocol(rng, comm_key, message, blinding, v, snark_proof) } + /// Generate challenge contribution for the Schnorr protocol pub fn challenge_contribution(&self, mut writer: W) -> Result<(), ProofSystemError> { if self.sp.is_none() { return Err(ProofSystemError::SubProtocolNotReadyToGenerateChallenge( @@ -239,13 +241,6 @@ impl<'a, E: Pairing> BoundCheckProtocol<'a, E> { Ok(()) } - pub fn validate_bounds(min: u64, max: u64) -> Result<(), ProofSystemError> { - if max <= min { - return Err(ProofSystemError::BoundCheckMaxNotGreaterThanMin); - } - Ok(()) - } - pub fn validate_verification_key(vk: &VerifyingKey) -> Result<(), ProofSystemError> { if vk.gamma_abc_g1.len() < 4 { return Err(ProofSystemError::LegoGroth16Error( @@ -259,6 +254,8 @@ impl<'a, E: Pairing> BoundCheckProtocol<'a, E> { vec![vk.gamma_abc_g1[1 + 2], vk.eta_gamma_inv_g1] } + /// Initializes a Schnorr protocol to prove the knowledge of committed values in the Pedersen + /// commitment in the Legosnark proof fn init_schnorr_protocol( &mut self, rng: &mut R, @@ -291,7 +288,7 @@ impl<'a, E: Pairing> BoundCheckProtocol<'a, E> { // elements which might not always be true in practice. If the upper bound on the byte-size of the numbers // is known, then the no. of constraints in the circuit can be reduced. -/// Enforce min <= value <= max +/// Enforce min <= value < max #[derive(Clone)] pub struct BoundCheckCircuit { min: Option, @@ -324,8 +321,8 @@ impl ConstraintSynthesizer AllocationMode::Input, )?; - // val strictly less than or equal to max, i.e. val <= max - val.enforce_cmp(&max, Ordering::Less, true)?; + // val strictly less than to max, i.e. val < max + val.enforce_cmp(&max, Ordering::Less, false)?; // val strictly greater than or equal to max, i.e. val >= min val.enforce_cmp(&min, Ordering::Greater, true)?; Ok(()) @@ -333,7 +330,7 @@ impl ConstraintSynthesizer } /// Generate SNARK proving key and verification key for a circuit that checks that given a witness -/// `w` and public inputs `min` and `max`, `min <= w <= max` +/// `w` and public inputs `min` and `max`, `min <= w < max` pub fn generate_snark_srs_bound_check(rng: &mut R) -> Result, ProofSystemError> where E: Pairing, @@ -352,6 +349,7 @@ mod tests { use super::*; use ark_bls12_381::Bls12_381; use ark_std::rand::{prelude::StdRng, SeedableRng}; + use std::time::{Duration, Instant}; type Fr = ::ScalarField; @@ -361,9 +359,11 @@ mod tests { let proving_key = generate_snark_srs_bound_check::(&mut rng).unwrap(); let pvk = PreparedVerifyingKey::from(&proving_key.vk); + let mut proving_time = Duration::default(); + let mut verifying_time = Duration::default(); + for (min, max, value) in [ (100, 200, 100), - (100, 200, 200), (100, 200, 101), (100, 200, 199), (100, 200, 150), @@ -374,10 +374,21 @@ mod tests { value: Some(Fr::from(value)), }; let v = Fr::rand(&mut rng); + + let start = Instant::now(); let proof = create_random_proof(circuit, v, &proving_key, &mut rng).unwrap(); + proving_time += start.elapsed(); + + let start = Instant::now(); verify_proof(&pvk, &proof, &[Fr::from(min), Fr::from(max)]).unwrap(); + verifying_time += start.elapsed(); } + println!( + "For 4 proofs, proving_time={:?} and verifying_time={:?}", + proving_time, verifying_time + ); + let circuit = BoundCheckCircuit { min: Some(Fr::from(100)), max: Some(Fr::from(200)), @@ -386,15 +397,16 @@ mod tests { let v = Fr::rand(&mut rng); assert!(create_random_proof(circuit, v, &proving_key, &mut rng).is_err()); - for (min, max, value) in [(100, 200, 99), (100, 200, 201)] { - // To create valid proof + for (min, max, value) in [(100, 200, 99), (100, 200, 201), (100, 200, 200)] { let circuit = BoundCheckCircuit { - min: Some(Fr::from(1)), - max: Some(Fr::from(1000)), + min: Some(Fr::from(min)), + max: Some(Fr::from(max)), value: Some(Fr::from(value)), }; let v = Fr::rand(&mut rng); + let proof = create_random_proof(circuit, v, &proving_key, &mut rng).unwrap(); + assert!(verify_proof(&pvk, &proof, &[Fr::from(min), Fr::from(max)],).is_err()); } } diff --git a/proof_system/src/sub_protocols/bound_check_smc.rs b/proof_system/src/sub_protocols/bound_check_smc.rs new file mode 100644 index 00000000..818363a2 --- /dev/null +++ b/proof_system/src/sub_protocols/bound_check_smc.rs @@ -0,0 +1,258 @@ +use ark_ec::{pairing::Pairing, AffineRepr}; +use ark_serialize::CanonicalSerialize; +use ark_std::{collections::BTreeMap, io::Write, rand::RngCore, vec, UniformRand}; + +use crate::{ + error::ProofSystemError, + prelude::bound_check_smc::SmcParamsWithPairingAndCommitmentKey, + statement::bound_check_smc::SmcParamsAndCommitmentKey, + statement_proof::{BoundCheckSmcInnerProof, BoundCheckSmcProof, StatementProof}, + sub_protocols::{enforce_and_get_u64, schnorr::SchnorrProtocol, should_use_cls}, +}; +use dock_crypto_utils::randomized_pairing_check::RandomizedPairingChecker; +use smc_range_proof::prelude::{ + CCSArbitraryRangeProofProtocol, CLSRangeProofProtocol, SetMembershipCheckParamsWithPairing, +}; + +#[derive(Clone, Debug, PartialEq)] +pub enum SmcProtocol { + CCS(CCSArbitraryRangeProofProtocol), + CLS(CLSRangeProofProtocol), +} + +/// Runs the set-membership check based protocol for proving bounds of a witness and a Schnorr protocol for proving +/// knowledge of the witness committed in the commitments accompanying the proof. +#[derive(Clone, Debug, PartialEq)] +pub struct BoundCheckSmcProtocol<'a, E: Pairing> { + pub id: usize, + pub min: u64, + pub max: u64, + pub params_and_comm_key: &'a SmcParamsAndCommitmentKey, + pub comm: Option, + pub smc_protocol: Option>, + pub smc_proof: Option>, + pub sp: Option>, +} + +impl<'a, E: Pairing> BoundCheckSmcProtocol<'a, E> { + pub fn new(id: usize, min: u64, max: u64, params: &'a SmcParamsAndCommitmentKey) -> Self { + Self { + id, + min, + max, + params_and_comm_key: params, + comm: None, + smc_protocol: None, + smc_proof: None, + sp: None, + } + } + + pub fn init( + &mut self, + rng: &mut R, + comm_key_as_slice: &'a [E::G1Affine], + message: E::ScalarField, + blinding: Option, + ) -> Result<(), ProofSystemError> { + if self.sp.is_some() { + return Err(ProofSystemError::SubProtocolAlreadyInitialized(self.id)); + } + let msg_as_u64 = enforce_and_get_u64::(&message)?; + let randomness = E::ScalarField::rand(rng); + let params_with_pairing = + SetMembershipCheckParamsWithPairing::from(self.params_and_comm_key.params.clone()); + let comm_key = &self.params_and_comm_key.comm_key; + self.comm = Some(comm_key.commit(&message, &randomness)); + let smc_protocol = if should_use_cls(self.min, self.max) { + let p = CLSRangeProofProtocol::init( + rng, + msg_as_u64, + randomness.clone(), + self.min, + self.max, + comm_key, + params_with_pairing, + )?; + SmcProtocol::CLS(p) + } else { + let p = CCSArbitraryRangeProofProtocol::init( + rng, + msg_as_u64, + randomness.clone(), + self.min, + self.max, + comm_key, + params_with_pairing, + )?; + SmcProtocol::CCS(p) + }; + self.smc_protocol = Some(smc_protocol); + self.init_schnorr_protocol(rng, comm_key_as_slice, message, blinding, randomness) + } + + fn init_schnorr_protocol( + &mut self, + rng: &mut R, + comm_key: &'a [E::G1Affine], + message: E::ScalarField, + blinding: Option, + blinding_for_smc: E::ScalarField, + ) -> Result<(), ProofSystemError> { + let blinding = if blinding.is_none() { + E::ScalarField::rand(rng) + } else { + blinding.unwrap() + }; + let mut blindings = BTreeMap::new(); + blindings.insert(0, blinding); + + // NOTE: value of id is dummy + let mut sp = SchnorrProtocol::new(10000, &comm_key, self.comm.unwrap()); + sp.init(rng, blindings.clone(), vec![message, blinding_for_smc])?; + self.sp = Some(sp); + Ok(()) + } + + pub fn challenge_contribution(&self, mut writer: W) -> Result<(), ProofSystemError> { + if self.sp.is_none() { + return Err(ProofSystemError::SubProtocolNotReadyToGenerateChallenge( + self.id, + )); + } + let params_with_pairing = + SetMembershipCheckParamsWithPairing::from(self.params_and_comm_key.params.clone()); + let comm_key = &self.params_and_comm_key.comm_key; + match &self.smc_protocol { + Some(SmcProtocol::CCS(c)) => c.challenge_contribution( + self.comm.as_ref().unwrap(), + comm_key, + params_with_pairing, + &mut writer, + )?, + Some(SmcProtocol::CLS(c)) => c.challenge_contribution( + self.comm.as_ref().unwrap(), + comm_key, + params_with_pairing, + &mut writer, + )?, + None => { + return Err(ProofSystemError::SubProtocolNotReadyToGenerateChallenge( + self.id, + )) + } + } + self.sp + .as_ref() + .unwrap() + .challenge_contribution(&mut writer)?; + Ok(()) + } + + pub fn gen_proof_contribution( + &mut self, + challenge: &E::ScalarField, + ) -> Result, ProofSystemError> { + if self.sp.is_none() { + return Err(ProofSystemError::SubProtocolNotReadyToGenerateProof( + self.id, + )); + } + let proof = match self.smc_protocol.take().unwrap() { + SmcProtocol::CCS(c) => { + let p = c.gen_proof(challenge); + BoundCheckSmcInnerProof::CCS(p) + } + SmcProtocol::CLS(c) => { + let p = c.gen_proof(challenge); + BoundCheckSmcInnerProof::CLS(p) + } + }; + Ok(StatementProof::BoundCheckSmc(BoundCheckSmcProof { + proof, + comm: self.comm.take().unwrap(), + sp: self + .sp + .take() + .unwrap() + .gen_proof_contribution_as_struct(challenge)?, + })) + } + + pub fn verify_proof_contribution( + &self, + challenge: &E::ScalarField, + proof: &BoundCheckSmcProof, + comm_key_as_slice: &[E::G1Affine], + params: SmcParamsWithPairingAndCommitmentKey, + pairing_checker: &mut Option>, + ) -> Result<(), ProofSystemError> { + let comm_key = &self.params_and_comm_key.comm_key; + match &proof.proof { + BoundCheckSmcInnerProof::CCS(c) => match pairing_checker { + Some(pc) => c.verify_given_randomized_pairing_checker( + &proof.comm, + challenge, + self.min, + self.max, + comm_key, + params.params, + pc, + )?, + None => c.verify( + &proof.comm, + challenge, + self.min, + self.max, + comm_key, + params.params, + )?, + }, + BoundCheckSmcInnerProof::CLS(c) => match pairing_checker { + Some(pc) => c.verify_given_randomized_pairing_checker( + &proof.comm, + challenge, + self.min, + self.max, + comm_key, + params.params, + pc, + )?, + None => c.verify( + &proof.comm, + challenge, + self.min, + self.max, + comm_key, + params.params, + )?, + }, + } + + // NOTE: value of id is dummy + let sp = SchnorrProtocol::new(10000, comm_key_as_slice, proof.comm); + + sp.verify_proof_contribution_as_struct(challenge, &proof.sp) + } + + pub fn compute_challenge_contribution( + comm_key_as_slice: &[E::G1Affine], + proof: &BoundCheckSmcProof, + params: SmcParamsWithPairingAndCommitmentKey, + mut writer: W, + ) -> Result<(), ProofSystemError> { + let comm_key = ¶ms.comm_key; + match &proof.proof { + BoundCheckSmcInnerProof::CCS(c) => { + c.challenge_contribution(&proof.comm, comm_key, params.params, &mut writer)? + } + BoundCheckSmcInnerProof::CLS(c) => { + c.challenge_contribution(&proof.comm, comm_key, params.params, &mut writer)? + } + } + comm_key_as_slice.serialize_compressed(&mut writer)?; + proof.comm.serialize_compressed(&mut writer)?; + proof.sp.t.serialize_compressed(&mut writer)?; + Ok(()) + } +} diff --git a/proof_system/src/sub_protocols/bound_check_smc_with_kv.rs b/proof_system/src/sub_protocols/bound_check_smc_with_kv.rs new file mode 100644 index 00000000..c7325c82 --- /dev/null +++ b/proof_system/src/sub_protocols/bound_check_smc_with_kv.rs @@ -0,0 +1,269 @@ +use crate::{ + error::ProofSystemError, + prelude::bound_check_smc_with_kv::SmcParamsAndCommitmentKeyAndSecretKey, + statement::bound_check_smc::SmcParamsAndCommitmentKey, + statement_proof::{BoundCheckSmcWithKVInnerProof, BoundCheckSmcWithKVProof, StatementProof}, + sub_protocols::{enforce_and_get_u64, schnorr::SchnorrProtocol, should_use_cls}, +}; +use ark_ec::{pairing::Pairing, AffineRepr}; +use ark_serialize::CanonicalSerialize; +use ark_std::{collections::BTreeMap, io::Write, rand::RngCore, vec, UniformRand}; +use smc_range_proof::{ + ccs_range_proof::kv_arbitrary_range::CCSArbitraryRangeProofWithKVProtocol, + prelude::CLSRangeProofWithKVProtocol, +}; + +#[derive(Clone, Debug, PartialEq)] +pub enum SmcProtocolWithKV { + CCS(CCSArbitraryRangeProofWithKVProtocol), + CLS(CLSRangeProofWithKVProtocol), +} + +/// Runs the set-membership check based protocol with keyed-verification for proving bounds of a witness and a Schnorr protocol for proving +/// knowledge of the witness committed in the commitments accompanying the proof. +#[derive(Clone, Debug, PartialEq)] +pub struct BoundCheckSmcWithKVProtocol<'a, E: Pairing> { + pub id: usize, + pub min: u64, + pub max: u64, + pub params_and_comm_key: Option<&'a SmcParamsAndCommitmentKey>, + pub params_and_comm_key_and_sk: Option<&'a SmcParamsAndCommitmentKeyAndSecretKey>, + pub comm: Option, + pub smc_protocol: Option>, + pub smc_proof: Option>, + pub sp: Option>, +} + +impl<'a, E: Pairing> BoundCheckSmcWithKVProtocol<'a, E> { + pub fn new_for_prover( + id: usize, + min: u64, + max: u64, + params: &'a SmcParamsAndCommitmentKey, + ) -> Self { + Self { + id, + min, + max, + params_and_comm_key: Some(params), + params_and_comm_key_and_sk: None, + comm: None, + smc_protocol: None, + smc_proof: None, + sp: None, + } + } + + pub fn new_for_verifier( + id: usize, + min: u64, + max: u64, + params: &'a SmcParamsAndCommitmentKeyAndSecretKey, + ) -> Self { + Self { + id, + min, + max, + params_and_comm_key: None, + params_and_comm_key_and_sk: Some(params), + comm: None, + smc_protocol: None, + smc_proof: None, + sp: None, + } + } + + pub fn init( + &mut self, + rng: &mut R, + comm_key_as_slice: &'a [E::G1Affine], + message: E::ScalarField, + blinding: Option, + ) -> Result<(), ProofSystemError> { + if self.sp.is_some() { + return Err(ProofSystemError::SubProtocolAlreadyInitialized(self.id)); + } + let params = self + .params_and_comm_key + .ok_or(ProofSystemError::SmcParamsNotProvided)?; + let msg_as_u64 = enforce_and_get_u64::(&message)?; + let randomness = E::ScalarField::rand(rng); + let comm_key = ¶ms.comm_key; + self.comm = Some(comm_key.commit(&message, &randomness)); + let smc_protocol = if should_use_cls(self.min, self.max) { + let p = CLSRangeProofWithKVProtocol::init( + rng, + msg_as_u64, + randomness.clone(), + self.min, + self.max, + comm_key, + ¶ms.params, + )?; + SmcProtocolWithKV::CLS(p) + } else { + let p = CCSArbitraryRangeProofWithKVProtocol::init( + rng, + msg_as_u64, + randomness.clone(), + self.min, + self.max, + comm_key, + ¶ms.params, + )?; + SmcProtocolWithKV::CCS(p) + }; + self.smc_protocol = Some(smc_protocol); + self.init_schnorr_protocol(rng, comm_key_as_slice, message, blinding, randomness) + } + + fn init_schnorr_protocol( + &mut self, + rng: &mut R, + comm_key: &'a [E::G1Affine], + message: E::ScalarField, + blinding: Option, + blinding_for_smc: E::ScalarField, + ) -> Result<(), ProofSystemError> { + let blinding = if blinding.is_none() { + E::ScalarField::rand(rng) + } else { + blinding.unwrap() + }; + let mut blindings = BTreeMap::new(); + blindings.insert(0, blinding); + + // NOTE: value of id is dummy + let mut sp = SchnorrProtocol::new(10000, &comm_key, self.comm.unwrap()); + sp.init(rng, blindings.clone(), vec![message, blinding_for_smc])?; + self.sp = Some(sp); + Ok(()) + } + + pub fn challenge_contribution(&self, mut writer: W) -> Result<(), ProofSystemError> { + if self.sp.is_none() { + return Err(ProofSystemError::SubProtocolNotReadyToGenerateChallenge( + self.id, + )); + } + let params = self + .params_and_comm_key + .ok_or(ProofSystemError::SmcParamsNotProvided)?; + let comm_key = ¶ms.comm_key; + match &self.smc_protocol { + Some(SmcProtocolWithKV::CCS(c)) => c.challenge_contribution( + self.comm.as_ref().unwrap(), + comm_key, + ¶ms.params, + &mut writer, + )?, + Some(SmcProtocolWithKV::CLS(c)) => c.challenge_contribution( + self.comm.as_ref().unwrap(), + comm_key, + ¶ms.params, + &mut writer, + )?, + None => { + return Err(ProofSystemError::SubProtocolNotReadyToGenerateChallenge( + self.id, + )) + } + } + self.sp + .as_ref() + .unwrap() + .challenge_contribution(&mut writer)?; + Ok(()) + } + + pub fn gen_proof_contribution( + &mut self, + challenge: &E::ScalarField, + ) -> Result, ProofSystemError> { + if self.sp.is_none() { + return Err(ProofSystemError::SubProtocolNotReadyToGenerateProof( + self.id, + )); + } + let proof = match self.smc_protocol.take().unwrap() { + SmcProtocolWithKV::CCS(c) => { + let p = c.gen_proof(challenge); + BoundCheckSmcWithKVInnerProof::CCS(p) + } + SmcProtocolWithKV::CLS(c) => { + let p = c.gen_proof(challenge); + BoundCheckSmcWithKVInnerProof::CLS(p) + } + }; + Ok(StatementProof::BoundCheckSmcWithKV( + BoundCheckSmcWithKVProof { + proof, + comm: self.comm.take().unwrap(), + sp: self + .sp + .take() + .unwrap() + .gen_proof_contribution_as_struct(challenge)?, + }, + )) + } + + pub fn verify_proof_contribution( + &self, + challenge: &E::ScalarField, + proof: &BoundCheckSmcWithKVProof, + comm_key_as_slice: &[E::G1Affine], + ) -> Result<(), ProofSystemError> { + let params = self + .params_and_comm_key_and_sk + .ok_or(ProofSystemError::SmcParamsNotProvided)?; + let comm_key = params.get_comm_key(); + match &proof.proof { + BoundCheckSmcWithKVInnerProof::CCS(c) => c.verify( + &proof.comm, + challenge, + self.min, + self.max, + comm_key, + params.get_smc_params(), + ¶ms.sk, + )?, + BoundCheckSmcWithKVInnerProof::CLS(c) => c.verify( + &proof.comm, + challenge, + self.min, + self.max, + comm_key, + params.get_smc_params(), + ¶ms.sk, + )?, + } + + // NOTE: value of id is dummy + let sp = SchnorrProtocol::new(10000, comm_key_as_slice, proof.comm); + + sp.verify_proof_contribution_as_struct(challenge, &proof.sp) + } + + pub fn compute_challenge_contribution( + comm_key_as_slice: &[E::G1Affine], + proof: &BoundCheckSmcWithKVProof, + params: &SmcParamsAndCommitmentKeyAndSecretKey, + mut writer: W, + ) -> Result<(), ProofSystemError> { + let comm_key = params.get_comm_key(); + let get_smc_params = params.get_smc_params(); + match &proof.proof { + BoundCheckSmcWithKVInnerProof::CCS(c) => { + c.challenge_contribution(&proof.comm, comm_key, get_smc_params, &mut writer)? + } + BoundCheckSmcWithKVInnerProof::CLS(c) => { + c.challenge_contribution(&proof.comm, comm_key, get_smc_params, &mut writer)? + } + } + comm_key_as_slice.serialize_compressed(&mut writer)?; + proof.comm.serialize_compressed(&mut writer)?; + proof.sp.t.serialize_compressed(&mut writer)?; + Ok(()) + } +} diff --git a/proof_system/src/sub_protocols/mod.rs b/proof_system/src/sub_protocols/mod.rs index bc0fc249..1bf92efd 100644 --- a/proof_system/src/sub_protocols/mod.rs +++ b/proof_system/src/sub_protocols/mod.rs @@ -2,7 +2,10 @@ pub mod accumulator; #[macro_use] pub mod bbs_plus; pub mod bbs_23; +pub mod bound_check_bpp; pub mod bound_check_legogroth16; +pub mod bound_check_smc; +pub mod bound_check_smc_with_kv; pub mod ps_signature; pub mod r1cs_legogorth16; pub mod saver; @@ -12,13 +15,18 @@ use core::borrow::Borrow; use crate::error::ProofSystemError; use ark_ec::{pairing::Pairing, AffineRepr}; -use ark_std::io::Write; +use ark_ff::PrimeField; +use ark_std::{format, io::Write}; use itertools::{EitherOrBoth, Itertools}; use crate::{ statement_proof::StatementProof, sub_protocols::{ - bound_check_legogroth16::BoundCheckProtocol, r1cs_legogorth16::R1CSLegogroth16Protocol, + bound_check_bpp::BoundCheckBppProtocol, + bound_check_legogroth16::BoundCheckLegoGrothProtocol, + bound_check_smc::BoundCheckSmcProtocol, + bound_check_smc_with_kv::BoundCheckSmcWithKVProtocol, + r1cs_legogorth16::R1CSLegogroth16Protocol, }, }; use accumulator::{AccumulatorMembershipSubProtocol, AccumulatorNonMembershipSubProtocol}; @@ -35,11 +43,14 @@ pub enum SubProtocol<'a, E: Pairing, G: AffineRepr> { /// For verifiable encryption using SAVER Saver(self::saver::SaverProtocol<'a, E>), /// For range proof using LegoGroth16 - BoundCheckProtocol(BoundCheckProtocol<'a, E>), + BoundCheckLegoGroth16(BoundCheckLegoGrothProtocol<'a, E>), R1CSLegogroth16Protocol(R1CSLegogroth16Protocol<'a, E>), - PSSignaturePoK(self::ps_signature::PSSignaturePoK<'a, E>), + PSSignaturePoK(ps_signature::PSSignaturePoK<'a, E>), /// For BBS signature in group G1 - PoKBBSSignature23G1(self::bbs_23::PoKBBSSigG1SubProtocol<'a, E>), + PoKBBSSignature23G1(bbs_23::PoKBBSSigG1SubProtocol<'a, E>), + BoundCheckBpp(BoundCheckBppProtocol<'a, G>), + BoundCheckSmc(BoundCheckSmcProtocol<'a, E>), + BoundCheckSmcWithKV(BoundCheckSmcWithKVProtocol<'a, E>), } macro_rules! delegate { @@ -51,10 +62,13 @@ macro_rules! delegate { AccumulatorNonMembership, PoKDiscreteLogs, Saver, - BoundCheckProtocol, + BoundCheckLegoGroth16, R1CSLegogroth16Protocol, PSSignaturePoK, - PoKBBSSignature23G1 + PoKBBSSignature23G1, + BoundCheckBpp, + BoundCheckSmc, + BoundCheckSmcWithKV : $($tt)+ } }}; @@ -119,3 +133,31 @@ fn merge_indexed_messages_with_blindings<'a, M, B, R: 'a>( .take_while(Option::is_some) .flatten() } + +pub fn validate_bounds(min: u64, max: u64) -> Result<(), ProofSystemError> { + if max <= min { + return Err(ProofSystemError::BoundCheckMaxNotGreaterThanMin); + } + Ok(()) +} + +pub fn enforce_and_get_u64(val: &F) -> Result { + let m = val.into_bigint(); + let limbs: &[u64] = m.as_ref(); + for i in 1..limbs.len() { + if limbs[i] != 0 { + return Err(ProofSystemError::UnsupportedValue(format!( + "Only supports 64 bit values Bulletproofs++ range proof but found {}", + val + ))); + } + } + Ok(limbs[0]) +} + +pub fn should_use_cls(min: u64, max: u64) -> bool { + assert!(max > min); + let diff = max - min; + let bits = diff.ilog2(); + bits < 20 +} diff --git a/proof_system/src/sub_protocols/saver.rs b/proof_system/src/sub_protocols/saver.rs index f7a90582..524a0ee8 100644 --- a/proof_system/src/sub_protocols/saver.rs +++ b/proof_system/src/sub_protocols/saver.rs @@ -441,6 +441,8 @@ impl<'a, E: Pairing> SaverProtocol<'a, E> { (ck_comm_chunks, ck_comm_combined) } + /// Initialize 3 Schnorr proof of knowledge protocols to prove the knowledge of committed value + /// in ciphertext fn init_schnorr_protocols( &mut self, rng: &mut R, diff --git a/proof_system/src/verifier.rs b/proof_system/src/verifier.rs index 2c8b5806..5922cdd4 100644 --- a/proof_system/src/verifier.rs +++ b/proof_system/src/verifier.rs @@ -8,7 +8,10 @@ use crate::{ accumulator::{AccumulatorMembershipSubProtocol, AccumulatorNonMembershipSubProtocol}, bbs_23::PoKBBSSigG1SubProtocol as PoKBBSSig23G1SubProtocol, bbs_plus::PoKBBSSigG1SubProtocol, - bound_check_legogroth16::BoundCheckProtocol, + bound_check_bpp::BoundCheckBppProtocol, + bound_check_legogroth16::BoundCheckLegoGrothProtocol, + bound_check_smc::BoundCheckSmcProtocol, + bound_check_smc_with_kv::BoundCheckSmcWithKVProtocol, ps_signature::PSSignaturePoK, r1cs_legogorth16::R1CSLegogroth16Protocol, saver::SaverProtocol, @@ -34,6 +37,54 @@ pub struct VerifierConfig { pub use_lazy_randomized_pairing_checks: Option, } +macro_rules! err_incompat_proof { + ($s_idx:ident, $s: ident, $proof: ident) => { + return Err(ProofSystemError::ProofIncompatibleWithStatement( + $s_idx, + format!("{:?}", $proof), + format!("{:?}", $s), + )) + }; +} + +macro_rules! check_resp_for_equalities { + ($witness_equalities:ident, $s_idx: ident, $p: ident, $func_name: ident, $self: ident, $responses_for_equalities: ident) => { + for i in 0..$witness_equalities.len() { + // Check witness equalities for this statement. As there is only 1 witness + // of interest, its index is always 0 + if $witness_equalities[i].contains(&($s_idx, 0)) { + let resp = $p.$func_name(); + $self::check_response_for_equality( + $s_idx, + 0, + i, + &mut $responses_for_equalities, + resp, + )?; + } + } + }; +} + +macro_rules! check_resp_for_equalities_with_err { + ($witness_equalities:ident, $s_idx: ident, $p: ident, $func_name: ident, $self: ident, $responses_for_equalities: ident) => { + for i in 0..$witness_equalities.len() { + // Check witness equalities for this statement. As there is only 1 witness + // of interest, its index is always 0 + if $witness_equalities[i].contains(&($s_idx, 0)) { + let resp = $p.$func_name()?; + $self::check_response_for_equality( + $s_idx, + 0, + i, + &mut $responses_for_equalities, + resp, + )?; + } + } + }; +} + impl Proof where E: Pairing, @@ -74,6 +125,9 @@ where )); } + // TODO: Use this for all sub-proofs and not just Bulletproofs++ + let mut transcript = new_merlin_transcript(b"composite-proof"); + // TODO: Check SNARK SRSs compatible when aggregating and statement proof compatible with proof spec when aggregating let aggregate_snarks = @@ -105,8 +159,14 @@ where } // Prepare commitment keys for running Schnorr protocols of all statements. - let (bound_check_comm, ek_comm, chunked_comm, r1cs_comm_keys) = - proof_spec.derive_commitment_keys()?; + let ( + bound_check_comm, + ek_comm, + chunked_comm, + r1cs_comm_keys, + bound_check_bpp_comm, + bound_check_smc_comm, + ) = proof_spec.derive_commitment_keys()?; // Prepare required parameters for pairings let ( @@ -121,6 +181,7 @@ where derived_ps_param, derived_ps_pk, derived_bbs_param, + derived_smc_param, ) = proof_spec.derive_prepared_parameters()?; // All the distinct equalities in `ProofSpec` @@ -182,13 +243,7 @@ where &mut challenge_bytes, )?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::PoKBBSSignature23G1(s) => match proof { StatementProof::PoKBBSSignature23G1(p) => { @@ -216,30 +271,18 @@ where &mut challenge_bytes, )?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::AccumulatorMembership(s) => match proof { StatementProof::AccumulatorMembership(p) => { - for i in 0..witness_equalities.len() { - // Check witness equalities for this statement. As there is only 1 witness - // of interest, i.e. the accumulator member, its index is always 0 - if witness_equalities[i].contains(&(s_idx, 0)) { - let resp = p.get_schnorr_response_for_element(); - Self::check_response_for_equality( - s_idx, - 0, - i, - &mut responses_for_equalities, - resp, - )?; - } - } + check_resp_for_equalities!( + witness_equalities, + s_idx, + p, + get_schnorr_response_for_element, + Self, + responses_for_equalities + ); let params = s.get_params(&proof_spec.setup_params, s_idx)?; let pk = s.get_public_key(&proof_spec.setup_params, s_idx)?; let prk = s.get_proving_key(&proof_spec.setup_params, s_idx)?; @@ -251,30 +294,18 @@ where &mut challenge_bytes, )?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::AccumulatorNonMembership(s) => match proof { StatementProof::AccumulatorNonMembership(p) => { - // Check witness equalities for this statement. As there is only 1 witness - // of interest, i.e. the accumulator non-member, its index is always 0 - for i in 0..witness_equalities.len() { - if witness_equalities[i].contains(&(s_idx, 0)) { - let resp = p.get_schnorr_response_for_element(); - Self::check_response_for_equality( - s_idx, - 0, - i, - &mut responses_for_equalities, - resp, - )?; - } - } + check_resp_for_equalities!( + witness_equalities, + s_idx, + p, + get_schnorr_response_for_element, + Self, + responses_for_equalities + ); let params = s.get_params(&proof_spec.setup_params, s_idx)?; let pk = s.get_public_key(&proof_spec.setup_params, s_idx)?; let prk = s.get_proving_key(&proof_spec.setup_params, s_idx)?; @@ -286,13 +317,7 @@ where &mut challenge_bytes, )?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::PedersenCommitment(s) => match proof { StatementProof::PedersenCommitment(p) => { @@ -320,28 +345,18 @@ where &mut challenge_bytes, )?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::SaverVerifier(s) => match proof { StatementProof::Saver(p) => { - for i in 0..witness_equalities.len() { - if witness_equalities[i].contains(&(s_idx, 0)) { - let resp = p.get_schnorr_response_for_combined_message()?; - Self::check_response_for_equality( - s_idx, - 0, - i, - &mut responses_for_equalities, - resp, - )?; - } - } + check_resp_for_equalities_with_err!( + witness_equalities, + s_idx, + p, + get_schnorr_response_for_combined_message, + Self, + responses_for_equalities + ); let ek_comm_key = ek_comm.get(s_idx).unwrap(); let cc_keys = chunked_comm.get(s_idx).unwrap(); SaverProtocol::compute_challenge_contribution( @@ -353,18 +368,14 @@ where )?; } StatementProof::SaverWithAggregation(p) => { - for i in 0..witness_equalities.len() { - if witness_equalities[i].contains(&(s_idx, 0)) { - let resp = p.get_schnorr_response_for_combined_message()?; - Self::check_response_for_equality( - s_idx, - 0, - i, - &mut responses_for_equalities, - resp, - )?; - } - } + check_resp_for_equalities_with_err!( + witness_equalities, + s_idx, + p, + get_schnorr_response_for_combined_message, + Self, + responses_for_equalities + ); let ek_comm_key = ek_comm.get(s_idx).unwrap(); let cc_keys = chunked_comm.get(s_idx).unwrap(); SaverProtocol::compute_challenge_contribution_when_aggregating_snark( @@ -375,64 +386,44 @@ where &mut challenge_bytes, )?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::BoundCheckLegoGroth16Verifier(s) => match proof { StatementProof::BoundCheckLegoGroth16(p) => { - for i in 0..witness_equalities.len() { - if witness_equalities[i].contains(&(s_idx, 0)) { - let resp = p.get_schnorr_response_for_message()?; - Self::check_response_for_equality( - s_idx, - 0, - i, - &mut responses_for_equalities, - resp, - )?; - } - } + check_resp_for_equalities_with_err!( + witness_equalities, + s_idx, + p, + get_schnorr_response_for_message, + Self, + responses_for_equalities + ); let comm_key = bound_check_comm.get(s_idx).unwrap(); - BoundCheckProtocol::compute_challenge_contribution( + BoundCheckLegoGrothProtocol::compute_challenge_contribution( comm_key, p, &mut challenge_bytes, )?; } StatementProof::BoundCheckLegoGroth16WithAggregation(p) => { - for i in 0..witness_equalities.len() { - if witness_equalities[i].contains(&(s_idx, 0)) { - let resp = p.get_schnorr_response_for_message()?; - Self::check_response_for_equality( - s_idx, - 0, - i, - &mut responses_for_equalities, - resp, - )?; - } - } + check_resp_for_equalities_with_err!( + witness_equalities, + s_idx, + p, + get_schnorr_response_for_message, + Self, + responses_for_equalities + ); let comm_key = bound_check_comm.get(s_idx).unwrap(); - BoundCheckProtocol::compute_challenge_contribution_when_aggregating_snark( + BoundCheckLegoGrothProtocol::compute_challenge_contribution_when_aggregating_snark( comm_key, p, &mut challenge_bytes, )?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::R1CSCircomVerifier(s) => { let verifying_key = s.get_verifying_key(&proof_spec.setup_params, s_idx)?; @@ -481,13 +472,7 @@ where &mut challenge_bytes, )?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), } } Statement::PoKPSSignature(s) => match proof { @@ -517,13 +502,71 @@ where } p.challenge_contribution(&mut challenge_bytes, pk, sig_params)?; } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( + _ => err_incompat_proof!(s_idx, s, proof), + }, + Statement::BoundCheckBpp(s) => match proof { + StatementProof::BoundCheckBpp(p) => { + check_resp_for_equalities_with_err!( + witness_equalities, s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) + p, + get_schnorr_response_for_message, + Self, + responses_for_equalities + ); + + let comm_key = bound_check_bpp_comm.get(s_idx).unwrap(); + BoundCheckBppProtocol::::compute_challenge_contribution( + s.min, + s.max, + comm_key.as_slice(), + p, + &mut challenge_bytes, + )?; } + _ => err_incompat_proof!(s_idx, s, proof), + }, + Statement::BoundCheckSmc(s) => match proof { + StatementProof::BoundCheckSmc(p) => { + check_resp_for_equalities_with_err!( + witness_equalities, + s_idx, + p, + get_schnorr_response_for_message, + Self, + responses_for_equalities + ); + + let comm_key_slice = bound_check_smc_comm.get(s_idx).unwrap(); + BoundCheckSmcProtocol::compute_challenge_contribution( + comm_key_slice.as_slice(), + p, + derived_smc_param.get(s_idx).unwrap().clone(), + &mut challenge_bytes, + )?; + } + _ => err_incompat_proof!(s_idx, s, proof), + }, + Statement::BoundCheckSmcWithKVVerifier(s) => match proof { + StatementProof::BoundCheckSmcWithKV(p) => { + check_resp_for_equalities_with_err!( + witness_equalities, + s_idx, + p, + get_schnorr_response_for_message, + Self, + responses_for_equalities + ); + + let comm_key_slice = bound_check_smc_comm.get(s_idx).unwrap(); + BoundCheckSmcWithKVProtocol::compute_challenge_contribution( + comm_key_slice.as_slice(), + p, + s.get_params_and_comm_key_and_sk(&proof_spec.setup_params, s_idx)?, + &mut challenge_bytes, + )? + } + _ => err_incompat_proof!(s_idx, s, proof), }, _ => return Err(ProofSystemError::InvalidStatement), } @@ -574,13 +617,7 @@ where &mut pairing_checker, )? } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::PoKBBSSignature23G1(s) => match proof { StatementProof::PoKBBSSignature23G1(ref p) => { @@ -600,13 +637,7 @@ where &mut pairing_checker, )? } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::AccumulatorMembership(s) => match proof { StatementProof::AccumulatorMembership(ref p) => { @@ -628,13 +659,7 @@ where &mut pairing_checker, )? } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::AccumulatorNonMembership(s) => match proof { StatementProof::AccumulatorNonMembership(ref p) => { @@ -656,13 +681,7 @@ where &mut pairing_checker, )? } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::PedersenCommitment(s) => match proof { StatementProof::PedersenCommitment(ref _p) => { @@ -670,13 +689,7 @@ where let sp = SchnorrProtocol::new(s_idx, comm_key, s.commitment); sp.verify_proof_contribution(&challenge, &proof)? } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( - s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) - } + _ => err_incompat_proof!(s_idx, s, proof), }, Statement::SaverVerifier(s) => { let enc_gens = s.get_encryption_gens(&proof_spec.setup_params, s_idx)?; @@ -731,8 +744,12 @@ where } Statement::BoundCheckLegoGroth16Verifier(s) => { let verifying_key = s.get_verifying_key(&proof_spec.setup_params, s_idx)?; - let sp = - BoundCheckProtocol::new_for_verifier(s_idx, s.min, s.max, verifying_key); + let sp = BoundCheckLegoGrothProtocol::new_for_verifier( + s_idx, + s.min, + s.max, + verifying_key, + ); let comm_key = bound_check_comm.get(s_idx).unwrap(); match proof { StatementProof::BoundCheckLegoGroth16(ref bc_proof) => sp @@ -817,13 +834,56 @@ where &mut pairing_checker, )? } - _ => { - return Err(ProofSystemError::ProofIncompatibleWithStatement( + _ => err_incompat_proof!(s_idx, s, proof), + }, + Statement::BoundCheckBpp(s) => match proof { + StatementProof::BoundCheckBpp(ref bc_proof) => { + let setup_params = s.get_setup_params(&proof_spec.setup_params, s_idx)?; + let sp = BoundCheckBppProtocol::new(s_idx, s.min, s.max, setup_params); + let comm_key = bound_check_bpp_comm.get(s_idx).unwrap(); + sp.verify_proof_contribution( + &challenge, + bc_proof, + comm_key.as_slice(), + &mut transcript, + )? + } + _ => err_incompat_proof!(s_idx, s, proof), + }, + Statement::BoundCheckSmc(s) => match proof { + StatementProof::BoundCheckSmc(ref bc_proof) => { + let setup_params = + s.get_params_and_comm_key(&proof_spec.setup_params, s_idx)?; + let sp = BoundCheckSmcProtocol::new(s_idx, s.min, s.max, setup_params); + let comm_key_slice = bound_check_smc_comm.get(s_idx).unwrap(); + sp.verify_proof_contribution( + &challenge, + bc_proof, + comm_key_slice.as_slice(), + derived_smc_param.get(s_idx).unwrap().clone(), + &mut pairing_checker, + )? + } + _ => err_incompat_proof!(s_idx, s, proof), + }, + Statement::BoundCheckSmcWithKVVerifier(s) => match proof { + StatementProof::BoundCheckSmcWithKV(ref bc_proof) => { + let setup_params = + s.get_params_and_comm_key_and_sk(&proof_spec.setup_params, s_idx)?; + let sp = BoundCheckSmcWithKVProtocol::new_for_verifier( s_idx, - format!("{:?}", proof), - format!("{:?}", s), - )) + s.min, + s.max, + setup_params, + ); + let comm_key_slice = bound_check_smc_comm.get(s_idx).unwrap(); + sp.verify_proof_contribution( + &challenge, + bc_proof, + comm_key_slice.as_slice(), + )? } + _ => err_incompat_proof!(s_idx, s, proof), }, _ => return Err(ProofSystemError::InvalidStatement), } diff --git a/proof_system/src/witness.rs b/proof_system/src/witness.rs index c9d18393..9881d51e 100644 --- a/proof_system/src/witness.rs +++ b/proof_system/src/witness.rs @@ -30,6 +30,10 @@ pub enum Witness { R1CSLegoGroth16(R1CSCircomWitness), PoKPSSignature(PoKPSSignature), PoKBBSSignature23G1(PoKBBSSignature23G1), + /// For bound check using Bulletproofs++ protocol. Its the message whose bounds are checked + BoundCheckBpp(#[serde_as(as = "ArkObjectBytes")] E::ScalarField), + BoundCheckSmc(#[serde_as(as = "ArkObjectBytes")] E::ScalarField), + BoundCheckSmcWithKV(#[serde_as(as = "ArkObjectBytes")] E::ScalarField), } macro_rules! delegate { @@ -44,7 +48,10 @@ macro_rules! delegate { BoundCheckLegoGroth16, R1CSLegoGroth16, PoKPSSignature, - PoKBBSSignature23G1 + PoKBBSSignature23G1, + BoundCheckBpp, + BoundCheckSmc, + BoundCheckSmcWithKV : $($tt)+ } }} @@ -62,7 +69,10 @@ macro_rules! delegate_reverse { BoundCheckLegoGroth16, R1CSLegoGroth16, PoKPSSignature, - PoKBBSSignature23G1 + PoKBBSSignature23G1, + BoundCheckBpp, + BoundCheckSmc, + BoundCheckSmcWithKV : $($tt)+ } diff --git a/proof_system/tests/bound_check_bpp.rs b/proof_system/tests/bound_check_bpp.rs new file mode 100644 index 00000000..42f1064c --- /dev/null +++ b/proof_system/tests/bound_check_bpp.rs @@ -0,0 +1,179 @@ +use ark_bls12_381::{Bls12_381, G1Affine}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{ + collections::{BTreeMap, BTreeSet}, + rand::{prelude::StdRng, SeedableRng}, +}; +use bbs_plus::{prelude::KeypairG2, setup::SignatureParamsG1, signature::SignatureG1}; +use blake2::Blake2b512; +use bulletproofs_plus_plus::prelude::SetupParams; +use std::time::Instant; + +use proof_system::{ + prelude::{EqualWitnesses, MetaStatements, ProofSpec, Witness, WitnessRef, Witnesses}, + statement::{ + bbs_plus::PoKBBSSignatureG1 as PoKSignatureBBSG1Stmt, + bound_check_bpp::BoundCheckBpp as BoundCheckStmt, Statements, + }, + witness::PoKBBSSignatureG1 as PoKSignatureBBSG1Wit, +}; + +use test_utils::{bbs::*, test_serialization, Fr, ProofG1}; + +#[test] +fn pok_of_bbs_plus_sig_and_bounded_message_using_bulletproofs_plus_plus() { + // Prove knowledge of BBS+ signature and a specific message satisfies some bounds i.e. min <= message <= max. + // Here message set as min and them max + let mut rng = StdRng::seed_from_u64(0u64); + + let min = 100; + let max = 200; + let msg_count = 5; + let msgs = (0..msg_count) + .map(|i| Fr::from(min + 1 + i as u64)) + .collect::>(); + + let (sig_params, sig_keypair, sig) = bbs_plus_sig_setup_given_messages(&mut rng, &msgs); + + let bpp_setup_params = + SetupParams::::new_for_arbitrary_range_proof::(b"test", 2, 64, 1); + + fn check( + rng: &mut StdRng, + min: u64, + max: u64, + msg_idx: usize, + msg: Fr, + msgs: Vec, + sig_params: SignatureParamsG1, + sig_keypair: KeypairG2, + sig: SignatureG1, + bpp_setup_params: SetupParams, + valid_proof: bool, + ) { + let mut prover_statements = Statements::new(); + prover_statements.add(PoKSignatureBBSG1Stmt::new_statement_from_params( + sig_params.clone(), + sig_keypair.public_key.clone(), + BTreeMap::new(), + )); + prover_statements.add( + BoundCheckStmt::new_statement_from_params(min, max, bpp_setup_params.clone()).unwrap(), + ); + + let mut meta_statements = MetaStatements::new(); + meta_statements.add_witness_equality(EqualWitnesses( + vec![(0, msg_idx), (1, 0)] + .into_iter() + .collect::>(), + )); + + if valid_proof { + test_serialization!(Statements, prover_statements); + test_serialization!(MetaStatements, meta_statements); + } + + let proof_spec_prover = ProofSpec::new( + prover_statements.clone(), + meta_statements.clone(), + vec![], + None, + ); + proof_spec_prover.validate().unwrap(); + + if valid_proof { + test_serialization!(ProofSpec, proof_spec_prover); + } + + let mut witnesses = Witnesses::new(); + witnesses.add(PoKSignatureBBSG1Wit::new_as_witness( + sig.clone(), + msgs.clone().into_iter().enumerate().collect(), + )); + witnesses.add(Witness::BoundCheckBpp(msg)); + + if valid_proof { + test_serialization!(Witnesses, witnesses); + } + + let start = Instant::now(); + let proof = ProofG1::new::( + rng, + proof_spec_prover, + witnesses.clone(), + None, + Default::default(), + ) + .unwrap() + .0; + println!( + "Time taken to create proof of Bulletproofs++ bound check of 1 message in signature over {} messages {:?}", + msgs.len(), + start.elapsed() + ); + + if valid_proof { + test_serialization!(ProofG1, proof); + } + + let mut verifier_statements = Statements::new(); + verifier_statements.add(PoKSignatureBBSG1Stmt::new_statement_from_params( + sig_params.clone(), + sig_keypair.public_key.clone(), + BTreeMap::new(), + )); + verifier_statements.add( + BoundCheckStmt::new_statement_from_params(min, max, bpp_setup_params.clone()).unwrap(), + ); + + let proof_spec_verifier = ProofSpec::new( + verifier_statements.clone(), + meta_statements.clone(), + vec![], + None, + ); + proof_spec_verifier.validate().unwrap(); + + let start = Instant::now(); + let res = + proof.verify::(rng, proof_spec_verifier, None, Default::default()); + assert_eq!(res.is_ok(), valid_proof); + println!( + "Time taken to verify proof of Bulletproofs++ bound check of 1 message in signature over {} messages {:?}", + msgs.len(), + start.elapsed() + ); + } + + // Following message's bounds will be checked + + // Check for message that is signed and satisfies the bounds + check( + &mut rng, + min, + max, + 1, + msgs[1], + msgs.clone(), + sig_params.clone(), + sig_keypair.clone(), + sig.clone(), + bpp_setup_params.clone(), + true, + ); + + // Check for message that satisfies the bounds but is not signed + check( + &mut rng, + min, + max, + 0, + Fr::from(min + 10), + msgs, + sig_params, + sig_keypair, + sig, + bpp_setup_params, + false, + ); +} diff --git a/proof_system/tests/bounds.rs b/proof_system/tests/bound_check_legogroth16.rs similarity index 91% rename from proof_system/tests/bounds.rs rename to proof_system/tests/bound_check_legogroth16.rs index 1e7e6e0e..86f8f64d 100644 --- a/proof_system/tests/bounds.rs +++ b/proof_system/tests/bound_check_legogroth16.rs @@ -36,7 +36,7 @@ macro_rules! gen_tests { ($test1_name: ident, $test2_name: ident, $setup_fn_name: ident, $stmt: ident, $wit: ident) => { #[test] fn $test1_name() { - // Prove knowledge of BBS+ signature and a specific message satisfies some bounds i.e. min <= message <= max. + // Prove knowledge of BBS+ signature and a specific message satisfies some bounds i.e. min <= message < max. let mut rng = StdRng::seed_from_u64(0u64); let min = 100; @@ -107,7 +107,7 @@ macro_rules! gen_tests { ) .unwrap(); println!( - "Time taken to create proof of bound check of 1 message in signature over {} messages {:?}", + "Time taken to create proof of LegoGroth16 bound check of 1 message in signature over {} messages {:?}", msg_count, start.elapsed() ); @@ -146,7 +146,7 @@ macro_rules! gen_tests { ) .unwrap(); println!( - "Time taken to verify proof of bound check of 1 message in signature over {} messages {:?}", + "Time taken to verify proof of LegoGroth16 bound check of 1 message in signature over {} messages {:?}", msg_count, start.elapsed() ); @@ -571,8 +571,8 @@ gen_tests!( #[test] fn pok_of_bbs_plus_sig_and_message_same_as_bound() { - // Prove knowledge of BBS+ signature and a specific message satisfies some bounds i.e. min <= message <= max. - // Here message set as min and them max + // Prove knowledge of BBS+ signature and a specific message satisfies some bounds i.e. min <= message < max. + // Here message set as min let mut rng = StdRng::seed_from_u64(0u64); let min = 100; @@ -662,76 +662,4 @@ fn pok_of_bbs_plus_sig_and_message_same_as_bound() { proof .verify::(&mut rng, proof_spec_verifier, None, Default::default()) .unwrap(); - - // Message same as maximum - let mut prover_statements = Statements::new(); - prover_statements.add(PoKSignatureBBSG1Stmt::new_statement_from_params( - sig_params.clone(), - sig_keypair.public_key.clone(), - BTreeMap::new(), - )); - prover_statements.add( - BoundCheckProverStmt::new_statement_from_params( - min, - msg.into_bigint().as_ref()[0], - snark_pk.clone(), - ) - .unwrap(), - ); - - let mut meta_statements = MetaStatements::new(); - meta_statements.add_witness_equality(EqualWitnesses( - vec![(0, msg_idx), (1, 0)] - .into_iter() - .collect::>(), - )); - let proof_spec_prover = ProofSpec::new( - prover_statements.clone(), - meta_statements.clone(), - vec![], - None, - ); - proof_spec_prover.validate().unwrap(); - - let mut witnesses = Witnesses::new(); - witnesses.add(PoKSignatureBBSG1Wit::new_as_witness( - sig, - msgs.clone().into_iter().enumerate().collect(), - )); - witnesses.add(Witness::BoundCheckLegoGroth16(msg)); - - let proof = ProofG1::new::( - &mut rng, - proof_spec_prover, - witnesses.clone(), - None, - Default::default(), - ) - .unwrap() - .0; - - let mut verifier_statements = Statements::new(); - verifier_statements.add(PoKSignatureBBSG1Stmt::new_statement_from_params( - sig_params, - sig_keypair.public_key.clone(), - BTreeMap::new(), - )); - verifier_statements.add( - BoundCheckVerifierStmt::new_statement_from_params( - min, - msg.into_bigint().as_ref()[0], - snark_pk.vk, - ) - .unwrap(), - ); - let proof_spec_verifier = ProofSpec::new( - verifier_statements.clone(), - meta_statements.clone(), - vec![], - None, - ); - proof_spec_verifier.validate().unwrap(); - proof - .verify::(&mut rng, proof_spec_verifier, None, Default::default()) - .unwrap(); } diff --git a/proof_system/tests/bound_check_smc.rs b/proof_system/tests/bound_check_smc.rs new file mode 100644 index 00000000..d01b6941 --- /dev/null +++ b/proof_system/tests/bound_check_smc.rs @@ -0,0 +1,255 @@ +use ark_bls12_381::{Bls12_381, G1Affine}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::rand::{prelude::StdRng, SeedableRng}; +use bbs_plus::prelude::{KeypairG2, SignatureG1, SignatureParamsG1}; +use blake2::Blake2b512; +use std::collections::{BTreeMap, BTreeSet}; + +use proof_system::prelude::{ + BoundCheckSmcInnerProof, EqualWitnesses, MetaStatements, ProofSpec, StatementProof, Statements, + Witness, WitnessRef, Witnesses, +}; +use test_utils::{test_serialization, Fr, ProofG1}; + +use proof_system::{ + prelude::bound_check_smc::SmcParamsAndCommitmentKey, + statement::{ + bbs_plus::PoKBBSSignatureG1 as PoKSignatureBBSG1Stmt, + bound_check_smc::BoundCheckSmc as BoundCheckStmt, + }, + sub_protocols::{bound_check_smc::BoundCheckSmcProtocol, should_use_cls}, + witness::PoKBBSSignatureG1 as PoKSignatureBBSG1Wit, +}; + +#[test] +fn pok_of_bbs_plus_sig_and_bounded_message_using_set_membership_check_range_proof() { + // Prove knowledge of BBS+ signature and a specific message satisfies some bounds i.e. min <= message < max. + // Here message set as min and them max + let mut rng = StdRng::seed_from_u64(0u64); + let msg_count = 5; + + let sig_params = SignatureParamsG1::::generate_using_rng(&mut rng, msg_count); + let sig_keypair = KeypairG2::::generate_using_rng(&mut rng, &sig_params); + + let (smc_setup_params, _) = SmcParamsAndCommitmentKey::new::<_, Blake2b512>( + &mut rng, b"test", + 2, + ); + smc_setup_params.verify().unwrap(); + + fn check( + rng: &mut StdRng, + min: u64, + max: u64, + msg_idx: usize, + msg: Fr, + msgs: Vec, + sig_params: SignatureParamsG1, + sig_keypair: KeypairG2, + sig: SignatureG1, + smc_setup_params: SmcParamsAndCommitmentKey, + valid_proof: bool, + is_cls: bool, + ) { + let mut prover_statements = Statements::new(); + prover_statements.add(PoKSignatureBBSG1Stmt::new_statement_from_params( + sig_params.clone(), + sig_keypair.public_key.clone(), + BTreeMap::new(), + )); + prover_statements.add( + BoundCheckStmt::new_statement_from_params(min, max, smc_setup_params.clone()).unwrap(), + ); + + let mut meta_statements = MetaStatements::new(); + meta_statements.add_witness_equality(EqualWitnesses( + vec![(0, msg_idx), (1, 0)] + .into_iter() + .collect::>(), + )); + + if valid_proof { + test_serialization!(Statements, prover_statements); + test_serialization!(MetaStatements, meta_statements); + } + + let proof_spec_prover = ProofSpec::new( + prover_statements.clone(), + meta_statements.clone(), + vec![], + None, + ); + proof_spec_prover.validate().unwrap(); + + if valid_proof { + test_serialization!(ProofSpec, proof_spec_prover); + } + + let mut witnesses = Witnesses::new(); + witnesses.add(PoKSignatureBBSG1Wit::new_as_witness( + sig.clone(), + msgs.clone().into_iter().enumerate().collect(), + )); + witnesses.add(Witness::BoundCheckSmc(msg)); + + if valid_proof { + test_serialization!(Witnesses, witnesses); + } + + let proof = ProofG1::new::( + rng, + proof_spec_prover, + witnesses.clone(), + None, + Default::default(), + ) + .unwrap() + .0; + + if valid_proof { + test_serialization!(ProofG1, proof); + } + + if is_cls { + match &proof.statement_proofs[1] { + StatementProof::BoundCheckSmc(p) => match &p.proof { + BoundCheckSmcInnerProof::CCS(_) => { + assert!(false, "expected CLS proof but found CCS") + } + BoundCheckSmcInnerProof::CLS(_) => assert!(true), + }, + _ => assert!( + false, + "this shouldn't happen as this test is checking set membership based proof" + ), + } + } else { + match &proof.statement_proofs[1] { + StatementProof::BoundCheckSmc(p) => match &p.proof { + BoundCheckSmcInnerProof::CLS(_) => { + assert!(false, "expected CCS proof but found CLS") + } + BoundCheckSmcInnerProof::CCS(_) => assert!(true), + }, + _ => assert!( + false, + "this shouldn't happen as this test is checking set membership based proof" + ), + } + } + + let mut verifier_statements = Statements::new(); + verifier_statements.add(PoKSignatureBBSG1Stmt::new_statement_from_params( + sig_params.clone(), + sig_keypair.public_key.clone(), + BTreeMap::new(), + )); + verifier_statements.add( + BoundCheckStmt::new_statement_from_params(min, max, smc_setup_params.clone()).unwrap(), + ); + + let proof_spec_verifier = ProofSpec::new( + verifier_statements.clone(), + meta_statements.clone(), + vec![], + None, + ); + proof_spec_verifier.validate().unwrap(); + + let res = + proof.verify::(rng, proof_spec_verifier, None, Default::default()); + assert_eq!(res.is_ok(), valid_proof); + } + + let min = 100; + let max = 200; + let msgs = (0..msg_count) + .map(|i| Fr::from(min + 1 + i as u64)) + .collect::>(); + + let sig = SignatureG1::::new(&mut rng, &msgs, &sig_keypair.secret_key, &sig_params) + .unwrap(); + sig.verify(&msgs, sig_keypair.public_key.clone(), sig_params.clone()) + .unwrap(); + + let is_cls = should_use_cls(min, max); + assert!(is_cls); + + // Check for message that is signed and satisfies the bounds + check( + &mut rng, + min, + max, + 1, + msgs[1], + msgs.clone(), + sig_params.clone(), + sig_keypair.clone(), + sig.clone(), + smc_setup_params.clone(), + true, + is_cls, + ); + + // Check for message that satisfies the bounds but is not signed + check( + &mut rng, + min, + max, + 0, + Fr::from(min + 10), + msgs, + sig_params.clone(), + sig_keypair.clone(), + sig, + smc_setup_params.clone(), + false, + is_cls, + ); + + let min = 100; + let max = min + 2_u64.pow(21); + let msgs = (0..msg_count) + .map(|i| Fr::from(min + 1 + i as u64)) + .collect::>(); + + let sig = SignatureG1::::new(&mut rng, &msgs, &sig_keypair.secret_key, &sig_params) + .unwrap(); + sig.verify(&msgs, sig_keypair.public_key.clone(), sig_params.clone()) + .unwrap(); + + let is_cls = BoundCheckSmcProtocol::::should_use_cls(min, max); + assert!(!is_cls); + + // Check for message that is signed and satisfies the bounds + check( + &mut rng, + min, + max, + 1, + msgs[1], + msgs.clone(), + sig_params.clone(), + sig_keypair.clone(), + sig.clone(), + smc_setup_params.clone(), + true, + is_cls, + ); + + // Check for message that satisfies the bounds but is not signed + check( + &mut rng, + min, + max, + 0, + Fr::from(min + 10), + msgs, + sig_params, + sig_keypair, + sig, + smc_setup_params, + false, + is_cls, + ); +} diff --git a/proof_system/tests/bound_check_smc_with_kv.rs b/proof_system/tests/bound_check_smc_with_kv.rs new file mode 100644 index 00000000..85da5ecc --- /dev/null +++ b/proof_system/tests/bound_check_smc_with_kv.rs @@ -0,0 +1,277 @@ +use ark_bls12_381::{Bls12_381, G1Affine}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::rand::{prelude::StdRng, SeedableRng}; +use bbs_plus::prelude::{KeypairG2, SignatureG1, SignatureParamsG1}; +use blake2::Blake2b512; +use std::collections::{BTreeMap, BTreeSet}; + +use proof_system::prelude::{ + BoundCheckSmcWithKVInnerProof, EqualWitnesses, MetaStatements, ProofSpec, StatementProof, + Statements, Witness, WitnessRef, Witnesses, +}; +use test_utils::{test_serialization, Fr, ProofG1}; + +use proof_system::{ + prelude::{ + bound_check_smc::SmcParamsAndCommitmentKey, + bound_check_smc_with_kv::SmcParamsAndCommitmentKeyAndSecretKey, + }, + statement::{ + bbs_plus::PoKBBSSignatureG1 as PoKSignatureBBSG1Stmt, + bound_check_smc_with_kv::{ + BoundCheckSmcWithKVProver as BoundCheckProverStmt, + BoundCheckSmcWithKVVerifier as BoundCheckVerifierStmt, + }, + }, + sub_protocols::{bound_check_smc::BoundCheckSmcProtocol, should_use_cls}, + witness::PoKBBSSignatureG1 as PoKSignatureBBSG1Wit, +}; + +#[test] +fn pok_of_bbs_plus_sig_and_bounded_message_using_set_membership_check_range_proof_with_keyed_verification( +) { + // Prove knowledge of BBS+ signature and a specific message satisfies some bounds i.e. min <= message < max. + // Here message set as min and them max + let mut rng = StdRng::seed_from_u64(0u64); + let msg_count = 5; + + let sig_params = SignatureParamsG1::::generate_using_rng(&mut rng, msg_count); + let sig_keypair = KeypairG2::::generate_using_rng(&mut rng, &sig_params); + + let (smc_setup_params, sk) = SmcParamsAndCommitmentKey::new::<_, Blake2b512>( + &mut rng, b"test", + 2, + ); + smc_setup_params.verify().unwrap(); + let smc_setup_params_with_sk = SmcParamsAndCommitmentKeyAndSecretKey { + params_and_comm_key: smc_setup_params.clone(), + sk, + }; + + fn check( + rng: &mut StdRng, + min: u64, + max: u64, + msg_idx: usize, + msg: Fr, + msgs: Vec, + sig_params: SignatureParamsG1, + sig_keypair: KeypairG2, + sig: SignatureG1, + smc_setup_params: SmcParamsAndCommitmentKey, + smc_setup_params_with_sk: SmcParamsAndCommitmentKeyAndSecretKey, + valid_proof: bool, + is_cls: bool, + ) { + let mut prover_statements = Statements::new(); + prover_statements.add(PoKSignatureBBSG1Stmt::new_statement_from_params( + sig_params.clone(), + sig_keypair.public_key.clone(), + BTreeMap::new(), + )); + prover_statements.add( + BoundCheckProverStmt::new_statement_from_params(min, max, smc_setup_params.clone()) + .unwrap(), + ); + + let mut meta_statements = MetaStatements::new(); + meta_statements.add_witness_equality(EqualWitnesses( + vec![(0, msg_idx), (1, 0)] + .into_iter() + .collect::>(), + )); + + if valid_proof { + test_serialization!(Statements, prover_statements); + test_serialization!(MetaStatements, meta_statements); + } + + let proof_spec_prover = ProofSpec::new( + prover_statements.clone(), + meta_statements.clone(), + vec![], + None, + ); + proof_spec_prover.validate().unwrap(); + + if valid_proof { + test_serialization!(ProofSpec, proof_spec_prover); + } + + let mut witnesses = Witnesses::new(); + witnesses.add(PoKSignatureBBSG1Wit::new_as_witness( + sig.clone(), + msgs.clone().into_iter().enumerate().collect(), + )); + witnesses.add(Witness::BoundCheckSmcWithKV(msg)); + + if valid_proof { + test_serialization!(Witnesses, witnesses); + } + + let proof = ProofG1::new::( + rng, + proof_spec_prover, + witnesses.clone(), + None, + Default::default(), + ) + .unwrap() + .0; + + if valid_proof { + test_serialization!(ProofG1, proof); + } + + if is_cls { + match &proof.statement_proofs[1] { + StatementProof::BoundCheckSmcWithKV(p) => match &p.proof { + BoundCheckSmcWithKVInnerProof::CCS(_) => { + assert!(false, "expected CLS proof but found CCS") + } + BoundCheckSmcWithKVInnerProof::CLS(_) => assert!(true), + }, + _ => assert!( + false, + "this shouldn't happen as this test is checking set membership based proof" + ), + } + } else { + match &proof.statement_proofs[1] { + StatementProof::BoundCheckSmcWithKV(p) => match &p.proof { + BoundCheckSmcWithKVInnerProof::CLS(_) => { + assert!(false, "expected CCS proof but found CLS") + } + BoundCheckSmcWithKVInnerProof::CCS(_) => assert!(true), + }, + _ => assert!( + false, + "this shouldn't happen as this test is checking set membership based proof" + ), + } + } + + let mut verifier_statements = Statements::new(); + verifier_statements.add(PoKSignatureBBSG1Stmt::new_statement_from_params( + sig_params.clone(), + sig_keypair.public_key.clone(), + BTreeMap::new(), + )); + verifier_statements.add( + BoundCheckVerifierStmt::new_statement_from_params( + min, + max, + smc_setup_params_with_sk.clone(), + ) + .unwrap(), + ); + + let proof_spec_verifier = ProofSpec::new( + verifier_statements.clone(), + meta_statements.clone(), + vec![], + None, + ); + proof_spec_verifier.validate().unwrap(); + + let res = + proof.verify::(rng, proof_spec_verifier, None, Default::default()); + assert_eq!(res.is_ok(), valid_proof); + } + + let min = 100; + let max = 200; + let msgs = (0..msg_count) + .map(|i| Fr::from(min + 1 + i as u64)) + .collect::>(); + + let sig = SignatureG1::::new(&mut rng, &msgs, &sig_keypair.secret_key, &sig_params) + .unwrap(); + sig.verify(&msgs, sig_keypair.public_key.clone(), sig_params.clone()) + .unwrap(); + + let is_cls = should_use_cls(min, max); + assert!(is_cls); + + // Check for message that is signed and satisfies the bounds + check( + &mut rng, + min, + max, + 1, + msgs[1], + msgs.clone(), + sig_params.clone(), + sig_keypair.clone(), + sig.clone(), + smc_setup_params.clone(), + smc_setup_params_with_sk.clone(), + true, + is_cls, + ); + + // Check for message that satisfies the bounds but is not signed + check( + &mut rng, + min, + max, + 0, + Fr::from(min + 10), + msgs, + sig_params.clone(), + sig_keypair.clone(), + sig, + smc_setup_params.clone(), + smc_setup_params_with_sk.clone(), + false, + is_cls, + ); + + let min = 100; + let max = min + 2_u64.pow(21); + let msgs = (0..msg_count) + .map(|i| Fr::from(min + 1 + i as u64)) + .collect::>(); + + let sig = SignatureG1::::new(&mut rng, &msgs, &sig_keypair.secret_key, &sig_params) + .unwrap(); + sig.verify(&msgs, sig_keypair.public_key.clone(), sig_params.clone()) + .unwrap(); + + let is_cls = should_use_cls(min, max); + assert!(!is_cls); + + // Check for message that is signed and satisfies the bounds + check( + &mut rng, + min, + max, + 1, + msgs[1], + msgs.clone(), + sig_params.clone(), + sig_keypair.clone(), + sig.clone(), + smc_setup_params.clone(), + smc_setup_params_with_sk.clone(), + true, + is_cls, + ); + + // Check for message that satisfies the bounds but is not signed + check( + &mut rng, + min, + max, + 0, + Fr::from(min + 10), + msgs, + sig_params, + sig_keypair, + sig, + smc_setup_params, + smc_setup_params_with_sk.clone(), + false, + is_cls, + ); +} diff --git a/saver/Cargo.toml b/saver/Cargo.toml index 09091b2d..b190d3e4 100644 --- a/saver/Cargo.toml +++ b/saver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "saver" -version = "0.13.0" +version = "0.14.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -17,11 +17,11 @@ ark-relations.workspace = true ark-groth16.workspace = true digest.workspace = true rayon = {workspace = true, optional = true} -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } serde.workspace = true serde_with.workspace = true zeroize.workspace = true -legogroth16 = { version = "0.10.0", default-features = false, features = ["aggregation"], path = "../legogroth16" } +legogroth16 = { version = "0.11.0", default-features = false, features = ["aggregation"], path = "../legogroth16" } merlin = { package = "dock_merlin", version = "2.0", default-features = false, path = "../merlin" } [dev-dependencies] diff --git a/schnorr_pok/Cargo.toml b/schnorr_pok/Cargo.toml index 118358b5..70a243c9 100644 --- a/schnorr_pok/Cargo.toml +++ b/schnorr_pok/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "schnorr_pok" -version = "0.14.0" +version = "0.15.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -18,7 +18,7 @@ ark-ec.workspace = true ark-std.workspace = true rayon = {workspace = true, optional = true} digest.workspace = true -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } serde.workspace = true serde_with.workspace = true zeroize.workspace = true diff --git a/secret_sharing_and_dkg/Cargo.toml b/secret_sharing_and_dkg/Cargo.toml index f6542590..0dc13940 100644 --- a/secret_sharing_and_dkg/Cargo.toml +++ b/secret_sharing_and_dkg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "secret_sharing_and_dkg" -version = "0.7.0" +version = "0.8.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -18,8 +18,8 @@ rayon = { workspace = true, optional = true } serde.workspace = true serde_with.workspace = true zeroize.workspace = true -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } [dev-dependencies] blake2.workspace = true diff --git a/smc_range_proof/Cargo.toml b/smc_range_proof/Cargo.toml new file mode 100644 index 00000000..34ff7bdd --- /dev/null +++ b/smc_range_proof/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "smc_range_proof" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description = "Range proofs based on set-membership check and sumsets" + +[dependencies] +ark-ff.workspace = true +ark-ec.workspace = true +ark-std.workspace = true +ark-serialize.workspace = true +digest.workspace = true +zeroize.workspace = true +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } +rayon = {workspace = true, optional = true} + +[dev-dependencies] +blake2.workspace = true +ark-bls12-381.workspace = true +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } + +[features] +default = [ "parallel"] +std = [ "ark-ff/std", "ark-ec/std", "ark-std/std", "ark-serialize/std", "dock_crypto_utils/std"] +parallel = [ "std", "ark-ff/parallel", "ark-ec/parallel", "ark-std/parallel", "rayon", "dock_crypto_utils/parallel"] \ No newline at end of file diff --git a/smc_range_proof/README.md b/smc_range_proof/README.md new file mode 100644 index 00000000..47d03c73 --- /dev/null +++ b/smc_range_proof/README.md @@ -0,0 +1,22 @@ +# Zero-knowledge range proof protocols based on set-membership check + + + +Implements the following range proof and set-membership protocols. +1. Set membership protocol using BB signature. Described in Fig.1 of the paper [1]. [Code](/src/ccs_set_membership) +2. Range proof protocol as described in Fig.3 of the paper [1]. Considers a perfect-range, i.e. range of the form `[0, u^l)` +where `u` is the base and the upper bound is a power of the base. [Code](src/ccs_range_proof/perfect_range.rs) +3. Range proof protocol as described in section 4.4 of the paper [1]. Considers an arbitrary range `[min, max)`. [Code](src/ccs_range_proof/arbitrary_range.rs) +4. Range proof using sumsets, based on Protocol 2 from the paper [2]. [Code](src/smc_range_proof.rs) +5. Implements the Keyed-Verification of the above protocols where the verifier knows the secret key of the BB sig. This makes +the proof generation and verification more efficient by removing the need for pairings. This idea is taken from this PhD. thesis. + +Above protocols use a pairing based signature called the [BB signature](src/bb_sig.rs). + +References: + +[1]: [Efficient Protocols for Set Membership and Range Proofs](https://link.springer.com/chapter/10.1007/978-3-540-89255-7_15) + +[2]: [Additive Combinatorics and Discrete Logarithm Based Range Protocols](https://eprint.iacr.org/2009/469) + + diff --git a/smc_range_proof/src/bb_sig.rs b/smc_range_proof/src/bb_sig.rs new file mode 100644 index 00000000..ae929711 --- /dev/null +++ b/smc_range_proof/src/bb_sig.rs @@ -0,0 +1,227 @@ +//! BB signature + +use crate::error::SmcRangeProofError; +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + AffineRepr, +}; +use ark_ff::{Field, PrimeField, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{ + cfg_into_iter, collections::BTreeSet, ops::Neg, rand::RngCore, vec::Vec, UniformRand, +}; +use digest::Digest; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use dock_crypto_utils::{concat_slices, hashing_utils::affine_group_elem_from_try_and_incr}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +/// Public parameters for creating and verifying BB signatures +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SignatureParams { + pub g1: E::G1Affine, + pub g2: E::G2Affine, +} + +/// `SignatureParams` with pre-computation done for protocols more efficient +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SignatureParamsWithPairing { + pub g1: E::G1Affine, + pub g2: E::G2Affine, + pub g2_prepared: E::G2Prepared, + /// pairing e(g1, g2) + pub g1g2: PairingOutput, +} + +/// Secret key used by the signer to sign messages +#[derive( + Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Zeroize, ZeroizeOnDrop, +)] +pub struct SecretKey(pub F); + +/// Public key used to verify signatures +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct PublicKeyG2(pub ::G2Affine); + +impl SignatureParams { + pub fn new(label: &[u8]) -> Self { + let g1 = + affine_group_elem_from_try_and_incr::(&concat_slices![label, b" : g1"]); + let g2 = + affine_group_elem_from_try_and_incr::(&concat_slices![label, b" : g2"]); + Self { g1, g2 } + } + + pub fn generate_using_rng(rng: &mut R) -> Self { + Self { + g1: E::G1::rand(rng).into(), + g2: E::G2::rand(rng).into(), + } + } + + pub fn is_valid(&self) -> bool { + !(self.g1.is_zero() || self.g2.is_zero()) + } +} + +impl From> for SignatureParamsWithPairing { + fn from(params: SignatureParams) -> Self { + let g1g2 = E::pairing(params.g1, params.g2); + Self { + g1: params.g1, + g2: params.g2, + g2_prepared: E::G2Prepared::from(params.g2), + g1g2, + } + } +} + +impl SecretKey { + pub fn new(rng: &mut R) -> Self { + Self(F::rand(rng)) + } + + /// Generate secret key which should not be in `0, -1, 2, .. -(base-1)` because during range proof using + /// base `base`, signature is created over `0, 1, 2, ... base-1` + pub fn new_for_base(rng: &mut R, base: u16) -> Self { + let mut sk = F::rand(rng); + let neg_bases = cfg_into_iter!(0..base) + .map(|b| F::from(b).neg()) + .collect::>(); + while neg_bases.contains(&sk) { + sk = F::rand(rng) + } + Self(sk) + } + + pub fn is_valid_for_base(&self, base: u16) -> bool { + let neg_bases = (0..base).map(|b| F::from(b).neg()); + let position = neg_bases.into_iter().position(|b| b == self.0); + position.is_none() + } +} + +impl PublicKeyG2 +where + E: Pairing, +{ + pub fn generate_using_secret_key( + secret_key: &SecretKey, + params: &SignatureParams, + ) -> Self { + Self((params.g2 * secret_key.0).into()) + } + + /// Public key shouldn't be 0. A verifier on receiving this must first check that its + /// valid and only then use it for any signature or proof of knowledge of signature verification. + pub fn is_valid(&self) -> bool { + !self.0.is_zero() + } +} + +#[derive( + Clone, Debug, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize, Zeroize, ZeroizeOnDrop, +)] +pub struct SignatureG1(pub E::G1Affine); + +impl SignatureG1 { + /// Create a new signature + pub fn new( + message: &E::ScalarField, + sk: &SecretKey, + params: &SignatureParams, + ) -> Self { + Self((params.g1 * ((sk.0 + message).inverse().unwrap())).into()) + } + + pub fn verify( + &self, + message: &E::ScalarField, + pk: &PublicKeyG2, + params: &SignatureParams, + ) -> Result<(), SmcRangeProofError> { + if !self.is_non_zero() { + return Err(SmcRangeProofError::ZeroSignature); + } + // Check e(sig, pk + g2*m) == e(g1, g2) => e(g1, g2) - e(sig, pk + g2*m) == 0 => e(g1, g2) + e(sig, -(pk + g2*m)) == 0 + // gm = -g2*m - g2*x + let gm = params.g2 * message.neg() - pk.0; + if !E::multi_pairing( + [E::G1Prepared::from(self.0), E::G1Prepared::from(params.g1)], + [E::G2Prepared::from(gm), E::G2Prepared::from(params.g2)], + ) + .is_zero() + { + return Err(SmcRangeProofError::InvalidSignature); + } + Ok(()) + } + + pub fn verify_given_sig_params_with_pairing( + &self, + message: &E::ScalarField, + pk: &PublicKeyG2, + params: &SignatureParamsWithPairing, + ) -> Result<(), SmcRangeProofError> { + if !self.is_non_zero() { + return Err(SmcRangeProofError::ZeroSignature); + } + // Check e(sig, pk + g2*m) == e(g1, g2) + // gm = g2*m + g2*x + let gm = params.g2 * message + pk.0; + if E::pairing(E::G1Prepared::from(self.0), E::G2Prepared::from(gm)) != params.g1g2 { + return Err(SmcRangeProofError::InvalidSignature); + } + Ok(()) + } + + pub fn is_non_zero(&self) -> bool { + !self.0.is_zero() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_bls12_381::{Bls12_381, Fr}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + + #[test] + fn secret_key_validation() { + let mut rng = StdRng::seed_from_u64(0u64); + for base in [2, 4, 8, 16, 32, 64] { + SecretKey::::new_for_base(&mut rng, base); + } + + // Create secret key as negative of a base + let sk = SecretKey(Fr::from(32).neg()); + + for base in [2, 4, 8, 16] { + assert!(sk.is_valid_for_base(base)); + } + + for base in [33, 64, 128] { + assert!(!sk.is_valid_for_base(base)); + } + } + + #[test] + fn signature_verification() { + let mut rng = StdRng::seed_from_u64(0u64); + + let params = SignatureParams::::generate_using_rng(&mut rng); + let params_with_pairing = SignatureParamsWithPairing::::from(params.clone()); + let sk = SecretKey::new(&mut rng); + let pk = PublicKeyG2::generate_using_secret_key(&sk, ¶ms); + + let message = Fr::rand(&mut rng); + let sig = SignatureG1::new(&message, &sk, ¶ms); + sig.verify(&message, &pk, ¶ms).unwrap(); + sig.verify_given_sig_params_with_pairing(&message, &pk, ¶ms_with_pairing) + .unwrap(); + } +} diff --git a/smc_range_proof/src/ccs_range_proof/arbitrary_range.rs b/smc_range_proof/src/ccs_range_proof/arbitrary_range.rs new file mode 100644 index 00000000..37d5d727 --- /dev/null +++ b/smc_range_proof/src/ccs_range_proof/arbitrary_range.rs @@ -0,0 +1,577 @@ +//! Range proof protocol as described in section 4.4 of the paper [Efficient Protocols for Set Membership and Range Proofs](https://link.springer.com/chapter/10.1007/978-3-540-89255-7_15). +//! Considers an arbitrary range `[min, max)` + +use crate::{ + ccs_set_membership::setup::SetMembershipCheckParamsWithPairing, common::MemberCommitmentKey, + error::SmcRangeProofError, +}; +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + AffineRepr, CurveGroup, +}; + +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, format, io::Write, ops::Mul, rand::RngCore, vec::Vec, UniformRand}; +use dock_crypto_utils::{misc::n_rand, msm::WindowTable}; + +use crate::ccs_range_proof::util::{check_commitment_for_arbitrary_range, find_l_greater_than}; +use dock_crypto_utils::randomized_pairing_check::RandomizedPairingChecker; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::common::padded_base_n_digits_as_field_elements; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSArbitraryRangeProofProtocol { + pub base: u16, + pub digits_min: Vec, + pub digits_max: Vec, + pub r: E::ScalarField, + pub v_min: Vec, + pub v_max: Vec, + pub V_min: Vec, + pub V_max: Vec, + pub a_min: Vec>, + pub a_max: Vec>, + pub D: E::G1Affine, + pub m: E::ScalarField, + pub s: Vec, + pub t_min: Vec, + pub t_max: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSArbitraryRangeProof { + pub base: u16, + pub V_min: Vec, + pub V_max: Vec, + pub a_min: Vec>, + pub a_max: Vec>, + pub D: E::G1Affine, + pub z_v_min: Vec, + pub z_v_max: Vec, + pub z_sigma_min: Vec, + pub z_sigma_max: Vec, + pub z_r: E::ScalarField, +} + +impl CCSArbitraryRangeProofProtocol { + pub fn init( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result { + let params = params.into(); + Self::init_given_base( + rng, + value, + randomness, + min, + max, + params.get_supported_base_for_range_proof(), + comm_key, + params, + ) + } + + pub fn init_given_base( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + min: u64, + max: u64, + base: u16, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result { + if min > value { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "value={} should be >= min={}", + value, min + ))); + } + if value >= max { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "value={} should be < max={}", + value, max + ))); + } + + let params = params.into(); + + params.validate_base(base)?; + + let l = find_l_greater_than(max, base) as usize; + + let m = E::ScalarField::rand(rng); + let s = n_rand(rng, l).collect::>(); + let D = comm_key.commit_decomposed(base, &s, &m); + + let digits_min = padded_base_n_digits_as_field_elements(value - min, base, l); + let digits_max = padded_base_n_digits_as_field_elements( + value + (base as u64).pow(l as u32) - max, + base, + l, + ); + + let t_min = n_rand(rng, l).collect::>(); + let t_max = n_rand(rng, l).collect::>(); + let v_min = n_rand(rng, l).collect::>(); + let v_max = n_rand(rng, l).collect::>(); + + // Randomize all signatures. Different randomizer vectors `v_min` and `v_max` are chosen to avoid leaking + // the information that some digits are potentially same at the same indices, i.e + // `V_min_i = V_max_i` if same `v_min` and `v_min` were used when `A_min_i = A_max_i` + + // V_min_i = A_min * v_min_i + let V_min = randomize_sigs!(&digits_min, &v_min, ¶ms); + // V_max_i = A_max * v_max_i + let V_max = randomize_sigs!(&digits_max, &v_max, ¶ms); + + let a_min = cfg_into_iter!(0..l) + .map(|i| { + E::pairing( + E::G1Prepared::from(V_min[i] * -s[i]), + params.bb_sig_params.g2_prepared.clone(), + ) + params.bb_sig_params.g1g2.mul(t_min[i]) + }) + .collect::>(); + let a_max = cfg_into_iter!(0..l) + .map(|i| { + E::pairing( + E::G1Prepared::from(V_max[i] * -s[i]), + params.bb_sig_params.g2_prepared.clone(), + ) + params.bb_sig_params.g1g2.mul(t_max[i]) + }) + .collect::>(); + + Ok(Self { + base, + digits_min, + digits_max, + r: randomness, + v_min, + v_max, + V_min: E::G1::normalize_batch(&V_min), + V_max: E::G1::normalize_batch(&V_max), + a_min, + a_max, + D, + m, + s, + t_min, + t_max, + }) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V_min, + &self.V_max, + &self.a_min, + &self.a_max, + &self.D, + commitment, + comm_key, + params, + writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> CCSArbitraryRangeProof { + gen_proof_arbitrary_range!(self, challenge, CCSArbitraryRangeProof) + } + + pub fn compute_challenge_contribution( + V_min: &[E::G1Affine], + V_max: &[E::G1Affine], + a_min: &[PairingOutput], + a_max: &[PairingOutput], + D: &E::G1Affine, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + params.serialize_for_schnorr_protocol(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + commitment.serialize_compressed(&mut writer)?; + for V_i in V_min { + V_i.serialize_compressed(&mut writer)?; + } + for V_i in V_max { + V_i.serialize_compressed(&mut writer)?; + } + for a_i in a_min { + a_i.serialize_compressed(&mut writer)?; + } + for a_i in a_max { + a_i.serialize_compressed(&mut writer)?; + } + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl CCSArbitraryRangeProof { + pub fn verify( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + self.non_crypto_validate(min, max, ¶ms)?; + check_commitment_for_arbitrary_range::( + self.base, + &self.z_sigma_min, + &self.z_sigma_max, + &self.z_r, + &self.D, + min, + max, + commitment, + challenge, + comm_key, + )?; + + let (yc_sigma_min, yc_sigma_max, lhs_min, lhs_max) = + self.compute_for_pairing_check(challenge, ¶ms); + + for i in 0..self.V_min.len() { + let rhs = E::pairing( + E::G1Prepared::from(self.V_min[i]), + E::G2Prepared::from(yc_sigma_min[i]), + ); + if lhs_min[i] != rhs { + return Err(SmcRangeProofError::InvalidRangeProof); + } + let rhs = E::pairing( + E::G1Prepared::from(self.V_max[i]), + E::G2Prepared::from(yc_sigma_max[i]), + ); + if lhs_max[i] != rhs { + return Err(SmcRangeProofError::InvalidRangeProof); + } + } + Ok(()) + } + + pub fn verify_given_randomized_pairing_checker( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + pairing_checker: &mut RandomizedPairingChecker, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + self.non_crypto_validate(min, max, ¶ms)?; + check_commitment_for_arbitrary_range::( + self.base, + &self.z_sigma_min, + &self.z_sigma_max, + &self.z_r, + &self.D, + min, + max, + commitment, + challenge, + comm_key, + )?; + let (yc_sigma_min, yc_sigma_max, lhs_min, lhs_max) = + self.compute_for_pairing_check(challenge, ¶ms); + + for i in 0..self.V_min.len() { + pairing_checker.add_multiple_sources_and_target( + &[self.V_min[i]], + &[yc_sigma_min[i]], + &lhs_min[i], + ); + pairing_checker.add_multiple_sources_and_target( + &[self.V_max[i]], + &[yc_sigma_max[i]], + &lhs_max[i], + ); + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + CCSArbitraryRangeProofProtocol::compute_challenge_contribution( + &self.V_min, + &self.V_max, + &self.a_min, + &self.a_max, + &self.D, + commitment, + comm_key, + params, + writer, + ) + } + + fn non_crypto_validate( + &self, + min: u64, + max: u64, + params: &SetMembershipCheckParamsWithPairing, + ) -> Result<(), SmcRangeProofError> { + if min >= max { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "min={} should be < max={}", + min, max + ))); + } + params.validate_base(self.base)?; + let l = find_l_greater_than(max, self.base) as usize; + assert_eq!(self.V_min.len(), l); + assert_eq!(self.V_max.len(), l); + assert_eq!(self.a_min.len(), l); + assert_eq!(self.a_max.len(), l); + assert_eq!(self.z_sigma_min.len(), l); + assert_eq!(self.z_sigma_max.len(), l); + Ok(()) + } + + fn compute_for_pairing_check( + &self, + challenge: &E::ScalarField, + params: &SetMembershipCheckParamsWithPairing, + ) -> ( + Vec, + Vec, + Vec>, + Vec>, + ) { + // y * c + let yc = params.bb_pk.0 * challenge; + + let table = WindowTable::new( + core::cmp::max(self.z_sigma_min.len(), self.z_sigma_max.len()), + params.bb_sig_params.g2.into_group(), + ); + // g2 * z_sigma_min + let g2_z_sigma_min = table.multiply_many(&self.z_sigma_min); + // g2 * z_sigma_max + let g2_z_sigma_max = table.multiply_many(&self.z_sigma_max); + + let lhs_min = cfg_into_iter!(0..self.V_min.len()) + .map(|i| self.a_min[i] - (params.bb_sig_params.g1g2 * self.z_v_min[i])) + .collect::>(); + let lhs_max = cfg_into_iter!(0..self.V_max.len()) + .map(|i| self.a_max[i] - (params.bb_sig_params.g1g2 * self.z_v_max[i])) + .collect::>(); + let yc_sigma_min = cfg_into_iter!(0..g2_z_sigma_min.len()) + .map(|i| yc - g2_z_sigma_min[i]) + .collect::>(); + let yc_sigma_max = cfg_into_iter!(0..g2_z_sigma_max.len()) + .map(|i| yc - g2_z_sigma_max[i]) + .collect::>(); + + (yc_sigma_min, yc_sigma_max, lhs_min, lhs_max) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccs_set_membership::setup::SetMembershipCheckParams; + use ark_bls12_381::{Bls12_381, Fr, G1Affine}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use schnorr_pok::compute_random_oracle_challenge; + use std::time::{Duration, Instant}; + + #[test] + fn range_proof_for_arbitrary_range() { + let mut rng = StdRng::seed_from_u64(0u64); + + let mut proving_time = Duration::default(); + let mut verifying_time = Duration::default(); + let mut verifying_with_rpc_time = Duration::default(); + let mut num_proofs = 0; + + for base in [2, 4, 8, 16] { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base); + params.verify().unwrap(); + + let params_with_pairing = SetMembershipCheckParamsWithPairing::from(params.clone()); + params_with_pairing.verify().unwrap(); + + let comm_key = MemberCommitmentKey::::generate_using_rng(&mut rng); + + for _ in 0..5 { + let shift = base.ilog2(); + let mut a = [ + u64::rand(&mut rng) >> shift, + u64::rand(&mut rng) >> shift, + u64::rand(&mut rng) >> shift, + ]; + // let mut a = [ + // u16::rand(&mut rng) as u64, + // u16::rand(&mut rng) as u64, + // u16::rand(&mut rng) as u64, + // ]; + a.sort(); + let min = a[0]; + let max = a[2]; + let value = a[1]; + assert!(value >= min); + assert!(value < max); + let randomness = Fr::rand(&mut rng); + let commitment = comm_key.commit(&Fr::from(value), &randomness); + + // Params with smaller base should fail + let params_with_smaller_base = { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base - 1); + SetMembershipCheckParamsWithPairing::from(params.clone()) + }; + assert!(CCSArbitraryRangeProofProtocol::init_given_base( + &mut rng, + value, + randomness, + min, + max, + base, + &comm_key, + params_with_smaller_base, + ) + .is_err()); + + // min > max should fail + assert!(CCSArbitraryRangeProofProtocol::init_given_base( + &mut rng, + value, + randomness, + max, + min, + base, + &comm_key, + params_with_pairing.clone(), + ) + .is_err()); + + // Params with larger base should work + let params_with_larger_base = { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base + 1); + SetMembershipCheckParamsWithPairing::from(params.clone()) + }; + + for params in [params_with_pairing.clone(), params_with_larger_base] { + let start = Instant::now(); + let protocol = CCSArbitraryRangeProofProtocol::init_given_base( + &mut rng, + value, + randomness, + min, + max, + base, + &comm_key, + params.clone(), + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution( + &commitment, + &comm_key, + params.clone(), + &mut chal_bytes_prover, + ) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + proving_time += start.elapsed(); + // assert_eq!(proof.V.len(), l as usize); + + let start = Instant::now(); + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution( + &commitment, + &comm_key, + params.clone(), + &mut chal_bytes_verifier, + ) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + proof + .verify( + &commitment, + &challenge_verifier, + min, + max, + &comm_key, + params.clone(), + ) + .unwrap(); + verifying_time += start.elapsed(); + + let start = Instant::now(); + let mut pairing_checker = + RandomizedPairingChecker::new_using_rng(&mut rng, true); + proof + .verify_given_randomized_pairing_checker( + &commitment, + &challenge_verifier, + min, + max, + &comm_key, + params.clone(), + &mut pairing_checker, + ) + .unwrap(); + verifying_with_rpc_time += start.elapsed(); + + num_proofs += 1; + } + } + } + + println!("For {} proofs, proving_time={:?}, verifying_time={:?} and verifying using randomized pairing checker time {:?}", num_proofs, proving_time, verifying_time, verifying_with_rpc_time); + } +} diff --git a/smc_range_proof/src/ccs_range_proof/kv_arbitrary_range.rs b/smc_range_proof/src/ccs_range_proof/kv_arbitrary_range.rs new file mode 100644 index 00000000..c3e65ee7 --- /dev/null +++ b/smc_range_proof/src/ccs_range_proof/kv_arbitrary_range.rs @@ -0,0 +1,439 @@ +//! Same as CCS arbitrary range proof protocol but does Keyed-Verification, i.e the verifies knows the +//! secret key of the BB-sig + +use crate::{ + bb_sig::SecretKey, ccs_set_membership::setup::SetMembershipCheckParams, + common::MemberCommitmentKey, error::SmcRangeProofError, +}; +use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; + +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, format, io::Write, rand::RngCore, vec::Vec, UniformRand}; +use dock_crypto_utils::misc::n_rand; + +use crate::ccs_range_proof::util::{check_commitment_for_arbitrary_range, find_l_greater_than}; +use dock_crypto_utils::msm::multiply_field_elems_with_same_group_elem; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::common::padded_base_n_digits_as_field_elements; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSArbitraryRangeProofWithKVProtocol { + pub base: u16, + pub digits_min: Vec, + pub digits_max: Vec, + pub r: E::ScalarField, + pub v_min: Vec, + pub v_max: Vec, + pub V_min: Vec, + pub V_max: Vec, + pub a_min: Vec, + pub a_max: Vec, + pub D: E::G1Affine, + pub m: E::ScalarField, + pub s: Vec, + pub t_min: Vec, + pub t_max: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSArbitraryRangeWithKVProof { + pub base: u16, + pub V_min: Vec, + pub V_max: Vec, + pub a_min: Vec, + pub a_max: Vec, + pub D: E::G1Affine, + pub z_v_min: Vec, + pub z_v_max: Vec, + pub z_sigma_min: Vec, + pub z_sigma_max: Vec, + pub z_r: E::ScalarField, +} + +impl CCSArbitraryRangeProofWithKVProtocol { + pub fn init( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + ) -> Result { + Self::init_given_base( + rng, + value, + randomness, + min, + max, + params.get_supported_base_for_range_proof(), + comm_key, + params, + ) + } + + pub fn init_given_base( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + min: u64, + max: u64, + base: u16, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + ) -> Result { + if min > value { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "value={} should be >= min={}", + value, min + ))); + } + if value >= max { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "value={} should be < max={}", + value, max + ))); + } + + params.validate_base(base)?; + + let l = find_l_greater_than(max, base) as usize; + + let m = E::ScalarField::rand(rng); + let s = n_rand(rng, l).collect::>(); + let D = comm_key.commit_decomposed(base, &s, &m); + + let digits_min = padded_base_n_digits_as_field_elements(value - min, base, l); + let digits_max = padded_base_n_digits_as_field_elements( + value + (base as u64).pow(l as u32) - max, + base, + l, + ); + + let t_min = n_rand(rng, l).collect::>(); + let t_max = n_rand(rng, l).collect::>(); + let v_min = n_rand(rng, l).collect::>(); + let v_max = n_rand(rng, l).collect::>(); + + // Randomize all signatures. Different randomizer vectors `v_min` and `v_max` are chosen to avoid leaking + // the information that some digits are potentially same at the same indices, i.e + // `V_min_i = V_max_i` if same `v_min` and `v_min` were used when `A_min_i = A_max_i` + + // V_min_i = A_min * v_min_i + let V_min = randomize_sigs!(&digits_min, &v_min, ¶ms); + // V_max_i = A_max * v_max_i + let V_max = randomize_sigs!(&digits_max, &v_max, ¶ms); + + let g = params.bb_sig_params.g1.into_group(); + // g * t_min_i + let g1t_min = multiply_field_elems_with_same_group_elem(g.clone(), &t_min); + // g * t_max_i + let g1t_max = multiply_field_elems_with_same_group_elem(g, &t_max); + + let a_min = cfg_into_iter!(0..l) + .map(|i| (V_min[i] * -s[i]) + g1t_min[i]) + .collect::>(); + let a_max = cfg_into_iter!(0..l) + .map(|i| (V_max[i] * -s[i]) + g1t_max[i]) + .collect::>(); + + Ok(Self { + base, + digits_min, + digits_max, + r: randomness, + v_min, + v_max, + V_min: E::G1::normalize_batch(&V_min), + V_max: E::G1::normalize_batch(&V_max), + a_min: E::G1::normalize_batch(&a_min), + a_max: E::G1::normalize_batch(&a_max), + D, + m, + s, + t_min, + t_max, + }) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V_min, + &self.V_max, + &self.a_min, + &self.a_max, + &self.D, + commitment, + comm_key, + params, + writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> CCSArbitraryRangeWithKVProof { + gen_proof_arbitrary_range!(self, challenge, CCSArbitraryRangeWithKVProof) + } + + pub fn compute_challenge_contribution( + V_min: &[E::G1Affine], + V_max: &[E::G1Affine], + a_min: &[E::G1Affine], + a_max: &[E::G1Affine], + D: &E::G1Affine, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + params.serialize_for_schnorr_protocol_for_kv(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + commitment.serialize_compressed(&mut writer)?; + for V_i in V_min { + V_i.serialize_compressed(&mut writer)?; + } + for V_i in V_max { + V_i.serialize_compressed(&mut writer)?; + } + for a_i in a_min { + a_i.serialize_compressed(&mut writer)?; + } + for a_i in a_max { + a_i.serialize_compressed(&mut writer)?; + } + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl CCSArbitraryRangeWithKVProof { + pub fn verify( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + secret_key: &SecretKey, + ) -> Result<(), SmcRangeProofError> { + self.non_crypto_validate(min, max, ¶ms)?; + check_commitment_for_arbitrary_range::( + self.base, + &self.z_sigma_min, + &self.z_sigma_max, + &self.z_r, + &self.D, + min, + max, + commitment, + challenge, + comm_key, + )?; + + let g1 = params.bb_sig_params.g1.into_group(); + let g1v_min = multiply_field_elems_with_same_group_elem(g1.clone(), &self.z_v_min); + let g1v_max = multiply_field_elems_with_same_group_elem(g1, &self.z_v_max); + + for i in 0..self.V_min.len() { + if self.a_min[i] + != (self.V_min[i] * (secret_key.0 * challenge - self.z_sigma_min[i]) + g1v_min[i]) + .into_affine() + { + return Err(SmcRangeProofError::InvalidRangeProof); + } + if self.a_max[i] + != (self.V_max[i] * (secret_key.0 * challenge - self.z_sigma_max[i]) + g1v_max[i]) + .into_affine() + { + return Err(SmcRangeProofError::InvalidRangeProof); + } + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + writer: W, + ) -> Result<(), SmcRangeProofError> { + CCSArbitraryRangeProofWithKVProtocol::compute_challenge_contribution( + &self.V_min, + &self.V_max, + &self.a_min, + &self.a_max, + &self.D, + commitment, + comm_key, + params, + writer, + ) + } + + fn non_crypto_validate( + &self, + min: u64, + max: u64, + params: &SetMembershipCheckParams, + ) -> Result<(), SmcRangeProofError> { + if min >= max { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "min={} should be < max={}", + min, max + ))); + } + params.validate_base(self.base)?; + let l = find_l_greater_than(max, self.base) as usize; + assert_eq!(self.V_min.len(), l); + assert_eq!(self.V_max.len(), l); + assert_eq!(self.a_min.len(), l); + assert_eq!(self.a_max.len(), l); + assert_eq!(self.z_sigma_min.len(), l); + assert_eq!(self.z_sigma_max.len(), l); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccs_set_membership::setup::SetMembershipCheckParams; + use ark_bls12_381::{Bls12_381, Fr, G1Affine}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use schnorr_pok::compute_random_oracle_challenge; + use std::time::{Duration, Instant}; + + #[test] + fn range_proof_for_arbitrary_range() { + let mut rng = StdRng::seed_from_u64(0u64); + + let mut proving_time = Duration::default(); + let mut verifying_time = Duration::default(); + let mut num_proofs = 0; + + for base in [2, 4, 8, 16] { + let (params, sk) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base); + params.verify().unwrap(); + + let comm_key = MemberCommitmentKey::::generate_using_rng(&mut rng); + + for _ in 0..5 { + let shift = base.ilog2(); + let mut a = [ + u64::rand(&mut rng) >> shift, + u64::rand(&mut rng) >> shift, + u64::rand(&mut rng) >> shift, + ]; + // let mut a = [ + // u16::rand(&mut rng) as u64, + // u16::rand(&mut rng) as u64, + // u16::rand(&mut rng) as u64, + // ]; + a.sort(); + let min = a[0]; + let max = a[2]; + let value = a[1]; + assert!(value >= min); + assert!(value < max); + let randomness = Fr::rand(&mut rng); + let commitment = comm_key.commit(&Fr::from(value), &randomness); + + // Params with incorrect base should fail + let params_with_smaller_base = { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base - 1); + params.verify().unwrap(); + params + }; + assert!(CCSArbitraryRangeProofWithKVProtocol::init_given_base( + &mut rng, + value, + randomness, + min, + max, + base, + &comm_key, + ¶ms_with_smaller_base, + ) + .is_err()); + + // min > max should fail + assert!(CCSArbitraryRangeProofWithKVProtocol::init_given_base( + &mut rng, value, randomness, max, min, base, &comm_key, ¶ms, + ) + .is_err()); + + // Params with larger base should work + let (params_with_larger_base, sk_larger) = { + let (params, sk) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base + 1); + params.verify().unwrap(); + (params, sk) + }; + + for (p, sk) in [(¶ms, &sk), (¶ms_with_larger_base, &sk_larger)] { + let start = Instant::now(); + let protocol = CCSArbitraryRangeProofWithKVProtocol::init_given_base( + &mut rng, value, randomness, min, max, base, &comm_key, p, + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution(&commitment, &comm_key, p, &mut chal_bytes_prover) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + proving_time += start.elapsed(); + // assert_eq!(proof.V.len(), l as usize); + + let start = Instant::now(); + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution(&commitment, &comm_key, p, &mut chal_bytes_verifier) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + proof + .verify(&commitment, &challenge_verifier, min, max, &comm_key, p, sk) + .unwrap(); + verifying_time += start.elapsed(); + + num_proofs += 1; + } + } + } + + println!( + "For {} proofs, proving_time={:?} and verifying_time={:?}", + num_proofs, proving_time, verifying_time + ); + } +} diff --git a/smc_range_proof/src/ccs_range_proof/kv_perfect_range.rs b/smc_range_proof/src/ccs_range_proof/kv_perfect_range.rs new file mode 100644 index 00000000..dac2defc --- /dev/null +++ b/smc_range_proof/src/ccs_range_proof/kv_perfect_range.rs @@ -0,0 +1,299 @@ +//! Same as CCS perfect range proof protocol but does Keyed-Verification, i.e the verifies knows the +//! secret key of the BB-sig + +use crate::{ + bb_sig::SecretKey, ccs_set_membership::setup::SetMembershipCheckParams, + common::MemberCommitmentKey, error::SmcRangeProofError, +}; +use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; + +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, io::Write, rand::RngCore, vec::Vec, UniformRand}; +use dock_crypto_utils::{misc::n_rand, msm::multiply_field_elems_with_same_group_elem}; + +use crate::common::padded_base_n_digits_as_field_elements; + +use crate::ccs_range_proof::util::check_commitment_for_prefect_range; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSPerfectRangeProofWithKVProtocol { + pub base: u16, + pub digits: Vec, + pub r: E::ScalarField, + pub v: Vec, + pub V: Vec, + pub a: Vec, + pub D: E::G1Affine, + pub m: E::ScalarField, + pub s: Vec, + pub t: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSPerfectRangeWithKVProof { + pub base: u16, + pub V: Vec, + pub a: Vec, + pub D: E::G1Affine, + pub z_v: Vec, + pub z_sigma: Vec, + pub z_r: E::ScalarField, +} + +impl CCSPerfectRangeProofWithKVProtocol { + pub fn init( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + max: u64, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + ) -> Result { + // TODO: Fx me, use min, max + Self::init_given_base( + rng, + value, + randomness, + max, + params.get_supported_base_for_range_proof(), + comm_key, + params, + ) + } + + pub fn init_given_base( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + max: u64, + base: u16, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + ) -> Result { + params.validate_base(base)?; + + let l = find_l(max, base) as usize; + + // Note: This is different from the paper as only a single `m` needs to be created. + let m = E::ScalarField::rand(rng); + let s = n_rand(rng, l).collect::>(); + let D = comm_key.commit_decomposed(base, &s, &m); + + let digits = padded_base_n_digits_as_field_elements(value, base, l); + let t = n_rand(rng, l).collect::>(); + let v = n_rand(rng, l).collect::>(); + let V = randomize_sigs!(&digits, &v, ¶ms); + let g1t = + multiply_field_elems_with_same_group_elem(params.bb_sig_params.g1.into_group(), &t); + let a = cfg_into_iter!(0..l) + .map(|i| (V[i] * -s[i]) + g1t[i]) + .collect::>(); + Ok(Self { + base, + digits, + r: randomness, + v, + V: E::G1::normalize_batch(&V), + a: E::G1::normalize_batch(&a), + D, + m, + s, + t, + }) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> CCSPerfectRangeWithKVProof { + gen_proof_perfect_range!(self, challenge, CCSPerfectRangeWithKVProof) + } + + pub fn compute_challenge_contribution( + V: &[E::G1Affine], + a: &[E::G1Affine], + D: &E::G1Affine, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + params.serialize_for_schnorr_protocol_for_kv(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + commitment.serialize_compressed(&mut writer)?; + for V_i in V { + V_i.serialize_compressed(&mut writer)?; + } + for a_i in a { + a_i.serialize_compressed(&mut writer)?; + } + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl CCSPerfectRangeWithKVProof { + pub fn verify( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + max: u64, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + secret_key: &SecretKey, + ) -> Result<(), SmcRangeProofError> { + self.non_crypto_validate(max, ¶ms)?; + check_commitment_for_prefect_range::( + self.base, + &self.z_sigma, + &self.z_r, + &self.D, + commitment, + challenge, + comm_key, + )?; + + let g1v = multiply_field_elems_with_same_group_elem( + params.bb_sig_params.g1.into_group(), + &self.z_v, + ); + + let sk_c = secret_key.0 * challenge; + for i in 0..self.V.len() { + // Check a[i] == V[i] * (challenge * secret_key - z_sigma[i]) + g1 * z_v[i] + if self.a[i] != (self.V[i] * (sk_c - self.z_sigma[i]) + g1v[i]).into_affine() { + return Err(SmcRangeProofError::InvalidRangeProof); + } + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + writer: W, + ) -> Result<(), SmcRangeProofError> { + CCSPerfectRangeProofWithKVProtocol::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + fn non_crypto_validate( + &self, + max: u64, + params: &SetMembershipCheckParams, + ) -> Result<(), SmcRangeProofError> { + params.validate_base(self.base)?; + let l = find_l(max, self.base) as usize; + assert_eq!(self.V.len(), l); + assert_eq!(self.a.len(), l); + assert_eq!(self.z_v.len(), l); + assert_eq!(self.z_sigma.len(), l); + Ok(()) + } +} + +fn find_l(max: u64, base: u16) -> u16 { + let l = max.ilog(base as u64); + let power = (base as u64).pow(l); + assert_eq!(power, max); + l as u16 +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccs_set_membership::setup::SetMembershipCheckParams; + use ark_bls12_381::{Bls12_381, Fr, G1Affine}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use schnorr_pok::compute_random_oracle_challenge; + + #[test] + fn range_proof_for_perfect_range() { + let mut rng = StdRng::seed_from_u64(0u64); + + for base in [2, 4, 8, 16] { + let (params, sk) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base); + params.verify().unwrap(); + + let comm_key = MemberCommitmentKey::::generate_using_rng(&mut rng); + + for _ in 0..5 { + for l in [10, 15] { + // TODO: Combine base and l in outer for loop + let max = (base as u64).pow(l); + let value = u64::rand(&mut rng) % max; + assert!(value < max); + let randomness = Fr::rand(&mut rng); + let commitment = comm_key.commit(&Fr::from(value), &randomness); + let protocol = CCSPerfectRangeProofWithKVProtocol::init_given_base( + &mut rng, value, randomness, max, base, &comm_key, ¶ms, + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution( + &commitment, + &comm_key, + ¶ms, + &mut chal_bytes_prover, + ) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution( + &commitment, + &comm_key, + ¶ms, + &mut chal_bytes_verifier, + ) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + assert_eq!(proof.V.len(), l as usize); + proof + .verify( + &commitment, + &challenge_verifier, + max, + &comm_key, + ¶ms, + &sk, + ) + .unwrap(); + } + } + } + } +} diff --git a/smc_range_proof/src/ccs_range_proof/mod.rs b/smc_range_proof/src/ccs_range_proof/mod.rs new file mode 100644 index 00000000..2eaaa176 --- /dev/null +++ b/smc_range_proof/src/ccs_range_proof/mod.rs @@ -0,0 +1,11 @@ +//! Range proof protocols as described in Fig.3 and section 4.4 of [Efficient Protocols for Set Membership and Range Proofs](https://link.springer.com/chapter/10.1007/978-3-540-89255-7_15) + +#[macro_use] +pub mod util; +pub mod arbitrary_range; +pub mod kv_arbitrary_range; +pub mod kv_perfect_range; +pub mod perfect_range; + +pub use arbitrary_range::{CCSArbitraryRangeProof, CCSArbitraryRangeProofProtocol}; +pub use kv_arbitrary_range::{CCSArbitraryRangeProofWithKVProtocol, CCSArbitraryRangeWithKVProof}; diff --git a/smc_range_proof/src/ccs_range_proof/perfect_range.rs b/smc_range_proof/src/ccs_range_proof/perfect_range.rs new file mode 100644 index 00000000..5cb3816b --- /dev/null +++ b/smc_range_proof/src/ccs_range_proof/perfect_range.rs @@ -0,0 +1,372 @@ +//! Range proof protocol as described in Fig.3 of the paper [Efficient Protocols for Set Membership and Range Proofs](https://link.springer.com/chapter/10.1007/978-3-540-89255-7_15). +//! Considers a perfect-range, i.e. range of the form `[0, u^l)` where `u` is the base and the upper bound is a power of the base. + +use crate::{ + ccs_set_membership::setup::SetMembershipCheckParamsWithPairing, common::MemberCommitmentKey, + error::SmcRangeProofError, +}; +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + AffineRepr, CurveGroup, +}; + +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, io::Write, ops::Mul, rand::RngCore, vec::Vec, UniformRand}; +use dock_crypto_utils::{misc::n_rand, msm::multiply_field_elems_with_same_group_elem}; + +use crate::common::padded_base_n_digits_as_field_elements; +use dock_crypto_utils::randomized_pairing_check::RandomizedPairingChecker; + +use crate::ccs_range_proof::util::{check_commitment_for_prefect_range, find_l}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSPerfectRangeProofProtocol { + pub base: u16, + pub digits: Vec, + pub r: E::ScalarField, + pub v: Vec, + pub V: Vec, + pub a: Vec>, + pub D: E::G1Affine, + pub m: E::ScalarField, + pub s: Vec, + pub t: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CCSPerfectRangeProof { + pub base: u16, + pub V: Vec, + pub a: Vec>, + pub D: E::G1Affine, + pub z_v: Vec, + pub z_sigma: Vec, + pub z_r: E::ScalarField, +} + +impl CCSPerfectRangeProofProtocol { + pub fn init( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result { + let params = params.into(); + Self::init_given_base( + rng, + value, + randomness, + max, + params.get_supported_base_for_range_proof(), + comm_key, + params, + ) + } + + pub fn init_given_base( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + max: u64, + base: u16, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result { + let params = params.into(); + + params.validate_base(base)?; + + let l = find_l(max, base) as usize; + + // Note: This is different from the paper as only a single `m` needs to be created. + let m = E::ScalarField::rand(rng); + let s = n_rand(rng, l).collect::>(); + let D = comm_key.commit_decomposed(base, &s, &m); + + let digits = padded_base_n_digits_as_field_elements(value, base, l); + let t = n_rand(rng, l).collect::>(); + let v = n_rand(rng, l).collect::>(); + let V = randomize_sigs!(&digits, &v, ¶ms); + let a = cfg_into_iter!(0..l) + .map(|i| { + E::pairing( + E::G1Prepared::from(V[i] * -s[i]), + params.bb_sig_params.g2_prepared.clone(), + ) + params.bb_sig_params.g1g2.mul(t[i]) + }) + .collect::>(); + Ok(Self { + base, + digits, + r: randomness, + v, + V: E::G1::normalize_batch(&V), + a, + D, + m, + s, + t, + }) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> CCSPerfectRangeProof { + gen_proof_perfect_range!(self, challenge, CCSPerfectRangeProof) + } + + pub fn compute_challenge_contribution( + V: &[E::G1Affine], + a: &[PairingOutput], + D: &E::G1Affine, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + params.serialize_for_schnorr_protocol(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + commitment.serialize_compressed(&mut writer)?; + for V_i in V { + V_i.serialize_compressed(&mut writer)?; + } + for a_i in a { + a_i.serialize_compressed(&mut writer)?; + } + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl CCSPerfectRangeProof { + pub fn verify( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + self.non_crypto_validate(max, ¶ms)?; + check_commitment_for_prefect_range::( + self.base, + &self.z_sigma, + &self.z_r, + &self.D, + commitment, + challenge, + comm_key, + )?; + + let (yc_sigma, lhs) = self.compute_for_pairing_check(challenge, ¶ms); + for i in 0..self.V.len() { + let rhs = E::pairing( + E::G1Prepared::from(self.V[i]), + E::G2Prepared::from(yc_sigma[i]), + ); + if lhs[i] != rhs { + return Err(SmcRangeProofError::InvalidRangeProof); + } + } + Ok(()) + } + + pub fn verify_given_randomized_pairing_checker( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + pairing_checker: &mut RandomizedPairingChecker, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + self.non_crypto_validate(max, ¶ms)?; + + check_commitment_for_prefect_range::( + self.base, + &self.z_sigma, + &self.z_r, + &self.D, + commitment, + challenge, + comm_key, + )?; + + let (yc_sigma, lhs) = self.compute_for_pairing_check(challenge, ¶ms); + + for i in 0..self.V.len() { + pairing_checker.add_multiple_sources_and_target(&[self.V[i]], &[yc_sigma[i]], &lhs[i]); + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + CCSPerfectRangeProofProtocol::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + fn non_crypto_validate( + &self, + max: u64, + params: &SetMembershipCheckParamsWithPairing, + ) -> Result<(), SmcRangeProofError> { + params.validate_base(self.base)?; + let l = find_l(max, self.base) as usize; + assert_eq!(self.V.len(), l); + assert_eq!(self.a.len(), l); + assert_eq!(self.z_v.len(), l); + assert_eq!(self.z_sigma.len(), l); + Ok(()) + } + + fn compute_for_pairing_check( + &self, + challenge: &E::ScalarField, + params: &SetMembershipCheckParamsWithPairing, + ) -> (Vec, Vec>) { + // y * c + let yc = params.bb_pk.0 * challenge; + // g2 * z_sigma + let g2_z_sigma = multiply_field_elems_with_same_group_elem( + params.bb_sig_params.g2.into_group(), + &self.z_sigma, + ); + let lhs = cfg_into_iter!(0..self.V.len()) + .map(|i| self.a[i] - (params.bb_sig_params.g1g2 * self.z_v[i])) + .collect::>(); + let yc_sigma = cfg_into_iter!(0..g2_z_sigma.len()) + .map(|i| yc - g2_z_sigma[i]) + .collect::>(); + (yc_sigma, lhs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccs_set_membership::setup::SetMembershipCheckParams; + use ark_bls12_381::{Bls12_381, Fr, G1Affine}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use schnorr_pok::compute_random_oracle_challenge; + + #[test] + fn range_proof_for_perfect_range() { + let mut rng = StdRng::seed_from_u64(0u64); + + for base in [2, 4, 8, 16] { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base); + params.verify().unwrap(); + + let params_with_pairing = SetMembershipCheckParamsWithPairing::from(params.clone()); + params_with_pairing.verify().unwrap(); + + let comm_key = MemberCommitmentKey::::generate_using_rng(&mut rng); + + for _ in 0..5 { + for l in [10, 15] { + // TODO: Combine base and l in outer for loop + let max = (base as u64).pow(l); + let value = u64::rand(&mut rng) % max; + assert!(value < max); + let randomness = Fr::rand(&mut rng); + let commitment = comm_key.commit(&Fr::from(value), &randomness); + let protocol = CCSPerfectRangeProofProtocol::init_given_base( + &mut rng, + value, + randomness, + max, + base, + &comm_key, + params_with_pairing.clone(), + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution( + &commitment, + &comm_key, + params_with_pairing.clone(), + &mut chal_bytes_prover, + ) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution( + &commitment, + &comm_key, + params_with_pairing.clone(), + &mut chal_bytes_verifier, + ) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + assert_eq!(proof.V.len(), l as usize); + proof + .verify( + &commitment, + &challenge_verifier, + max, + &comm_key, + params_with_pairing.clone(), + ) + .unwrap(); + + let mut pairing_checker = + RandomizedPairingChecker::new_using_rng(&mut rng, true); + proof + .verify_given_randomized_pairing_checker( + &commitment, + &challenge_verifier, + max, + &comm_key, + params_with_pairing.clone(), + &mut pairing_checker, + ) + .unwrap(); + } + } + } + } +} diff --git a/smc_range_proof/src/ccs_range_proof/util.rs b/smc_range_proof/src/ccs_range_proof/util.rs new file mode 100644 index 00000000..156e6e3b --- /dev/null +++ b/smc_range_proof/src/ccs_range_proof/util.rs @@ -0,0 +1,129 @@ +use crate::{common::MemberCommitmentKey, error::SmcRangeProofError}; +use ark_ec::{pairing::Pairing, CurveGroup}; +use dock_crypto_utils::ff::powers; + +pub(super) fn check_commitment_for_arbitrary_range( + base: u16, + z_sigma_min: &[E::ScalarField], + z_sigma_max: &[E::ScalarField], + z_r: &E::ScalarField, + D: &E::G1Affine, + min: u64, + max: u64, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + comm_key: &MemberCommitmentKey, +) -> Result<(), SmcRangeProofError> { + let l = find_l_greater_than(max, base) as u32; + + let comm_c = *commitment * challenge; + // Calculate powers of base once to avoid recomputing them again during commitment + + let base_powers = powers(&E::ScalarField::from(base), z_sigma_min.len() as u32); + + // Following 2 checks are different from the paper. The paper has typos where the exponent + // of `g` is not multiplied by the challenge + if (comm_c - comm_key.g * (E::ScalarField::from(min) * challenge) + + comm_key.commit_decomposed_given_base_powers(&base_powers, z_sigma_min, z_r)) + .into_affine() + != *D + { + return Err(SmcRangeProofError::InvalidRangeProof); + } + if (comm_c + + comm_key.g * (E::ScalarField::from((base as u64).pow(l) - max) * challenge) + + comm_key.commit_decomposed_given_base_powers(&base_powers, z_sigma_max, z_r)) + .into_affine() + != *D + { + return Err(SmcRangeProofError::InvalidRangeProof); + } + Ok(()) +} + +pub(super) fn check_commitment_for_prefect_range( + base: u16, + z_sigma: &[E::ScalarField], + z_r: &E::ScalarField, + D: &E::G1Affine, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + comm_key: &MemberCommitmentKey, +) -> Result<(), SmcRangeProofError> { + if (*commitment * challenge + comm_key.commit_decomposed(base, z_sigma, z_r)).into_affine() + != *D + { + return Err(SmcRangeProofError::InvalidRangeProof); + } + Ok(()) +} + +pub fn find_l_greater_than(max: u64, base: u16) -> u16 { + let l = max.ilog(base as u64); + if (base as u64).pow(l) > max { + l as u16 + } else { + l as u16 + 1 + } +} + +pub fn find_l(max: u64, base: u16) -> u16 { + let l = max.ilog(base as u64); + let power = (base as u64).pow(l); + assert_eq!(power, max); + l as u16 +} + +#[macro_export] +macro_rules! gen_proof_perfect_range { + ($self: ident, $challenge: ident, $proof: ident) => {{ + let z_v = cfg_into_iter!(0..$self.V.len()) + .map(|i| $self.t[i] - ($self.v[i] * $challenge)) + .collect::>(); + let z_sigma = cfg_into_iter!(0..$self.V.len()) + .map(|i| $self.s[i] - ($self.digits[i] * $challenge)) + .collect::>(); + let z_r = $self.m - ($self.r * $challenge); + $proof { + base: $self.base, + V: $self.V, + a: $self.a, + D: $self.D, + z_v, + z_sigma, + z_r, + } + }}; +} + +#[macro_export] +macro_rules! gen_proof_arbitrary_range { + ($self: ident, $challenge: ident, $proof: ident) => {{ + let z_v_min = cfg_into_iter!(0..$self.V_min.len()) + .map(|i| $self.t_min[i] - ($self.v_min[i] * $challenge)) + .collect::>(); + let z_v_max = cfg_into_iter!(0..$self.V_max.len()) + .map(|i| $self.t_max[i] - ($self.v_max[i] * $challenge)) + .collect::>(); + let z_sigma_min = cfg_into_iter!(0..$self.V_min.len()) + .map(|i| $self.s[i] - ($self.digits_min[i] * $challenge)) + .collect::>(); + let z_sigma_max = cfg_into_iter!(0..$self.V_max.len()) + .map(|i| $self.s[i] - ($self.digits_max[i] * $challenge)) + .collect::>(); + let z_r = $self.m - ($self.r * $challenge); + $proof { + base: $self.base, + V_min: $self.V_min, + V_max: $self.V_max, + a_min: $self.a_min, + a_max: $self.a_max, + D: $self.D, + z_v_min, + z_v_max, + z_sigma_min, + z_sigma_max, + z_r, + } + }}; +} diff --git a/smc_range_proof/src/ccs_set_membership/batch_members.rs b/smc_range_proof/src/ccs_set_membership/batch_members.rs new file mode 100644 index 00000000..81041655 --- /dev/null +++ b/smc_range_proof/src/ccs_set_membership/batch_members.rs @@ -0,0 +1,302 @@ +//! Check membership of a batch of elements in the set + +use crate::{ + ccs_set_membership::setup::SetMembershipCheckParamsWithPairing, common::MemberCommitmentKey, + error::SmcRangeProofError, +}; +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + AffineRepr, CurveGroup, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, io::Write, ops::Mul, rand::RngCore, vec::Vec}; +use dock_crypto_utils::misc::n_rand; + +use dock_crypto_utils::msm::multiply_field_elems_with_same_group_elem; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetMembershipBatchCheckProtocol { + pub members: Vec, + pub r: Vec, + pub v: Vec, + pub V: Vec, + pub a: Vec>, + pub D: Vec, + pub m: Vec, + pub s: Vec, + pub t: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetMembershipBatchCheckProof { + pub V: Vec, + pub a: Vec>, + pub D: Vec, + pub z_v: Vec, + pub z_sigma: Vec, + pub z_r: Vec, +} + +impl SetMembershipBatchCheckProtocol { + pub fn init( + rng: &mut R, + members: Vec, + r: Vec, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result { + assert_eq!(members.len(), r.len()); + let params = params.into(); + let v = n_rand(rng, members.len()).collect::>(); + let V = randomize_sigs!(&members, &v, ¶ms); + let m = n_rand(rng, members.len()).collect::>(); + let s = n_rand(rng, members.len()).collect::>(); + let t = n_rand(rng, members.len()).collect::>(); + + // Note: This can be optimized for larger batches by using a table of comm_key elements + let D = cfg_into_iter!(0..members.len()) + .map(|i| comm_key.commit(&s[i], &m[i])) + .collect::>(); + let a = cfg_into_iter!(0..members.len()) + .map(|i| { + E::pairing( + E::G1Prepared::from(V[i] * -s[i]), + params.bb_sig_params.g2_prepared.clone(), + ) + params.bb_sig_params.g1g2.mul(t[i]) + }) + .collect::>(); + Ok(Self { + members, + r, + v, + V: E::G1::normalize_batch(&V), + a, + D, + m, + s, + t, + }) + } + + pub fn challenge_contribution( + &self, + commitments: &[E::G1Affine], + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V, + &self.a, + &self.D, + commitments, + comm_key, + params, + writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> SetMembershipBatchCheckProof { + let z_v = cfg_into_iter!(0..self.V.len()) + .map(|i| self.t[i] - (self.v[i] * challenge)) + .collect::>(); + let z_sigma = cfg_into_iter!(0..self.V.len()) + .map(|i| self.s[i] - (self.members[i] * challenge)) + .collect::>(); + let z_r = cfg_into_iter!(0..self.V.len()) + .map(|i| self.m[i] - (self.r[i] * challenge)) + .collect::>(); + SetMembershipBatchCheckProof { + V: self.V, + a: self.a, + D: self.D, + z_v, + z_sigma, + z_r, + } + } + + pub fn compute_challenge_contribution( + V: &[E::G1Affine], + a: &[PairingOutput], + D: &[E::G1Affine], + commitments: &[E::G1Affine], + comm_key: &MemberCommitmentKey, + params: impl Into>, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + params.serialize_for_schnorr_protocol(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + for comm in commitments { + comm.serialize_compressed(&mut writer)?; + } + for V_i in V { + V_i.serialize_compressed(&mut writer)?; + } + for a_i in a { + a_i.serialize_compressed(&mut writer)?; + } + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl SetMembershipBatchCheckProof { + pub fn verify( + &self, + commitments: &[E::G1Affine], + challenge: &E::ScalarField, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + // TODO: Check size of vectors in proof + assert_eq!(self.V.len(), commitments.len()); + assert_eq!(self.a.len(), commitments.len()); + assert_eq!(self.z_v.len(), commitments.len()); + assert_eq!(self.z_sigma.len(), commitments.len()); + + // Note: Following can be optimized for larger batches by taking a random linear combination of the following + for i in 0..commitments.len() { + if (commitments[i] * challenge + comm_key.commit(&self.z_sigma[i], &self.z_r[i])) + .into_affine() + != self.D[i] + { + return Err(SmcRangeProofError::InvalidSetMembershipProof); + } + } + + // y * c + let yc = params.bb_pk.0 * challenge; + // g2 * z_sigma + let g2_z_sigma = multiply_field_elems_with_same_group_elem( + params.bb_sig_params.g2.into_group(), + &self.z_sigma, + ); + + // TODO: Allow verifying with randomized pairing checker + for i in 0..commitments.len() { + let lhs = self.a[i] - (params.bb_sig_params.g1g2 * self.z_v[i]); + let rhs = E::pairing( + E::G1Prepared::from(self.V[i]), + E::G2Prepared::from(yc - g2_z_sigma[i]), + ); + if lhs != rhs { + return Err(SmcRangeProofError::InvalidSetMembershipProof); + } + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitments: &[E::G1Affine], + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + SetMembershipBatchCheckProtocol::compute_challenge_contribution( + &self.V, + &self.a, + &self.D, + commitments, + comm_key, + params, + writer, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccs_set_membership::setup::{ + SetMembershipCheckParams, SetMembershipCheckParamsWithPairing, + }; + use ark_bls12_381::{Bls12_381, Fr}; + use ark_std::rand::{rngs::StdRng, SeedableRng}; + use blake2::Blake2b512; + use dock_crypto_utils::misc::n_rand; + use schnorr_pok::compute_random_oracle_challenge; + use std::time::Instant; + + #[test] + fn membership_batch_check() { + let mut rng = StdRng::seed_from_u64(0u64); + + let set_size = 20; + let batch_size = 10; + + let set = n_rand(&mut rng, set_size).collect::>(); + let (params, _) = SetMembershipCheckParams::::new::<_, Blake2b512>( + &mut rng, + b"test", + set.clone(), + ); + params.verify().unwrap(); + + let params_with_pairing = SetMembershipCheckParamsWithPairing::from(params.clone()); + params_with_pairing.verify().unwrap(); + + let comm_key = MemberCommitmentKey::generate_using_rng(&mut rng); + + let members = (0..batch_size).map(|i| set[i]).collect::>(); + let randomness = n_rand(&mut rng, batch_size).collect::>(); + let commitments = (0..batch_size) + .map(|i| comm_key.commit(&members[i], &randomness[i])) + .collect::>(); + + let protocol = SetMembershipBatchCheckProtocol::init( + &mut rng, + members, + randomness, + &comm_key, + params_with_pairing.clone(), + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution( + &commitments, + &comm_key, + params_with_pairing.clone(), + &mut chal_bytes_prover, + ) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution( + &commitments, + &comm_key, + params_with_pairing.clone(), + &mut chal_bytes_verifier, + ) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + let start = Instant::now(); + proof + .verify( + &commitments, + &challenge_verifier, + &comm_key, + params_with_pairing.clone(), + ) + .unwrap(); + println!("Time to verify={:?}", start.elapsed()); + } +} diff --git a/smc_range_proof/src/ccs_set_membership/kv_single.rs b/smc_range_proof/src/ccs_set_membership/kv_single.rs new file mode 100644 index 00000000..7c48874a --- /dev/null +++ b/smc_range_proof/src/ccs_set_membership/kv_single.rs @@ -0,0 +1,212 @@ +//! Check membership of a single element in the set using keyed-verification, i.e. verifier knows +//! the secret key for BB sig + +use crate::{ + bb_sig::SecretKey, ccs_set_membership::setup::SetMembershipCheckParams, + common::MemberCommitmentKey, error::SmcRangeProofError, +}; +use ark_ec::{pairing::Pairing, CurveGroup}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{io::Write, rand::RngCore, vec::Vec, UniformRand}; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetMembershipCheckWithKVProtocol { + /// The set member that is committed + pub member: E::ScalarField, + /// Randomness for the commitment + pub r: E::ScalarField, + /// Randomness used to randomize the signature + pub v: E::ScalarField, + /// The randomized signature over the committed member + pub V: E::G1Affine, + pub a: E::G1Affine, + pub D: E::G1Affine, + pub m: E::ScalarField, + pub s: E::ScalarField, + pub t: E::ScalarField, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetMembershipCheckWithKVProof { + /// The randomized signature over the committed member + pub V: E::G1Affine, + pub a: E::G1Affine, + pub D: E::G1Affine, + pub z_v: E::ScalarField, + pub z_sigma: E::ScalarField, + pub z_r: E::ScalarField, +} + +impl SetMembershipCheckWithKVProtocol { + pub fn init( + rng: &mut R, + member: E::ScalarField, + r: E::ScalarField, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + ) -> Result { + let v = E::ScalarField::rand(rng); + let m = E::ScalarField::rand(rng); + let t = E::ScalarField::rand(rng); + let s = E::ScalarField::rand(rng); + let V = params.get_sig_for_member(&member)?.0 * v; + let D = comm_key.commit(&s, &m); + // a = V * -s + g1 * t + let a = (V * -s + params.bb_sig_params.g1 * t).into_affine(); + Ok(Self { + member, + r, + v, + V: V.into(), + a, + D: D.into(), + m, + s, + t, + }) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> SetMembershipCheckWithKVProof { + let z_v = self.t - (self.v * challenge); + let z_r = self.m - (self.r * challenge); + let z_sigma = self.s - (self.member * challenge); + SetMembershipCheckWithKVProof { + V: self.V, + a: self.a, + D: self.D, + z_v, + z_r, + z_sigma, + } + } + + pub fn compute_challenge_contribution( + V: &E::G1Affine, + a: &E::G1Affine, + D: &E::G1Affine, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + params.serialize_for_schnorr_protocol_for_kv(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + commitment.serialize_compressed(&mut writer)?; + V.serialize_compressed(&mut writer)?; + a.serialize_compressed(&mut writer)?; + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl SetMembershipCheckWithKVProof { + pub fn verify( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + secret_key: &SecretKey, + ) -> Result<(), SmcRangeProofError> { + // Check commitment * challenge + g * z_sigma + h * z_r == D + if (*commitment * challenge + comm_key.commit(&self.z_sigma, &self.z_r)).into_affine() + != self.D + { + return Err(SmcRangeProofError::InvalidSetMembershipProof); + } + // Check a == V * (challenge * secret_key - z_sigma) + g1 * z_v + if self.a + != (self.V * (secret_key.0 * challenge - self.z_sigma) + + params.bb_sig_params.g1 * self.z_v) + .into_affine() + { + return Err(SmcRangeProofError::InvalidSetMembershipProof); + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + writer: W, + ) -> Result<(), SmcRangeProofError> { + SetMembershipCheckWithKVProtocol::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{bb_sig::SignatureParams, ccs_set_membership::setup::SetMembershipCheckParams}; + use ark_bls12_381::{Bls12_381, Fr}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use dock_crypto_utils::misc::n_rand; + use schnorr_pok::compute_random_oracle_challenge; + + #[test] + fn membership_check() { + let mut rng = StdRng::seed_from_u64(0u64); + + let set_size = 10; + let sig_params = SignatureParams::::generate_using_rng(&mut rng); + + let set = n_rand(&mut rng, set_size).collect::>(); + let (params, sk) = + SetMembershipCheckParams::new_given_sig_params(&mut rng, set.clone(), sig_params); + params.verify().unwrap(); + + let comm_key = MemberCommitmentKey::generate_using_rng(&mut rng); + let member = set[3].clone(); + let randomness = Fr::rand(&mut rng); + let commitment = comm_key.commit(&member, &randomness); + + let protocol = SetMembershipCheckWithKVProtocol::init( + &mut rng, member, randomness, &comm_key, ¶ms, + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution(&commitment, &comm_key, ¶ms, &mut chal_bytes_prover) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution(&commitment, &comm_key, ¶ms, &mut chal_bytes_verifier) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + proof + .verify(&commitment, &challenge_verifier, &comm_key, ¶ms, &sk) + .unwrap(); + } +} diff --git a/smc_range_proof/src/ccs_set_membership/mod.rs b/smc_range_proof/src/ccs_set_membership/mod.rs new file mode 100644 index 00000000..8a679f27 --- /dev/null +++ b/smc_range_proof/src/ccs_set_membership/mod.rs @@ -0,0 +1,6 @@ +//! Set membership protocol using BB signature. Described in Fig.1 of [Efficient Protocols for Set Membership and Range Proofs](https://link.springer.com/chapter/10.1007/978-3-540-89255-7_15) + +pub mod batch_members; +pub mod kv_single; +pub mod setup; +pub mod single_member; diff --git a/smc_range_proof/src/ccs_set_membership/setup.rs b/smc_range_proof/src/ccs_set_membership/setup.rs new file mode 100644 index 00000000..0789433b --- /dev/null +++ b/smc_range_proof/src/ccs_set_membership/setup.rs @@ -0,0 +1,205 @@ +use crate::{ + bb_sig::{PublicKeyG2, SecretKey, SignatureG1, SignatureParams, SignatureParamsWithPairing}, + error::SmcRangeProofError, +}; +use ark_ec::{pairing::Pairing, AffineRepr}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, cfg_iter, io::Write, rand::RngCore, vec::Vec}; +use digest::Digest; +use dock_crypto_utils::msm::multiply_field_elems_with_same_group_elem; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +/// Public params to prove set membership in a specific set. It contains the BB sig params, public key, the set and the +/// BB signatures on each set member. +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetMembershipCheckParams { + pub bb_sig_params: SignatureParams, + pub bb_pk: PublicKeyG2, + pub set: Vec, + pub sigs: Vec>, +} + +/// Same as `SetMembershipCheckParams` but contains the precomputed pairing for a more efficient protocol execution +// Note: PartialEq cannot be implemented because of `SignatureParamsWithPairing` even when `SignatureParamsWithPairing` implements PartialEq. +// This is because of G2Prepared +// #[derive(Clone, PartialEq, Debug, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetMembershipCheckParamsWithPairing { + pub bb_sig_params: SignatureParamsWithPairing, + pub bb_pk: PublicKeyG2, + pub set: Vec, + pub sigs: Vec>, +} + +impl From> for SetMembershipCheckParamsWithPairing { + fn from(params: SetMembershipCheckParams) -> Self { + let bb_sig_params = SignatureParamsWithPairing::from(params.bb_sig_params.clone()); + Self { + bb_sig_params, + bb_pk: params.bb_pk, + set: params.set, + sigs: params.sigs, + } + } +} + +macro_rules! impl_common_functions { + ($base_function_name: ident, $get_sig_function_name: ident, $serialize_function_name: ident) => { + /// Check if the given base can be used with these params + pub fn $base_function_name(&self, base: u16) -> Result<(), SmcRangeProofError> { + // If params support a larger base, then its fine. + if self.get_supported_base_for_range_proof() < base { + return Err(SmcRangeProofError::UnsupportedBase( + base, + self.get_supported_base_for_range_proof(), + )); + } + Ok(()) + } + + /// Get signature for the given member + pub fn $get_sig_function_name( + &self, + member: &E::ScalarField, + ) -> Result<&SignatureG1, SmcRangeProofError> { + let member_idx = match self.set.iter().position(|&s| s == *member) { + Some(m) => m, + None => return Err(SmcRangeProofError::CannotFindElementInSet), + }; + Ok(&self.sigs[member_idx]) + } + + pub fn $serialize_function_name( + &self, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + self.bb_sig_params.g1.serialize_compressed(&mut writer)?; + self.bb_sig_params.g2.serialize_compressed(&mut writer)?; + self.bb_pk.0.serialize_compressed(&mut writer)?; + Ok(()) + } + }; +} + +impl SetMembershipCheckParams { + /// Create new params for a given set and return the BB secret key. The secret key should be discarded. `label` is to + /// generate the BB sig params + pub fn new( + rng: &mut R, + label: &[u8], + set: Vec, + ) -> (Self, SecretKey) { + let sig_params = SignatureParams::new::(label); + Self::new_given_sig_params(rng, set, sig_params) + } + + /// Create new params when the set-membership check protocol is used for a range proof. The set in + /// this case consists of elements `(0, 1, 2, 3, ..., base-1)` + pub fn new_for_range_proof( + rng: &mut R, + label: &[u8], + base: u16, + ) -> (Self, SecretKey) { + let sig_params = SignatureParams::new::(label); + Self::new_for_range_proof_given_sig_params(rng, base, sig_params) + } + + /// Same as `Self::new` except that it accepts already created BB sig params + pub fn new_given_sig_params( + rng: &mut R, + set: Vec, + sig_params: SignatureParams, + ) -> (Self, SecretKey) { + let sk = SecretKey::new(rng); + let pk = PublicKeyG2::generate_using_secret_key(&sk, &sig_params); + let sigs = cfg_iter!(set) + .map(|i| SignatureG1::new(i, &sk, &sig_params)) + .collect(); + ( + Self { + bb_sig_params: sig_params, + bb_pk: pk, + set, + sigs, + }, + sk, + ) + } + + /// Same as `Self::new_for_range_proof` except that it accepts already created BB sig params + pub fn new_for_range_proof_given_sig_params( + rng: &mut R, + base: u16, + sig_params: SignatureParams, + ) -> (Self, SecretKey) { + let set = cfg_into_iter!(0..base) + .map(|i| E::ScalarField::from(i)) + .collect(); + Self::new_given_sig_params(rng, set, sig_params) + } + + /// Verify each signature in the params + pub fn verify(&self) -> Result<(), SmcRangeProofError> { + let params = SetMembershipCheckParamsWithPairing::from(self.clone()); + params.verify() + } + + /// No. of set members these params support + pub fn supported_set_size(&self) -> usize { + self.set.len() + } + + /// The base these params supported for range-proof. + pub fn get_supported_base_for_range_proof(&self) -> u16 { + self.supported_set_size() as u16 + } + + pub fn serialize_for_schnorr_protocol_for_kv( + &self, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + self.bb_sig_params.g1.serialize_compressed(&mut writer)?; + Ok(()) + } + + impl_common_functions!( + validate_base, + get_sig_for_member, + serialize_for_schnorr_protocol + ); +} + +impl SetMembershipCheckParamsWithPairing { + pub fn verify(&self) -> Result<(), SmcRangeProofError> { + if self.sigs.len() != self.set.len() { + return Err(SmcRangeProofError::InvalidSetMembershipSetup); + } + let gm = multiply_field_elems_with_same_group_elem( + self.bb_sig_params.g2.into_group(), + &self.set, + ); + let r = gm.iter().zip(self.sigs.iter()).all(|(gm_i, sig)| { + sig.is_non_zero() && (E::pairing(sig.0, self.bb_pk.0 + gm_i) == self.bb_sig_params.g1g2) + }); + if !r { + return Err(SmcRangeProofError::InvalidSetMembershipSetup); + } + Ok(()) + } + + pub fn supported_set_size(&self) -> usize { + self.set.len() + } + + pub fn get_supported_base_for_range_proof(&self) -> u16 { + self.supported_set_size() as u16 + } + + impl_common_functions!( + validate_base, + get_sig_for_member, + serialize_for_schnorr_protocol + ); +} diff --git a/smc_range_proof/src/ccs_set_membership/single_member.rs b/smc_range_proof/src/ccs_set_membership/single_member.rs new file mode 100644 index 00000000..48f5f8dc --- /dev/null +++ b/smc_range_proof/src/ccs_set_membership/single_member.rs @@ -0,0 +1,251 @@ +//! Check membership of a single element in the set + +use crate::{ + ccs_set_membership::setup::SetMembershipCheckParamsWithPairing, common::MemberCommitmentKey, + error::SmcRangeProofError, +}; +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + CurveGroup, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{io::Write, ops::Mul, rand::RngCore, vec::Vec, UniformRand}; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetMembershipCheckProtocol { + /// The set member that is committed + pub member: E::ScalarField, + /// Randomness for the commitment + pub r: E::ScalarField, + /// Randomness used to randomize the signature + pub v: E::ScalarField, + /// The randomized signature over the committed member + pub V: E::G1Affine, + pub a: PairingOutput, + pub D: E::G1Affine, + pub m: E::ScalarField, + pub s: E::ScalarField, + pub t: E::ScalarField, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct SetMembershipCheckProof { + /// The randomized signature over the committed member + pub V: E::G1Affine, + pub a: PairingOutput, + pub D: E::G1Affine, + pub z_v: E::ScalarField, + pub z_sigma: E::ScalarField, + pub z_r: E::ScalarField, +} + +impl SetMembershipCheckProtocol { + pub fn init( + rng: &mut R, + member: E::ScalarField, + r: E::ScalarField, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result { + let params = params.into(); + let v = E::ScalarField::rand(rng); + let m = E::ScalarField::rand(rng); + let t = E::ScalarField::rand(rng); + let s = E::ScalarField::rand(rng); + let V = params.get_sig_for_member(&member)?.0 * v; + let D = comm_key.commit(&s, &m); + // a = e(V, g2) * -s + e(g1, g2) * -t = e(V * -s, g2) + e(g1, g2) * -t + let a = E::pairing( + E::G1Prepared::from(V * -s), + params.bb_sig_params.g2_prepared.clone(), + ) + params.bb_sig_params.g1g2.mul(t); + Ok(Self { + member, + r, + v, + V: V.into(), + a, + D: D.into(), + m, + s, + t, + }) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> SetMembershipCheckProof { + let z_v = self.t - (self.v * challenge); + let z_r = self.m - (self.r * challenge); + let z_sigma = self.s - (self.member * challenge); + SetMembershipCheckProof { + V: self.V, + a: self.a, + D: self.D, + z_v, + z_r, + z_sigma, + } + } + + pub fn compute_challenge_contribution( + V: &E::G1Affine, + a: &PairingOutput, + D: &E::G1Affine, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + params.serialize_for_schnorr_protocol(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + commitment.serialize_compressed(&mut writer)?; + V.serialize_compressed(&mut writer)?; + a.serialize_compressed(&mut writer)?; + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl SetMembershipCheckProof { + pub fn verify( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result<(), SmcRangeProofError> { + // Check commitment * challenge + g * z_sigma + h * z_r == D + if (*commitment * challenge + comm_key.commit(&self.z_sigma, &self.z_r)).into_affine() + != self.D + { + return Err(SmcRangeProofError::InvalidSetMembershipProof); + } + let params = params.into(); + // Check a == e(V, y) * challenge + e(V, g2) * -z_sigma + e(g1, g2) * z_v + // => a == e(V, y*challenge - g2 * z_sigma) + e(g1, g2) * z_v + // => a - e(g1, g2) * z_v == e(V, y*challenge - g2 * z_sigma) + let lhs = self.a - (params.bb_sig_params.g1g2 * self.z_v); + let rhs = E::pairing( + E::G1Prepared::from(self.V), + E::G2Prepared::from( + (params.bb_pk.0 * challenge) - (params.bb_sig_params.g2 * self.z_sigma), + ), + ); + if lhs != rhs { + return Err(SmcRangeProofError::InvalidSetMembershipProof); + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + SetMembershipCheckProtocol::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + bb_sig::SignatureParams, + ccs_set_membership::setup::{ + SetMembershipCheckParams, SetMembershipCheckParamsWithPairing, + }, + }; + use ark_bls12_381::{Bls12_381, Fr}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use dock_crypto_utils::misc::n_rand; + use schnorr_pok::compute_random_oracle_challenge; + + #[test] + fn membership_check() { + let mut rng = StdRng::seed_from_u64(0u64); + + let set_size = 10; + let sig_params = SignatureParams::::generate_using_rng(&mut rng); + + let set = n_rand(&mut rng, set_size).collect::>(); + let (params, _) = + SetMembershipCheckParams::new_given_sig_params(&mut rng, set.clone(), sig_params); + params.verify().unwrap(); + + let params_with_pairing = SetMembershipCheckParamsWithPairing::from(params.clone()); + params_with_pairing.verify().unwrap(); + + let comm_key = MemberCommitmentKey::generate_using_rng(&mut rng); + let member = set[3].clone(); + let randomness = Fr::rand(&mut rng); + let commitment = comm_key.commit(&member, &randomness); + + let protocol = SetMembershipCheckProtocol::init( + &mut rng, + member, + randomness, + &comm_key, + params_with_pairing.clone(), + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution( + &commitment, + &comm_key, + params_with_pairing.clone(), + &mut chal_bytes_prover, + ) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution( + &commitment, + &comm_key, + params_with_pairing.clone(), + &mut chal_bytes_verifier, + ) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + proof + .verify( + &commitment, + &challenge_verifier, + &comm_key, + params_with_pairing.clone(), + ) + .unwrap(); + } +} diff --git a/smc_range_proof/src/cls_range_proof/kv_range_proof.rs b/smc_range_proof/src/cls_range_proof/kv_range_proof.rs new file mode 100644 index 00000000..4445b9fc --- /dev/null +++ b/smc_range_proof/src/cls_range_proof/kv_range_proof.rs @@ -0,0 +1,435 @@ +//! Same as CLS range range proof protocol but does Keyed-Verification, i.e the verifies knows the +//! secret key of the BB-sig + +use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, format, io::Write, rand::RngCore, vec::Vec, UniformRand}; + +use crate::{ + ccs_set_membership::setup::SetMembershipCheckParams, common::MemberCommitmentKey, + error::SmcRangeProofError, +}; +use dock_crypto_utils::misc::n_rand; + +use crate::prelude::SecretKey; +use dock_crypto_utils::{ff::inner_product, msm::multiply_field_elems_with_same_group_elem}; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::cls_range_proof::util::{ + check_commitment, find_number_of_digits, find_sumset_boundaries, + get_range_and_randomness_multiple, solve_linear_equations, +}; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CLSRangeProofWithKVProtocol { + pub base: u16, + pub digits: Vec, + pub r: E::ScalarField, + pub v: Vec, + pub V: Vec, + pub a: Vec, + pub D: E::G1Affine, + pub m: E::ScalarField, + pub s: Vec, + pub t: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CLSRangeProofWithKV { + pub base: u16, + pub V: Vec, + pub a: Vec, + pub D: E::G1Affine, + pub z_v: Vec, + pub z_sigma: Vec, + pub z_r: E::ScalarField, +} + +impl CLSRangeProofWithKVProtocol { + pub fn init( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + ) -> Result { + Self::init_given_base( + rng, + value, + randomness, + min, + max, + params.get_supported_base_for_range_proof(), + comm_key, + params, + ) + } + + pub fn init_given_base( + rng: &mut R, + mut value: u64, + randomness: E::ScalarField, + min: u64, + max: u64, + base: u16, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + ) -> Result { + if min > value { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "value={} should be >= min={}", + value, min + ))); + } + if value >= max { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "value={} should be < max={}", + value, max + ))); + } + + params.validate_base(base)?; + + let (range, randomness_multiple) = get_range_and_randomness_multiple(base, min, max); + value = value - min; + if randomness_multiple != 1 { + value = value * (base - 1) as u64; + } + + let l = find_number_of_digits(range, base); + let G = find_sumset_boundaries(range, base, l); + + // Note: This is different from the paper as only a single `m` needs to be created. + let m = E::ScalarField::rand(rng); + let s = n_rand(rng, l).collect::>(); + let D = comm_key.commit( + &inner_product( + &s, + &cfg_into_iter!(G.clone()) + .map(|G_i| E::ScalarField::from(G_i)) + .collect::>(), + ), + &(m * E::ScalarField::from(randomness_multiple)), + ); + + if let Some(digits) = solve_linear_equations(value, &G, base) { + // Following is only for debugging + // let mut expected = 0_u64; + // for j in 0..digits.len() { + // assert!(digits[j] < base); + // expected += digits[j] as u64 * G[j]; + // } + // assert_eq!(expected, value); + + let digits = cfg_into_iter!(digits) + .map(|d| E::ScalarField::from(d)) + .collect::>(); + let t = n_rand(rng, l).collect::>(); + let v = n_rand(rng, l).collect::>(); + let V = randomize_sigs!(&digits, &v, ¶ms); + + let g1t = + multiply_field_elems_with_same_group_elem(params.bb_sig_params.g1.into_group(), &t); + let a = cfg_into_iter!(0..l as usize) + .map(|i| (V[i] * -s[i]) + g1t[i]) + .collect::>(); + Ok(Self { + base, + digits, + r: randomness, + v, + V: E::G1::normalize_batch(&V), + a: E::G1::normalize_batch(&a), + D, + m, + s, + t, + }) + } else { + Err(SmcRangeProofError::InvalidRange(range, base)) + } + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> CLSRangeProofWithKV { + gen_proof!(self, challenge, CLSRangeProofWithKV) + } + + pub fn compute_challenge_contribution( + V: &[E::G1Affine], + a: &[E::G1Affine], + D: &E::G1Affine, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + params.serialize_for_schnorr_protocol_for_kv(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + commitment.serialize_compressed(&mut writer)?; + for V_i in V { + V_i.serialize_compressed(&mut writer)?; + } + for a_i in a { + a_i.serialize_compressed(&mut writer)?; + } + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl CLSRangeProofWithKV { + pub fn verify( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + secret_key: &SecretKey, + ) -> Result<(), SmcRangeProofError> { + self.non_crypto_validate(min, max, ¶ms)?; + + check_commitment::( + self.base, + &self.z_sigma, + &self.z_r, + &self.D, + min, + max, + commitment, + challenge, + comm_key, + )?; + + let g1v = multiply_field_elems_with_same_group_elem( + params.bb_sig_params.g1.into_group(), + &self.z_v, + ); + + let sk_c = secret_key.0 * challenge; + + for i in 0..self.V.len() { + if self.a[i] != (self.V[i] * (sk_c - self.z_sigma[i]) + g1v[i]).into_affine() { + return Err(SmcRangeProofError::InvalidRangeProof); + } + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: &SetMembershipCheckParams, + writer: W, + ) -> Result<(), SmcRangeProofError> { + CLSRangeProofWithKVProtocol::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + fn non_crypto_validate( + &self, + min: u64, + max: u64, + params: &SetMembershipCheckParams, + ) -> Result<(), SmcRangeProofError> { + params.validate_base(self.base)?; + if min >= max { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "min={} should be < max={}", + min, max + ))); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ccs_set_membership::setup::SetMembershipCheckParams; + use ark_bls12_381::{Bls12_381, Fr, G1Affine}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use schnorr_pok::compute_random_oracle_challenge; + use std::time::{Duration, Instant}; + + #[test] + fn sumsets_check() { + let mut rng = StdRng::seed_from_u64(0u64); + + fn check(max: u64, g: &[u64], base: u16) { + for i in max..=max { + let sigma = solve_linear_equations(max, g, base).unwrap(); + assert_eq!(sigma.len(), g.len()); + let mut expected = 0_u64; + for j in 0..sigma.len() { + assert!(sigma[j] < base); + expected += sigma[j] as u64 * g[j]; + } + assert_eq!(expected, i); + } + } + + let mut runs = 0; + let start = Instant::now(); + for base in [3, 4, 5, 8, 10, 11, 14, 16] { + for _ in 0..10 { + // let max = ((u16::rand(&mut rng) as u64)) * (base as u64 - 1); + // let max = ((u16::rand(&mut rng) as u64) << 4) * (base as u64 - 1); + let max = ((u16::rand(&mut rng) as u64) >> 4) * (base as u64 - 1); + // while (max % (base as u64 - 1)) != 0 { + // max = u64::rand(&mut rng); + // } + let l = find_number_of_digits(max, base); + let G = find_sumset_boundaries(max, base, l); + println!("Starting for base={} and max={}", base, max); + let start_check = Instant::now(); + check(max, &G, base); + println!( + "Check done for base={} and max={} in {:?}", + base, + max, + start_check.elapsed() + ); + runs += 1; + } + } + println!("Time for {} runs: {:?}", runs, start.elapsed()); + } + + #[test] + fn cls_range_proof() { + let mut rng = StdRng::seed_from_u64(0u64); + let mut proving_time = Duration::default(); + let mut verifying_time = Duration::default(); + let mut num_proofs = 0; + + for base in [2, 4, 8, 16] { + let (params, sk) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base); + params.verify().unwrap(); + + let comm_key = MemberCommitmentKey::::generate_using_rng(&mut rng); + + for _ in 0..5 { + let mut a = [ + u16::rand(&mut rng) as u64, + u16::rand(&mut rng) as u64, + u16::rand(&mut rng) as u64, + ]; + a.sort(); + let min = a[0]; + let max = a[2]; + let value = a[1]; + assert!(value > min); + assert!(value < max); + let randomness = Fr::rand(&mut rng); + let commitment = comm_key.commit(&Fr::from(value), &randomness); + + // Params with incorrect base should fail + let params_with_smaller_base = { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base - 1); + params.verify().unwrap(); + params + }; + assert!(CLSRangeProofWithKVProtocol::init_given_base( + &mut rng, + value, + randomness, + min, + max, + base, + &comm_key, + ¶ms_with_smaller_base, + ) + .is_err()); + + // min > max should fail + assert!(CLSRangeProofWithKVProtocol::init_given_base( + &mut rng, value, randomness, max, min, base, &comm_key, ¶ms, + ) + .is_err()); + + // Params with larger base should work + let (params_with_larger_base, sk_larger) = { + let (params, sk) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base + 1); + params.verify().unwrap(); + (params, sk) + }; + + for (p, sk) in [(¶ms, &sk), (¶ms_with_larger_base, &sk_larger)] { + let start = Instant::now(); + let protocol = CLSRangeProofWithKVProtocol::init_given_base( + &mut rng, value, randomness, min, max, base, &comm_key, p, + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution(&commitment, &comm_key, p, &mut chal_bytes_prover) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + proving_time += start.elapsed(); + + let start = Instant::now(); + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution(&commitment, &comm_key, p, &mut chal_bytes_verifier) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + // assert_eq!(proof.V.len(), l as usize); + proof + .verify(&commitment, &challenge_verifier, min, max, &comm_key, p, sk) + .unwrap(); + verifying_time += start.elapsed(); + + num_proofs += 1; + } + } + } + + println!( + "For {} proofs, proving_time={:?} and verifying_time={:?}", + num_proofs, proving_time, verifying_time + ); + } +} diff --git a/smc_range_proof/src/cls_range_proof/mod.rs b/smc_range_proof/src/cls_range_proof/mod.rs new file mode 100644 index 00000000..c684a7ce --- /dev/null +++ b/smc_range_proof/src/cls_range_proof/mod.rs @@ -0,0 +1,8 @@ +#[macro_use] +pub mod util; + +pub mod kv_range_proof; +pub mod range_proof; + +pub use kv_range_proof::{CLSRangeProofWithKV, CLSRangeProofWithKVProtocol}; +pub use range_proof::{CLSRangeProof, CLSRangeProofProtocol}; diff --git a/smc_range_proof/src/cls_range_proof/range_proof.rs b/smc_range_proof/src/cls_range_proof/range_proof.rs new file mode 100644 index 00000000..c7bab63b --- /dev/null +++ b/smc_range_proof/src/cls_range_proof/range_proof.rs @@ -0,0 +1,555 @@ +//! Range proof based on Protocol 2 from the paper [Additive Combinatorics and Discrete Logarithm Based Range Protocols](https://eprint.iacr.org/2009/469) + +use ark_ec::{ + pairing::{Pairing, PairingOutput}, + AffineRepr, CurveGroup, +}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{cfg_into_iter, format, io::Write, ops::Mul, rand::RngCore, vec::Vec, UniformRand}; + +use crate::{ + ccs_set_membership::setup::SetMembershipCheckParamsWithPairing, common::MemberCommitmentKey, + error::SmcRangeProofError, +}; +use dock_crypto_utils::misc::n_rand; + +use dock_crypto_utils::{ + ff::inner_product, msm::multiply_field_elems_with_same_group_elem, + randomized_pairing_check::RandomizedPairingChecker, +}; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::cls_range_proof::{ + util, + util::{check_commitment, get_range_and_randomness_multiple}, +}; + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CLSRangeProofProtocol { + pub base: u16, + pub digits: Vec, + pub r: E::ScalarField, + pub v: Vec, + pub V: Vec, + pub a: Vec>, + pub D: E::G1Affine, + pub m: E::ScalarField, + pub s: Vec, + pub t: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct CLSRangeProof { + pub base: u16, + pub V: Vec, + pub a: Vec>, + pub D: E::G1Affine, + pub z_v: Vec, + pub z_sigma: Vec, + pub z_r: E::ScalarField, +} + +impl CLSRangeProofProtocol { + pub fn init( + rng: &mut R, + value: u64, + randomness: E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result { + let params = params.into(); + Self::init_given_base( + rng, + value, + randomness, + min, + max, + params.get_supported_base_for_range_proof(), + comm_key, + params, + ) + } + + pub fn init_given_base( + rng: &mut R, + mut value: u64, + randomness: E::ScalarField, + min: u64, + max: u64, + base: u16, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result { + if min > value { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "value={} should be >= min={}", + value, min + ))); + } + if value >= max { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "value={} should be < max={}", + value, max + ))); + } + + let params = params.into(); + params.validate_base(base)?; + + let (range, randomness_multiple) = get_range_and_randomness_multiple(base, min, max); + value = value - min; + if randomness_multiple != 1 { + value = value * (base - 1) as u64; + } + // let mut randomness_multiple = 1; + // if range % ((base - 1) as u64) != 0 { + // range = range * (base - 1) as u64; + // value = value * (base - 1) as u64; + // randomness_multiple = randomness_multiple * (base - 1); + // } + + let l = util::find_number_of_digits(range, base); + let G = util::find_sumset_boundaries(range, base, l); + + // Note: This is different from the paper as only a single `m` needs to be created. + let m = E::ScalarField::rand(rng); + let s = n_rand(rng, l).collect::>(); + let D = comm_key.commit( + &inner_product( + &s, + &cfg_into_iter!(G.clone()) + .map(|G_i| E::ScalarField::from(G_i)) + .collect::>(), + ), + &(m * E::ScalarField::from(randomness_multiple)), + ); + + if let Some(digits) = util::solve_linear_equations(value, &G, base) { + // Following is only for debugging + // let mut expected = 0_u64; + // for j in 0..digits.len() { + // assert!(digits[j] < base); + // expected += digits[j] as u64 * G[j]; + // } + // assert_eq!(expected, value); + + let digits = cfg_into_iter!(digits) + .map(|d| E::ScalarField::from(d)) + .collect::>(); + let t = n_rand(rng, l).collect::>(); + let v = n_rand(rng, l).collect::>(); + let V = randomize_sigs!(&digits, &v, ¶ms); + + let a = cfg_into_iter!(0..l as usize) + .map(|i| { + E::pairing( + E::G1Prepared::from(V[i] * -s[i]), + params.bb_sig_params.g2_prepared.clone(), + ) + params.bb_sig_params.g1g2.mul(t[i]) + }) + .collect::>(); + Ok(Self { + base, + digits, + r: randomness, + v, + V: E::G1::normalize_batch(&V), + a, + D, + m, + s, + t, + }) + } else { + Err(SmcRangeProofError::InvalidRange(range, base)) + } + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + Self::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + pub fn gen_proof(self, challenge: &E::ScalarField) -> CLSRangeProof { + gen_proof!(self, challenge, CLSRangeProof) + } + + pub fn compute_challenge_contribution( + V: &[E::G1Affine], + a: &[PairingOutput], + D: &E::G1Affine, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + mut writer: W, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + params.serialize_for_schnorr_protocol(&mut writer)?; + comm_key.serialize_compressed(&mut writer)?; + for sig in ¶ms.sigs { + sig.0.serialize_compressed(&mut writer)?; + } + commitment.serialize_compressed(&mut writer)?; + for V_i in V { + V_i.serialize_compressed(&mut writer)?; + } + for a_i in a { + a_i.serialize_compressed(&mut writer)?; + } + D.serialize_compressed(&mut writer)?; + Ok(()) + } +} + +impl CLSRangeProof { + pub fn verify( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + + self.non_crypto_validate(min, max, ¶ms)?; + + check_commitment::( + self.base, + &self.z_sigma, + &self.z_r, + &self.D, + min, + max, + commitment, + challenge, + comm_key, + )?; + + let (yc_sigma, lhs) = self.compute_for_pairing_check(challenge, ¶ms); + for i in 0..self.V.len() { + let rhs = E::pairing( + E::G1Prepared::from(self.V[i]), + E::G2Prepared::from(yc_sigma[i]), + ); + if lhs[i] != rhs { + return Err(SmcRangeProofError::InvalidRangeProof); + } + } + Ok(()) + } + + pub fn verify_given_randomized_pairing_checker( + &self, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + min: u64, + max: u64, + comm_key: &MemberCommitmentKey, + params: impl Into>, + pairing_checker: &mut RandomizedPairingChecker, + ) -> Result<(), SmcRangeProofError> { + let params = params.into(); + + self.non_crypto_validate(min, max, ¶ms)?; + + check_commitment::( + self.base, + &self.z_sigma, + &self.z_r, + &self.D, + min, + max, + commitment, + challenge, + comm_key, + )?; + + let (yc_sigma, lhs) = self.compute_for_pairing_check(challenge, ¶ms); + for i in 0..self.V.len() { + pairing_checker.add_multiple_sources_and_target(&[self.V[i]], &[yc_sigma[i]], &lhs[i]); + } + Ok(()) + } + + pub fn challenge_contribution( + &self, + commitment: &E::G1Affine, + comm_key: &MemberCommitmentKey, + params: impl Into>, + writer: W, + ) -> Result<(), SmcRangeProofError> { + CLSRangeProofProtocol::compute_challenge_contribution( + &self.V, &self.a, &self.D, commitment, comm_key, params, writer, + ) + } + + fn non_crypto_validate( + &self, + min: u64, + max: u64, + params: &SetMembershipCheckParamsWithPairing, + ) -> Result<(), SmcRangeProofError> { + params.validate_base(self.base)?; + if min >= max { + return Err(SmcRangeProofError::IncorrectBounds(format!( + "min={} should be < max={}", + min, max + ))); + } + Ok(()) + } + + fn compute_for_pairing_check( + &self, + challenge: &E::ScalarField, + params: &SetMembershipCheckParamsWithPairing, + ) -> (Vec, Vec>) { + // y * c + let yc = params.bb_pk.0 * challenge; + // g2 * z_sigma + let g2_z_sigma = multiply_field_elems_with_same_group_elem( + params.bb_sig_params.g2.into_group(), + &self.z_sigma, + ); + let lhs = cfg_into_iter!(0..self.V.len()) + .map(|i| self.a[i] - (params.bb_sig_params.g1g2 * self.z_v[i])) + .collect::>(); + let yc_sigma = cfg_into_iter!(0..g2_z_sigma.len()) + .map(|i| yc - g2_z_sigma[i]) + .collect::>(); + (yc_sigma, lhs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + ccs_set_membership::setup::SetMembershipCheckParams, + cls_range_proof::util::{ + find_number_of_digits, find_sumset_boundaries, solve_linear_equations, + }, + }; + use ark_bls12_381::{Bls12_381, Fr, G1Affine}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + use blake2::Blake2b512; + use schnorr_pok::compute_random_oracle_challenge; + use std::time::{Duration, Instant}; + + #[test] + fn sumsets_check() { + let mut rng = StdRng::seed_from_u64(0u64); + + fn check(max: u64, g: &[u64], base: u16) { + for i in max..=max { + let sigma = solve_linear_equations(max, g, base).unwrap(); + assert_eq!(sigma.len(), g.len()); + let mut expected = 0_u64; + for j in 0..sigma.len() { + assert!(sigma[j] < base); + expected += sigma[j] as u64 * g[j]; + } + assert_eq!(expected, i); + } + } + + let mut runs = 0; + let start = Instant::now(); + for base in [3, 4, 5, 8, 10, 11, 14, 16] { + for _ in 0..10 { + // let max = ((u16::rand(&mut rng) as u64)) * (base as u64 - 1); + // let max = ((u16::rand(&mut rng) as u64) << 4) * (base as u64 - 1); + let max = ((u16::rand(&mut rng) as u64) >> 4) * (base as u64 - 1); + // while (max % (base as u64 - 1)) != 0 { + // max = u64::rand(&mut rng); + // } + let l = find_number_of_digits(max, base); + let G = find_sumset_boundaries(max, base, l); + println!("Starting for base={} and max={}", base, max); + let start_check = Instant::now(); + check(max, &G, base); + println!( + "Check done for base={} and max={} in {:?}", + base, + max, + start_check.elapsed() + ); + runs += 1; + } + } + println!("Time for {} runs: {:?}", runs, start.elapsed()); + } + + #[test] + fn cls_range_proof() { + let mut rng = StdRng::seed_from_u64(0u64); + let mut proving_time = Duration::default(); + let mut verifying_time = Duration::default(); + let mut verifying_with_rpc_time = Duration::default(); + let mut num_proofs = 0; + + for base in [2, 4, 8, 16] { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base); + params.verify().unwrap(); + + let params_with_pairing = SetMembershipCheckParamsWithPairing::from(params.clone()); + params_with_pairing.verify().unwrap(); + + let comm_key = MemberCommitmentKey::::generate_using_rng(&mut rng); + + for _ in 0..5 { + let mut a = [ + u16::rand(&mut rng) as u64, + u16::rand(&mut rng) as u64, + u16::rand(&mut rng) as u64, + ]; + a.sort(); + let min = a[0]; + let max = a[2]; + let value = a[1]; + assert!(value > min); + assert!(value < max); + let randomness = Fr::rand(&mut rng); + let commitment = comm_key.commit(&Fr::from(value), &randomness); + + // Params with incorrect base should fail + let params_with_smaller_base = { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base - 1); + SetMembershipCheckParamsWithPairing::from(params.clone()) + }; + assert!(CLSRangeProofProtocol::init_given_base( + &mut rng, + value, + randomness, + min, + max, + base, + &comm_key, + params_with_smaller_base, + ) + .is_err()); + + // min > max should fail + assert!(CLSRangeProofProtocol::init_given_base( + &mut rng, + value, + randomness, + max, + min, + base, + &comm_key, + params_with_pairing.clone(), + ) + .is_err()); + + // Params with larger base should work + let params_with_larger_base = { + let (params, _) = SetMembershipCheckParams::::new_for_range_proof::< + _, + Blake2b512, + >(&mut rng, b"test", base + 1); + SetMembershipCheckParamsWithPairing::from(params.clone()) + }; + + for params in [params_with_pairing.clone(), params_with_larger_base] { + let start = Instant::now(); + let protocol = CLSRangeProofProtocol::init_given_base( + &mut rng, + value, + randomness, + min, + max, + base, + &comm_key, + params.clone(), + ) + .unwrap(); + + let mut chal_bytes_prover = vec![]; + protocol + .challenge_contribution( + &commitment, + &comm_key, + params.clone(), + &mut chal_bytes_prover, + ) + .unwrap(); + let challenge_prover = + compute_random_oracle_challenge::(&chal_bytes_prover); + + let proof = protocol.gen_proof(&challenge_prover); + proving_time += start.elapsed(); + + let start = Instant::now(); + let mut chal_bytes_verifier = vec![]; + proof + .challenge_contribution( + &commitment, + &comm_key, + params.clone(), + &mut chal_bytes_verifier, + ) + .unwrap(); + let challenge_verifier = + compute_random_oracle_challenge::(&chal_bytes_verifier); + assert_eq!(challenge_prover, challenge_verifier); + + // assert_eq!(proof.V.len(), l as usize); + proof + .verify( + &commitment, + &challenge_verifier, + min, + max, + &comm_key, + params.clone(), + ) + .unwrap(); + verifying_time += start.elapsed(); + + let start = Instant::now(); + let mut pairing_checker = + RandomizedPairingChecker::new_using_rng(&mut rng, true); + proof + .verify_given_randomized_pairing_checker( + &commitment, + &challenge_verifier, + min, + max, + &comm_key, + params, + &mut pairing_checker, + ) + .unwrap(); + verifying_with_rpc_time += start.elapsed(); + + num_proofs += 1; + } + } + } + + println!("For {} proofs, proving_time={:?}, verifying_time={:?} and verifying using randomized pairing checker time {:?}", num_proofs, proving_time, verifying_time, verifying_with_rpc_time); + } +} diff --git a/smc_range_proof/src/cls_range_proof/util.rs b/smc_range_proof/src/cls_range_proof/util.rs new file mode 100644 index 00000000..5e4ef1fb --- /dev/null +++ b/smc_range_proof/src/cls_range_proof/util.rs @@ -0,0 +1,163 @@ +use crate::common::{base_n_digits, MemberCommitmentKey}; +use ark_ec::{pairing::Pairing, CurveGroup}; +use ark_std::{cfg_into_iter, collections::BTreeMap, vec, vec::Vec}; + +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::error::SmcRangeProofError; +use dock_crypto_utils::ff::inner_product; + +#[macro_export] +macro_rules! gen_proof { + ($self: ident, $challenge: ident, $proof: ident) => {{ + let z_v = cfg_into_iter!(0..$self.V.len()) + .map(|i| $self.t[i] - ($self.v[i] * $challenge)) + .collect::>(); + let z_sigma = cfg_into_iter!(0..$self.V.len()) + .map(|i| $self.s[i] - ($self.digits[i] * $challenge)) + .collect::>(); + let z_r = $self.m - ($self.r * $challenge); + $proof { + base: $self.base, + V: $self.V, + a: $self.a, + D: $self.D, + z_v, + z_sigma, + z_r, + } + }}; +} + +pub(super) fn get_range_and_randomness_multiple(base: u16, min: u64, max: u64) -> (u64, u16) { + let mut range = max - min; + let mut randomness_multiple = 1; + let b_1 = (base - 1) as u64; + if range % b_1 != 0 { + range = range * b_1; + randomness_multiple = randomness_multiple * (base - 1); + } + (range, randomness_multiple) +} + +pub(super) fn check_commitment( + base: u16, + z_sigma: &[E::ScalarField], + z_r: &E::ScalarField, + D: &E::G1Affine, + min: u64, + max: u64, + commitment: &E::G1Affine, + challenge: &E::ScalarField, + comm_key: &MemberCommitmentKey, +) -> Result<(), SmcRangeProofError> { + let (range, randomness_multiple) = get_range_and_randomness_multiple(base, min, max); + + let l = find_number_of_digits(range, base); + let G = find_sumset_boundaries(range, base, l); + + if (*commitment * (E::ScalarField::from(randomness_multiple) * challenge) + - comm_key.g * (E::ScalarField::from(min * randomness_multiple as u64) * challenge) + + comm_key.commit( + &inner_product( + z_sigma, + &cfg_into_iter!(G) + .map(|G_i| E::ScalarField::from(G_i)) + .collect::>(), + ), + &(E::ScalarField::from(randomness_multiple) * z_r), + )) + .into_affine() + != *D + { + return Err(SmcRangeProofError::InvalidRangeProof); + } + Ok(()) +} + +/// Returns what the paper calls l. Here we assume that `base - 1` divides `max`. +pub fn find_number_of_digits(max: u64, base: u16) -> u16 { + // (((max + 1) as f64).log(base as f64)).ceil() as u16 + // Above can cause overflow with large u64 values as f64 can't contain the same amount of + // integers as u64 so using the below loop instead + let mut power = 1; + let mut l = 0; + while power < max { + power *= base as u64; + l += 1; + } + l +} + +/// Returns what the paper calls `G_i`, `max` is called `H` and `num` is called `l` in the paper +pub fn find_sumset_boundaries(max: u64, base: u16, num: u16) -> Vec { + if base == 2 { + cfg_into_iter!(0..num) + .map(|i| (max + (1 << i)) >> (i + 1)) + .collect() + } else { + let h = base_n_digits(max, base); + let mut g = vec![]; + for i in 0..num as usize { + let h_hat = max / (base as u64).pow(i as u32 + 1); + let sum = h[..i].iter().map(|h_i| *h_i as u64).sum::() as u64; + g.push(h_hat + ((1 + h[i] as u64 + (sum % (base as u64 - 1))) / base as u64)) + } + g + } +} + +pub fn solve_linear_equations(y: u64, coefficients: &[u64], u: u16) -> Option> { + let n = coefficients.len(); + let mut solutions = vec![0; n]; + + fn find_value_for_index( + index: usize, + remaining_y: i64, + solutions: &mut Vec, + coefficients: &[u64], + u: u16, + memo: &mut BTreeMap<(usize, i64), bool>, + ) -> bool { + if index == coefficients.len() { + if remaining_y == 0 { + return true; + } + return false; + } + + if let Some(&result) = memo.get(&(index, remaining_y)) { + return result; + } + + for x in 0..u { + solutions[index] = x; + let new_remaining_y = remaining_y - coefficients[index] as i64 * x as i64; + if new_remaining_y >= 0 + && find_value_for_index( + index + 1, + new_remaining_y, + solutions, + coefficients, + u, + memo, + ) + { + memo.insert((index, remaining_y), true); + return true; + } + } + + memo.insert((index, remaining_y), false); + false + } + + let mut memo: BTreeMap<(usize, i64), bool> = BTreeMap::new(); + + if find_value_for_index(0, y as i64, &mut solutions, coefficients, u, &mut memo) { + Some(solutions) + } else { + None + } +} diff --git a/smc_range_proof/src/common.rs b/smc_range_proof/src/common.rs new file mode 100644 index 00000000..3c5766a6 --- /dev/null +++ b/smc_range_proof/src/common.rs @@ -0,0 +1,135 @@ +use ark_ec::AffineRepr; + +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec::Vec}; +use digest::Digest; +use dock_crypto_utils::{ + concat_slices, + ff::{inner_product, powers}, + hashing_utils::affine_group_elem_from_try_and_incr, + misc::rand, +}; + +/// Commitment key to commit the set member +#[derive(Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct MemberCommitmentKey { + pub g: G, + pub h: G, +} + +impl MemberCommitmentKey { + pub fn new(label: &[u8]) -> Self { + let g = affine_group_elem_from_try_and_incr::(&concat_slices![label, b" : G"]); + let h = affine_group_elem_from_try_and_incr::(&concat_slices![label, b" : H"]); + Self { g, h } + } + + pub fn generate_using_rng(rng: &mut R) -> Self { + Self { + g: rand(rng), + h: rand(rng), + } + } + + /// Pedersen commitment to the set member + pub fn commit(&self, member: &G::ScalarField, randomness: &G::ScalarField) -> G { + (self.g * member + self.h * randomness).into() + } + + pub fn commit_decomposed( + &self, + base: u16, + digits: &[G::ScalarField], + randomness: &G::ScalarField, + ) -> G { + let base_powers = powers(&G::ScalarField::from(base), digits.len() as u32); + self.commit_decomposed_given_base_powers(&base_powers, digits, randomness) + } + + pub fn commit_decomposed_given_base_powers( + &self, + base_powers: &[G::ScalarField], + digits: &[G::ScalarField], + randomness: &G::ScalarField, + ) -> G { + (self.g * inner_product(base_powers, digits) + self.h * randomness).into() + } +} + +/// Representation of `value` in base `base`-representation. Returns the base `base` digits in little-endian form +pub fn base_n_digits(mut value: u64, base: u16) -> Vec { + let mut digits = Vec::::new(); + while value != 0 { + // Note: Can use bitwise ops if base is power of 2 + digits.push((value % base as u64) as u16); + value = value / base as u64; + } + digits +} + +/// Same as `base_n_digits` but pads representation with 0s to the until to make the output vector length as `size` +pub fn padded_base_n_digits_as_field_elements( + value: u64, + base: u16, + size: usize, +) -> Vec { + let mut digits = base_n_digits(value, base) + .into_iter() + .map(|d| F::from(d)) + .collect::>(); + while digits.len() < size { + digits.push(F::zero()); + } + digits +} + +#[macro_export] +macro_rules! randomize_sigs { + ($members: expr, $randomizers: expr, $params: expr) => {{ + let mut A = Vec::with_capacity($members.len()); + for m in $members { + A.push($params.get_sig_for_member(m)?) + } + cfg_into_iter!(A) + .enumerate() + .map(|(i, A_i)| A_i.0 * $randomizers[i]) + .collect::>() + }}; +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_bls12_381::{Fr, G1Affine}; + use ark_std::{ + rand::{rngs::StdRng, SeedableRng}, + UniformRand, + }; + + #[test] + fn base_digits() { + let mut rng = StdRng::seed_from_u64(0u64); + let comm_key = MemberCommitmentKey::::generate_using_rng(&mut rng); + + let value = u64::rand(&mut rng); + let randomness = Fr::rand(&mut rng); + let base = 4; + let digits = base_n_digits(value, base); + let mut expected_value = 0u64; + let mut power = 1u64; + for (i, digit) in digits.iter().enumerate() { + let d = *digit as u64; + expected_value += d * power; + if i != digits.len() - 1 { + power = base as u64 * power; + } + } + assert_eq!(expected_value, value); + + let digits = digits.into_iter().map(|d| Fr::from(d)).collect::>(); + let comm = comm_key.commit(&Fr::from(value), &randomness); + let comm_d = comm_key.commit_decomposed(base, &digits, &randomness); + assert_eq!(comm, comm_d) + } +} diff --git a/smc_range_proof/src/error.rs b/smc_range_proof/src/error.rs new file mode 100644 index 00000000..0feb19e6 --- /dev/null +++ b/smc_range_proof/src/error.rs @@ -0,0 +1,22 @@ +use ark_serialize::SerializationError; +use ark_std::string::String; + +#[derive(Debug)] +pub enum SmcRangeProofError { + ZeroSignature, + InvalidSignature, + InvalidSetMembershipSetup, + CannotFindElementInSet, + InvalidSetMembershipProof, + InvalidRangeProof, + UnsupportedBase(u16, u16), + InvalidRange(u64, u16), + IncorrectBounds(String), + Serialization(SerializationError), +} + +impl From for SmcRangeProofError { + fn from(e: SerializationError) -> Self { + Self::Serialization(e) + } +} diff --git a/smc_range_proof/src/lib.rs b/smc_range_proof/src/lib.rs new file mode 100644 index 00000000..1459943b --- /dev/null +++ b/smc_range_proof/src/lib.rs @@ -0,0 +1,45 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(non_snake_case)] + +//! Implements the following range proof and set-membership protocols. +//! 1. Set membership protocol using BB signature. Described in Fig.1 of the paper [1]. [Code](/src/ccs_set_membership) +//! 2. Range proof protocol as described in Fig.3 of the paper [1]. Considers a perfect-range, i.e. range of the form `[0, u^l)` +//! where `u` is the base and the upper bound is a power of the base. [Code](src/ccs_range_proof/perfect_range.rs) +//! 3. Range proof protocol as described in section 4.4 of the paper [1]. Considers an arbitrary range `[min, max)`. [Code](src/ccs_range_proof/arbitrary_range.rs) +//! 4. Range proof using sumsets, based on Protocol 2 from the paper [2]. [Code](src/smc_range_proof.rs) +//! 5. Implements the Keyed-Verification of the above protocols where the verifier knows the secret key of the BB sig. This makes +//! the proof generation and verification more efficient by removing the need for pairings. This idea is taken from this PhD. thesis. +//! +//! Above protocols use a pairing based signature called the [BB signature](src/bb_sig.rs). +//! +//! References: +//! +//! [1]: [Efficient Protocols for Set Membership and Range Proofs](https://link.springer.com/chapter/10.1007/978-3-540-89255-7_15) +//! +//! [2]: [Additive Combinatorics and Discrete Logarithm Based Range Protocols](https://eprint.iacr.org/2009/469) + +#[macro_use] +pub mod common; +pub mod bb_sig; +pub mod ccs_range_proof; +pub mod ccs_set_membership; +mod cls_range_proof; +pub mod error; + +pub mod prelude { + pub use crate::{ + bb_sig::{PublicKeyG2, SecretKey, SignatureParams, SignatureParamsWithPairing}, + ccs_range_proof::{ + CCSArbitraryRangeProof, CCSArbitraryRangeProofProtocol, + CCSArbitraryRangeProofWithKVProtocol, CCSArbitraryRangeWithKVProof, + }, + ccs_set_membership::setup::{ + SetMembershipCheckParams, SetMembershipCheckParamsWithPairing, + }, + cls_range_proof::{ + CLSRangeProof, CLSRangeProofProtocol, CLSRangeProofWithKV, CLSRangeProofWithKVProtocol, + }, + common::MemberCommitmentKey, + error::SmcRangeProofError, + }; +} diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 135917d7..92117d9f 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -6,16 +6,16 @@ authors.workspace = true license.workspace = true [dependencies] -bbs_plus = { version = "0.16.0", default-features = false, path = "../bbs_plus" } -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } -vb_accumulator = { version = "0.17.0", default-features = false, path = "../vb_accumulator" } +bbs_plus = { version = "0.17.0", default-features = false, path = "../bbs_plus" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } +vb_accumulator = { version = "0.18.0", default-features = false, path = "../vb_accumulator" } ark-ff.workspace = true ark-ec.workspace = true ark-std.workspace = true ark-bls12-381.workspace = true ark-serialize.workspace = true blake2.workspace = true -proof_system = { version = "0.22.0", default-features = false, path = "../proof_system"} +proof_system = { version = "0.23.0", default-features = false, path = "../proof_system"} [features] default = ["parallel"] diff --git a/utils/Cargo.toml b/utils/Cargo.toml index bf7f9fd2..c1cdb837 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dock_crypto_utils" -version = "0.15.0" +version = "0.16.0" edition.workspace = true authors.workspace = true license.workspace = true diff --git a/utils/src/ff.rs b/utils/src/ff.rs index 3b2dc9b2..573136ee 100644 --- a/utils/src/ff.rs +++ b/utils/src/ff.rs @@ -1,8 +1,8 @@ use ark_ff::PrimeField; -use ark_std::{rand::Rng, vec, vec::Vec}; +use ark_std::{cfg_into_iter, cfg_iter, cfg_iter_mut, rand::Rng, vec::Vec}; #[cfg(feature = "parallel")] -use {ark_std::cfg_into_iter, rayon::prelude::*}; +use rayon::prelude::*; /// Inner product of 2 vectors `a` and `b` pub fn inner_product(a: &[F], b: &[F]) -> F { @@ -21,6 +21,52 @@ pub fn inner_product(a: &[F], b: &[F]) -> F { sum } +/// Hadamard product of two vectors of scalars +pub fn hadamard_product(a: &[F], b: &[F]) -> Vec { + cfg_iter!(a) + .zip(cfg_iter!(b)) + .map(|(a_i, b_i)| *a_i * b_i) + .collect() +} + +/// Add two vectors of scalars +pub fn add_vecs(a: &[F], b: &[F]) -> Vec { + let (a_len, b_len) = (a.len(), b.len()); + let res_len = ark_std::cmp::max(a_len, b_len); + cfg_into_iter!(0..res_len) + .map(|i| { + let a_i = a.get(i).cloned().unwrap_or(F::zero()); + let b_i = b.get(i).cloned().unwrap_or(F::zero()); + a_i + b_i + }) + .collect() +} + +/// Weighted inner product of 2 vectors `a` and `b` with weight `w`. Calculated as `\sum_{i=0}(a_i * b_i * w^{i+1})` +pub fn weighted_inner_product(a: &[F], b: &[F], w: &F) -> F { + let size = a.len().min(b.len()); + + let mut weights = powers(w, size as u32 + 1); + weights.remove(0); + + #[cfg(feature = "parallel")] + let sum = cfg_into_iter!(0..size) + .map(|i| a[i] * b[i] * weights[i]) + .reduce(|| F::zero(), |accum, v| accum + v); + + #[cfg(not(feature = "parallel"))] + let sum = (0..size) + .map(|i| a[i] * b[i] * weights[i]) + .fold(F::zero(), |accum, v| accum + v); + + sum +} + +/// Weighted inner product of the vector `n` with itself. Calculated as `\sum_{i=0}(n_i * n_i * w^{i+1})` +pub fn weighted_norm(n: &[F], w: &F) -> F { + weighted_inner_product(n, n, w) +} + pub fn non_zero_random(rng: &mut R) -> F { let mut r = F::rand(rng); while r.is_zero() { @@ -31,9 +77,24 @@ pub fn non_zero_random(rng: &mut R) -> F { /// Powers of a finite field as `[1, s, s^2, .. s^{num-1}]` pub fn powers(s: &F, num: u32) -> Vec { - let mut powers = vec![F::one()]; - for i in 1..num { - powers.push(powers[i as usize - 1] * s); + let mut powers = Vec::with_capacity(num as usize); + if num > 0 { + powers.push(F::one()); + for i in 1..num { + powers.push(powers[i as usize - 1] * s); + } + } + powers +} + +/// Powers of a finite field as `[start, start*exp, start * exp^2, .. start * exp^{num-1}]` +pub fn powers_starting_from(start: F, exp: &F, num: u32) -> Vec { + let mut powers = Vec::with_capacity(num as usize); + if num > 0 { + powers.push(start); + for i in 1..num as usize { + powers.push(powers[i - 1] * exp); + } } powers } @@ -44,3 +105,75 @@ pub fn powers(s: &F, num: u32) -> Vec { pub fn sum_of_powers(r: &F, num: u32) -> F { (r.pow([num as u64]) - &F::one()) * (*r - F::one()).inverse().unwrap() } + +/// Return a scaled vector by multiplying each of its elements by `factor`. Returns `[arr_0 * factor, arr_1 * factor, arr_2 * factor, ...]` +pub fn scale(arr: &[F], factor: &F) -> Vec { + cfg_iter!(arr).map(|elem| *elem * factor).collect() +} + +/// Mutate a vector `arr` by multiplying each of its elements by `factor`. +pub fn scale_mut(arr: &mut [F], factor: &F) { + cfg_iter_mut!(arr).for_each(|elem| *elem = *elem * factor); +} + +#[cfg(test)] +mod test { + use super::*; + use crate::misc::n_rand; + use ark_bls12_381::Fr; + use ark_ff::{Field, One, Zero}; + use ark_std::{ + rand::{prelude::StdRng, SeedableRng}, + UniformRand, + }; + + #[test] + fn check_inner_product_and_norm() { + let mut rng = StdRng::seed_from_u64(0u64); + + let count = 10; + let a = n_rand(&mut rng, count).collect::>(); + let b = n_rand(&mut rng, count).collect::>(); + + let weight = Fr::rand(&mut rng); + + let res1 = inner_product(&a, &b); + let mut res2 = Fr::zero(); + for i in 0..count { + res2 += a[i] * b[i]; + } + assert_eq!(res1, res2); + + let res3 = weighted_inner_product(&a, &b, &weight); + let mut res4 = Fr::zero(); + for i in 0..count { + res4 += a[i] * b[i] * weight.pow(&[i as u64 + 1]); + } + assert_eq!(res3, res4); + + let res5 = weighted_norm(&a, &weight); + let mut res6 = Fr::zero(); + for i in 0..count { + res6 += a[i] * a[i] * weight.pow(&[i as u64 + 1]); + } + assert_eq!(res5, res6); + } + + #[test] + fn check_powers() { + let mut rng = StdRng::seed_from_u64(0u64); + + let exp = Fr::rand(&mut rng); + let num = 10; + let mut p1 = powers(&exp, num); + assert!(p1[0].is_one()); + for i in 1..num as usize { + assert_eq!(p1[i], p1[i - 1] * exp); + } + + let p2 = powers_starting_from(exp.clone(), &exp, num - 1); + assert_eq!(p2[0], exp); + p1.remove(0); + assert_eq!(p1, p2); + } +} diff --git a/utils/src/transcript.rs b/utils/src/transcript.rs index 05c89318..a37f1240 100644 --- a/utils/src/transcript.rs +++ b/utils/src/transcript.rs @@ -5,7 +5,7 @@ use ark_std::{vec, vec::Vec}; pub use merlin::Transcript as Merlin; /// must be specific to the application. -pub fn new_merlin_transcript(label: &'static [u8]) -> impl Transcript { +pub fn new_merlin_transcript(label: &'static [u8]) -> impl Transcript + Clone { Merlin::new(label) } diff --git a/vb_accumulator/Cargo.toml b/vb_accumulator/Cargo.toml index 76e2675f..d9af2f8d 100644 --- a/vb_accumulator/Cargo.toml +++ b/vb_accumulator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vb_accumulator" -version = "0.17.0" +version = "0.18.0" edition.workspace = true authors.workspace = true license.workspace = true @@ -22,8 +22,8 @@ rayon = {workspace = true, optional = true} serde.workspace = true serde_with.workspace = true zeroize.workspace = true -schnorr_pok = { version = "0.14.0", default-features = false, path = "../schnorr_pok" } -dock_crypto_utils = { version = "0.15.0", default-features = false, path = "../utils" } +schnorr_pok = { version = "0.15.0", default-features = false, path = "../schnorr_pok" } +dock_crypto_utils = { version = "0.16.0", default-features = false, path = "../utils" } [dev-dependencies] blake2.workspace = true