Skip to content

Commit

Permalink
Fix BBS proof generation
Browse files Browse the repository at this point in the history
Signed-off-by: lovesh <[email protected]>
  • Loading branch information
lovesh committed Oct 19, 2023
1 parent cf9cfc8 commit ef3650a
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 54 deletions.
7 changes: 4 additions & 3 deletions bbs_plus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down
93 changes: 53 additions & 40 deletions bbs_plus/src/proof_23_alternate.rs → bbs_plus/src/proof_23_cdl.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
//! 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
//! knowledge or proving predicates about the messages in zero-knowledge using LegoSnark where the proof
//! 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::{
Expand All @@ -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,
};
Expand All @@ -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(
Expand All @@ -72,9 +68,11 @@ pub struct PoKOfSignature23G1Protocol<E: Pairing> {
#[zeroize(skip)]
#[serde_as(as = "ArkObjectBytes")]
pub d: E::G1Affine,
/// For proving relation `B_bar - d = A_bar * -e`
pub sc_comm_1: KnowledgeOfEProtocol<E::G1Affine>,
/// 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<E::G1Affine>,
#[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<E::G1Affine>,
#[serde_as(as = "Vec<ArkObjectBytes>")]
Expand All @@ -94,9 +92,11 @@ pub struct PoKOfSignature23G1Proof<E: Pairing> {
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<E::G1Affine>,
/// 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<E::G1Affine>,
/// 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<E::G1Affine>,
Expand Down Expand Up @@ -137,29 +137,34 @@ impl<E: Pairing> PoKOfSignature23G1Protocol<E> {
}

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
Expand All @@ -176,7 +181,7 @@ impl<E: Pairing> PoKOfSignature23G1Protocol<E> {
.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),
);
Expand All @@ -189,6 +194,7 @@ impl<E: Pairing> PoKOfSignature23G1Protocol<E> {
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,
})
Expand Down Expand Up @@ -218,16 +224,19 @@ impl<E: Pairing> PoKOfSignature23G1Protocol<E> {
self,
challenge: &E::ScalarField,
) -> Result<PoKOfSignature23G1Proof<E>, 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,
})
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -380,12 +389,16 @@ where
h: Vec<E::G1Affine>,
) -> 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
Expand Down
1 change: 0 additions & 1 deletion bulletproofs_plus_plus/src/range_proof_arbitrary_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ impl<G: AffineRepr> ProofArbitraryRange<G> {
setup_params: SetupParams<G>,
transcript: &mut impl Transcript,
) -> Result<Self, BulletproofsPlusPlusError> {
// TODO: Fx base
let base = 2;
Self::new_with_given_base(
rng,
Expand Down
2 changes: 1 addition & 1 deletion bulletproofs_plus_plus/src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,6 @@ impl<Gr: AffineRepr> SetupParams<Gr> {

/// 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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ impl<G: AffineRepr> WeightedNormLinearArgument<G> {
}

/// Returns <c, l> + {|n|^2}_mu
#[cfg(test)]
fn compute_v(
l: &[G::ScalarField],
n: &[G::ScalarField],
Expand Down
11 changes: 9 additions & 2 deletions schnorr_pok/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
11 changes: 9 additions & 2 deletions schnorr_pok/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion smc_range_proof/src/ccs_range_proof/kv_perfect_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ impl<E: Pairing> CCSPerfectRangeProofWithKVProtocol<E> {
comm_key: &MemberCommitmentKey<E::G1Affine>,
params: &SetMembershipCheckParams<E>,
) -> Result<Self, SmcRangeProofError> {
// TODO: Fx me, use min, max
Self::init_given_base(
rng,
value,
Expand Down
8 changes: 4 additions & 4 deletions test_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
4 changes: 4 additions & 0 deletions utils/src/transcript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down

0 comments on commit ef3650a

Please sign in to comment.