From ef3650a30d1f9678537c4b3f29c6573d12f0ca00 Mon Sep 17 00:00:00 2001 From: lovesh Date: Thu, 19 Oct 2023 18:39:32 +0530 Subject: [PATCH] Fix BBS proof generation Signed-off-by: lovesh --- bbs_plus/src/lib.rs | 7 +- ...{proof_23_alternate.rs => proof_23_cdl.rs} | 93 +++++++++++-------- .../src/range_proof_arbitrary_range.rs | 1 - bulletproofs_plus_plus/src/setup.rs | 2 +- .../src/weighted_norm_linear_argument.rs | 1 + schnorr_pok/README.md | 11 ++- schnorr_pok/src/lib.rs | 11 ++- .../src/ccs_range_proof/kv_perfect_range.rs | 1 - test_utils/Cargo.toml | 8 +- utils/src/transcript.rs | 4 + 10 files changed, 85 insertions(+), 54 deletions(-) rename bbs_plus/src/{proof_23_alternate.rs => proof_23_cdl.rs} (90%) diff --git a/bbs_plus/src/lib.rs b/bbs_plus/src/lib.rs index 294e2c19..6ca155bf 100644 --- a/bbs_plus/src/lib.rs +++ b/bbs_plus/src/lib.rs @@ -44,18 +44,19 @@ //! //! The implementation tries to use the same variable names as the paper and thus violate Rust's naming conventions at places. //! +//! //! [`setup`]: crate::setup //! [`signature`]: crate::signature //! [`proof`]: crate::proof //! [`signature_23`]: crate::signature_23 //! [`proof_23`]: crate::proof_23 -//! [`proof_23_alternate`]: crate::proof_23_alternate +//! [`proof_23_alternate`]: crate::proof_23_cdl //! [`threshold`]: crate::threshold pub mod error; pub mod proof; pub mod proof_23; -pub mod proof_23_alternate; +pub mod proof_23_cdl; pub mod setup; pub mod signature; pub mod signature_23; @@ -65,7 +66,7 @@ pub mod prelude { pub use crate::{ error::BBSPlusError, proof::{MessageOrBlinding, PoKOfSignatureG1Proof, PoKOfSignatureG1Protocol}, - proof_23_alternate::{PoKOfSignature23G1Proof, PoKOfSignature23G1Protocol}, + proof_23_cdl::{PoKOfSignature23G1Proof, PoKOfSignature23G1Protocol}, setup::*, signature::{SignatureG1, SignatureG2}, signature_23::Signature23G1, diff --git a/bbs_plus/src/proof_23_alternate.rs b/bbs_plus/src/proof_23_cdl.rs similarity index 90% rename from bbs_plus/src/proof_23_alternate.rs rename to bbs_plus/src/proof_23_cdl.rs index 2155d771..64f01f53 100644 --- a/bbs_plus/src/proof_23_alternate.rs +++ b/bbs_plus/src/proof_23_cdl.rs @@ -1,6 +1,6 @@ //! Proof of knowledge of BBS signature and corresponding messages as per section 5.2 of the BBS paper with //! slight modification described below. -//! The paper requires the prover to prove `e(A_bar, X_2) = e (B_bar, g2)` where `B_bar = C(m)*r + A_bar*-e`. +//! The paper requires the prover to prove `e(A_bar, X_2) = e (B_bar, g2)` where `A_bar = A * r` and `B_bar = C(m)*r + A_bar*-e`. //! The prover sends `A_bar`, `B_bar` to the verifier and also proves the knowledge of `r`, `e` and any //! messages in `C(m)` in `B_bar`. Here `r` is a random element chosen by the prover on each proof of knowledge. //! Above approach has a problem when some messages under 2 signatures need to be proven equal in zero @@ -8,11 +8,11 @@ //! contains a Pedersen commitment to witness of the SNARK. Because `r` will be different for each signature, //! the witnesses for the Schnorr proof will be different, i.e. `m*r` and `m*r'` for the same message `m` and //! thus the folklore method of proving equal witnesses in multiple statements cant be used. Thus the protocol -//! below using the same approach as used in BBS+. The prover in addition to sending `A_bar = A*r1`, `B_bar = B*r1` -//! to the verifier sends `d = C(m)*r1` as well. The prover picks a random `r1`, calculates `r2 = 1 / r1` and +//! below uses a similar approach as used in BBS+. The prover in addition to sending `A_bar = A*r1*r2`, `B_bar = (C(m) - A*e)*r1*r2` +//! to the verifier sends `d = C(m)*r2` as well for random `r1`, `r2`. The prover calculates `r3 = 1 / r2` and //! creates 2 Schnorr proofs for: -//! 1. `B_bar - d = A_bar * -e`, here `-e` is the witness and `B_bar, d, A_bar` are the instance -//! 2. `d * r2 = C(m)`. Here the witnesses are `r2` and any messages part of `C(m)` which the prover is hiding and +//! 1. `B_bar = d * r1 + A_bar * -e`, here `r1` and `-e` is the witness and `B_bar, d, A_bar` are the instance +//! 2. `d * r3 = C(m)`. Here the witnesses are `r3` and any messages part of `C(m)` which the prover is hiding and //! the instance is `g + \sum_i{h_i*m_i}` for all `m_i` that the prover is revealing. use crate::{ @@ -22,14 +22,15 @@ use crate::{ setup::{MultiMessageSignatureParams, PreparedSignatureParams23G1, SignatureParams23G1}, signature_23::Signature23G1, }; -use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group, VariableBaseMSM}; -use ark_ff::{Field, PrimeField, Zero}; +use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, VariableBaseMSM}; +use ark_ff::{Field, Zero}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{ collections::{BTreeMap, BTreeSet}, fmt::Debug, io::Write, rand::RngCore, + vec, vec::Vec, One, UniformRand, }; @@ -38,16 +39,11 @@ use dock_crypto_utils::{ serde_utils::*, }; use itertools::multiunzip; -use schnorr_pok::{ - error::SchnorrError, impl_proof_of_knowledge_of_discrete_log, SchnorrCommitment, - SchnorrResponse, -}; +use schnorr_pok::{error::SchnorrError, SchnorrCommitment, SchnorrResponse}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; use zeroize::{Zeroize, ZeroizeOnDrop}; -impl_proof_of_knowledge_of_discrete_log!(KnowledgeOfEProtocol, KnowledgeOfEProof); - /// Protocol to prove knowledge of BBS signature in group G1. #[serde_as] #[derive( @@ -72,9 +68,11 @@ pub struct PoKOfSignature23G1Protocol { #[zeroize(skip)] #[serde_as(as = "ArkObjectBytes")] pub d: E::G1Affine, - /// For proving relation `B_bar - d = A_bar * -e` - pub sc_comm_1: KnowledgeOfEProtocol, - /// For proving relation `g1 + \sum_{i in D}(h_i*m_i)` = `d*r2 + sum_{j notin D}(h_j*m_j)` + /// For proving relation `B_bar = d * r1 + A_bar * -e` + pub sc_comm_1: SchnorrCommitment, + #[serde_as(as = "(ArkObjectBytes, ArkObjectBytes)")] + sc_wits_1: (E::ScalarField, E::ScalarField), + /// For proving relation `g1 + \sum_{i in D}(h_i*m_i)` = `d*r3 + sum_{j notin D}(h_j*m_j)` #[zeroize(skip)] pub sc_comm_2: SchnorrCommitment, #[serde_as(as = "Vec")] @@ -94,9 +92,11 @@ pub struct PoKOfSignature23G1Proof { pub B_bar: E::G1Affine, #[serde_as(as = "ArkObjectBytes")] pub d: E::G1Affine, - /// Proof of relation `B_bar - d = A_bar * -e` - pub sc_proof_1: KnowledgeOfEProof, - /// Proof of relation `g1 + h1*m1 + h2*m2 +.... + h_i*m_i` = `d*r2 + h1*{-m1} + h2*{-m2} + .... + h_j*{-m_j}` for all disclosed messages `m_i` and for all undisclosed messages `m_j` + /// Proof of relation `B_bar = d * r3 + A_bar * -e` + #[serde_as(as = "ArkObjectBytes")] + pub T1: E::G1Affine, + pub sc_resp_1: SchnorrResponse, + /// Proof of relation `g1 + h1*m1 + h2*m2 +.... + h_i*m_i` = `d*r3 + h1*{-m1} + h2*{-m2} + .... + h_j*{-m_j}` for all disclosed messages `m_i` and for all undisclosed messages `m_j` #[serde_as(as = "ArkObjectBytes")] pub T2: E::G1Affine, pub sc_resp_2: SchnorrResponse, @@ -137,29 +137,34 @@ impl PoKOfSignature23G1Protocol { } let r1 = E::ScalarField::rand(rng); - let r2 = r1.inverse().ok_or(BBSPlusError::CannotInvert0)?; + let r2 = E::ScalarField::rand(rng); + // r3 = 1/r2 + let r3 = r2.inverse().ok_or(BBSPlusError::CannotInvert0)?; // b = (e+x) * A = g1 + sum(h_i*m_i) for all i in I let b = params.b(messages.iter().enumerate())?; - // d = b * r1 - let d = b * r1; - // A_bar = A * r1 - let A_bar = signature.A.mul_bigint(r1.into_bigint()); + // d = b * r2 + let d = b * r2; + // A_bar = A * r1 * r2 + let A_bar = signature.A * (r1 * r2); let A_bar_affine = A_bar.into_affine(); - // B_bar = d - e * A_bar - let B_bar = d - (A_bar.mul_bigint(signature.e.into_bigint())); + // B_bar = d * r1 - e * A_bar + let B_bar = d * r1 - (A_bar * signature.e); let d_affine = d.into_affine(); // Following is the 1st step of the Schnorr protocol for the relation pi in the paper. pi is a // conjunction of 2 relations: - // 1. `B_bar - d == A_bar*{-e}` - // 2. `g1 + \sum_{i \in D}(h_i*m_i)` == `d*r2 + \sum_{j \notin D}(h_j*{-m_j})` + // 1. `B_bar == d * r1 + A_bar*{-e}` + // 2. `g1 + \sum_{i \in D}(h_i*m_i)` == `d*r3 + \sum_{j \notin D}(h_j*{-m_j})` // for all disclosed messages `m_i` and for all undisclosed messages `m_j`. // For each of the above relations, a Schnorr protocol is executed; the first to prove knowledge // of `(e, r1)`, and the second of `(r2, {m_j}_{j \notin D})`. The secret knowledge items are // referred to as witnesses, and the public items as instances. - let sc_comm_1 = - KnowledgeOfEProtocol::init(-signature.e, E::ScalarField::rand(rng), &A_bar_affine); + let bases_1 = [A_bar_affine, d_affine]; + let randomness_1 = vec![E::ScalarField::rand(rng), E::ScalarField::rand(rng)]; + let wits_1 = (-signature.e, r1); + + let sc_comm_1 = SchnorrCommitment::new(&bases_1, randomness_1); // For proving relation `g1 + \sum_{i \in D}(h_i*m_i)` = `d*r2 + \sum_{j \notin D}(h_j*{-m_j})` // for all disclosed messages `m_i` and for all undisclosed messages `m_j`, usually the number of disclosed @@ -176,7 +181,7 @@ impl PoKOfSignature23G1Protocol { .map(|(idx, blinding)| (params.h[idx], blinding, messages[idx])); let (bases_2, randomness_2, wits_2): (Vec<_>, Vec<_>, Vec<_>) = multiunzip( - [(d_affine, rand(rng), -r2)] + [(d_affine, rand(rng), -r3)] .into_iter() .chain(h_blinding_message), ); @@ -189,6 +194,7 @@ impl PoKOfSignature23G1Protocol { B_bar: B_bar.into_affine(), d: d_affine, sc_comm_1, + sc_wits_1: wits_1, sc_comm_2, sc_wits_2: wits_2, }) @@ -218,16 +224,19 @@ impl PoKOfSignature23G1Protocol { self, challenge: &E::ScalarField, ) -> Result, BBSPlusError> { - let sc_proof_1 = self.sc_comm_1.clone().gen_proof(challenge); + let resp_1 = self + .sc_comm_1 + .response(&[self.sc_wits_1.0, self.sc_wits_1.1], challenge)?; - // Schnorr response for relation `g1 + \sum_{i in D}(h_i*m_i)` = `d*r2 + \sum_{j not in D}(h_j*{-m_j})` + // Schnorr response for relation `g1 + \sum_{i in D}(h_i*m_i)` = `d*r3 + \sum_{j not in D}(h_j*{-m_j})` let resp_2 = self.sc_comm_2.response(&self.sc_wits_2, challenge)?; Ok(PoKOfSignature23G1Proof { A_bar: self.A_bar, B_bar: self.B_bar, d: self.d, - sc_proof_1, + T1: self.sc_comm_1.t, + sc_resp_1: resp_1, T2: self.sc_comm_2.t, sc_resp_2: resp_2, }) @@ -341,7 +350,7 @@ where &self.A_bar, &self.B_bar, &self.d, - &self.sc_proof_1.t, + &self.T1, &self.T2, revealed_msgs, params, @@ -380,12 +389,16 @@ where h: Vec, ) -> Result<(), BBSPlusError> { // Verify the 1st Schnorr proof - let B_bar_minus_d = (self.B_bar.into_group() - self.d.into_group()).into_affine(); - if !self - .sc_proof_1 - .verify(&B_bar_minus_d, &self.A_bar, challenge) + let bases_1 = [self.A_bar, self.d]; + match self + .sc_resp_1 + .is_valid(&bases_1, &self.B_bar, &self.T1, challenge) { - return Err(BBSPlusError::FirstSchnorrVerificationFailed); + Ok(()) => (), + Err(SchnorrError::InvalidResponse) => { + return Err(BBSPlusError::FirstSchnorrVerificationFailed) + } + Err(other) => return Err(BBSPlusError::SchnorrError(other)), } // Verify the 2nd Schnorr proof diff --git a/bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs b/bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs index d341db65..6b3cdc77 100644 --- a/bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs +++ b/bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs @@ -33,7 +33,6 @@ impl ProofArbitraryRange { setup_params: SetupParams, transcript: &mut impl Transcript, ) -> Result { - // TODO: Fx base let base = 2; Self::new_with_given_base( rng, diff --git a/bulletproofs_plus_plus/src/setup.rs b/bulletproofs_plus_plus/src/setup.rs index ca926c1f..535ca650 100644 --- a/bulletproofs_plus_plus/src/setup.rs +++ b/bulletproofs_plus_plus/src/setup.rs @@ -112,6 +112,6 @@ impl SetupParams { /// 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 + core::cmp::max(num_value_bits as u32 / base_bits(base) as u32, base as u32) * num_proofs } } diff --git a/bulletproofs_plus_plus/src/weighted_norm_linear_argument.rs b/bulletproofs_plus_plus/src/weighted_norm_linear_argument.rs index cd4c4563..eb270ec9 100644 --- a/bulletproofs_plus_plus/src/weighted_norm_linear_argument.rs +++ b/bulletproofs_plus_plus/src/weighted_norm_linear_argument.rs @@ -312,6 +312,7 @@ impl WeightedNormLinearArgument { } /// Returns + {|n|^2}_mu + #[cfg(test)] fn compute_v( l: &[G::ScalarField], n: &[G::ScalarField], diff --git a/schnorr_pok/README.md b/schnorr_pok/README.md index 65ecfa7e..73219632 100644 --- a/schnorr_pok/README.md +++ b/schnorr_pok/README.md @@ -20,14 +20,21 @@ Step 4: Verifier checks that `g_1*s_1 + g_2*s_2 = y*c + t` Above can be generalized to more than 2 `x`s -There is another variant of Schnorr which gives shorter proof but is not implemented yet: +There is another variant of Schnorr which gives shorter proof but is not implemented: 1. Prover creates `r` and then `T = r * G`. 2. Prover computes challenge as `c = Hash(G||Y||T)`. 3. Prover creates response `s = r + c*x` and sends `c` and `s` to the Verifier as proof. 4. Verifier creates `T'` as `T' = s * G - c * Y` and computes `c'` as `c' = Hash(G||Y||T')` 5. Proof if valid if `c == c'` -Also implements the proof of inequality of discrete log (a value committed in a Pedersen commitment), +The problem with this variant is that it leads to poorer failure reporting as in case of failure, it can't be +pointed out which relation failed to verify. Eg. say there are 2 relations being proven which leads to 2 +`T`s `T1` and `T2` and 2 responses `s1` and `s2`. If only the responses and challenge are sent then +in case of failure, the verifier will only know that its computed challenge `c'` doesn't match prover's given +challenge `c` but won't know which response `s1` or `s2` or both were incorrect. This is not the case +with the implemented variant as verifier checks 2 equations `s1 = r1 + x1*c` and `s2 = r2 + x2*c` + +Also implements the proof of **inequality of discrete** log (a value committed in a Pedersen commitment), either with a public value or with another discrete log in [`Inequality`] [`Inequality`]: https://docs.rs/schnorr_pok/latest/schnorr_pok/inequality/ diff --git a/schnorr_pok/src/lib.rs b/schnorr_pok/src/lib.rs index 66169aae..b8fbcf63 100644 --- a/schnorr_pok/src/lib.rs +++ b/schnorr_pok/src/lib.rs @@ -18,14 +18,21 @@ //! //! Above can be generalized to more than 2 `x`s //! -//! There is another variant of Schnorr which gives shorter proof but is not implemented yet: +//! There is another variant of Schnorr which gives shorter proof but is not implemented: //! 1. Prover creates `r` and then `T = r * G`. //! 2. Prover computes challenge as `c = Hash(G||Y||T)`. //! 3. Prover creates response `s = r + c*x` and sends `c` and `s` to the Verifier as proof. //! 4. Verifier creates `T'` as `T' = s * G - c * Y` and computes `c'` as `c' = Hash(G||Y||T')` //! 5. Proof if valid if `c == c'` //! -//! Also implements the proof of inequality of discrete log (a value committed in a Pedersen commitment), +//! The problem with this variant is that it leads to poorer failure reporting as in case of failure, it can't be +//! pointed out which relation failed to verify. Eg. say there are 2 relations being proven which leads to 2 +//! `T`s `T1` and `T2` and 2 responses `s1` and `s2`. If only the responses and challenge are sent then +//! in case of failure, the verifier will only know that its computed challenge `c'` doesn't match prover's given +//! challenge `c` but won't know which response `s1` or `s2` or both were incorrect. This is not the case +//! with the implemented variant as verifier checks 2 equations `s1 = r1 + x1*c` and `s2 = r2 + x2*c` +//! +//! Also implements the proof of **inequality of discrete log** (a value committed in a Pedersen commitment), //! either with a public value or with another discrete log in [`Inequality`] //! //! [`Inequality`]: crate::inequality 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 index dac2defc..b68a8ac5 100644 --- a/smc_range_proof/src/ccs_range_proof/kv_perfect_range.rs +++ b/smc_range_proof/src/ccs_range_proof/kv_perfect_range.rs @@ -51,7 +51,6 @@ impl CCSPerfectRangeProofWithKVProtocol { comm_key: &MemberCommitmentKey, params: &SetMembershipCheckParams, ) -> Result { - // TODO: Fx me, use min, max Self::init_given_base( rng, value, diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 87d09473..4624f646 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.18.0", default-features = false, path = "../bbs_plus" } -schnorr_pok = { version = "0.16.0", default-features = false, path = "../schnorr_pok" } -vb_accumulator = { version = "0.19.0", default-features = false, path = "../vb_accumulator" } +bbs_plus = { default-features = false, path = "../bbs_plus" } +schnorr_pok = { default-features = false, path = "../schnorr_pok" } +vb_accumulator = { 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.24.0", default-features = false, path = "../proof_system"} +proof_system = { default-features = false, path = "../proof_system"} [features] default = ["parallel"] diff --git a/utils/src/transcript.rs b/utils/src/transcript.rs index a37f1240..e8c98101 100644 --- a/utils/src/transcript.rs +++ b/utils/src/transcript.rs @@ -97,6 +97,10 @@ impl Transcript for Merlin { } } +// TODO: Impl Write trait for Merlin +// TODO: Support domain-separator function that adds a label to transcript. One approach is to have MerlinTranscript struct +// that has a mutable field called write_label set which is used in call to `append_message` + #[cfg(test)] mod test { use super::*;