From a1f322a30718d0e483a3c6d20f46d1ef39d0c470 Mon Sep 17 00:00:00 2001 From: lovesh Date: Fri, 31 May 2024 11:35:39 +0530 Subject: [PATCH] Publicly verifiable secret sharing Signed-off-by: lovesh --- saver/src/commitment.rs | 7 +- schnorr_pok/src/discrete_log.rs | 10 +- schnorr_pok/src/discrete_log_pairing.rs | 2 +- .../src/bagheri_pvss/README.md | 26 ++ .../src/bagheri_pvss/different_base.rs | 335 ++++++++++++++++++ .../src/bagheri_pvss/different_base_alt.rs | 292 +++++++++++++++ .../src/bagheri_pvss/mod.rs | 73 ++++ .../src/bagheri_pvss/same_base.rs | 287 +++++++++++++++ .../src/bagheri_pvss/same_base_alt.rs | 246 +++++++++++++ .../maliciously_secure.rs | 25 +- secret_sharing_and_dkg/src/error.rs | 13 + .../src/feldman_dvss_dkg.rs | 6 +- secret_sharing_and_dkg/src/feldman_vss.rs | 7 +- secret_sharing_and_dkg/src/frost_dkg.rs | 20 +- secret_sharing_and_dkg/src/lib.rs | 1 + secret_sharing_and_dkg/src/pedersen_dvss.rs | 6 +- secret_sharing_and_dkg/src/pedersen_vss.rs | 12 +- secret_sharing_and_dkg/src/shamir_ss.rs | 9 +- .../src/cls_range_proof/range_proof.rs | 2 + test_utils/src/ot.rs | 1 + 20 files changed, 1356 insertions(+), 24 deletions(-) create mode 100644 secret_sharing_and_dkg/src/bagheri_pvss/README.md create mode 100644 secret_sharing_and_dkg/src/bagheri_pvss/different_base.rs create mode 100644 secret_sharing_and_dkg/src/bagheri_pvss/different_base_alt.rs create mode 100644 secret_sharing_and_dkg/src/bagheri_pvss/mod.rs create mode 100644 secret_sharing_and_dkg/src/bagheri_pvss/same_base.rs create mode 100644 secret_sharing_and_dkg/src/bagheri_pvss/same_base_alt.rs diff --git a/saver/src/commitment.rs b/saver/src/commitment.rs index 7a3e8600..8496a2d4 100644 --- a/saver/src/commitment.rs +++ b/saver/src/commitment.rs @@ -35,11 +35,6 @@ use dock_crypto_utils::{msm::multiply_field_elems_with_same_group_elem, serde_ut /// ``` /// /// Since `b`, `n` and `G` are public, it can be ensured that `G_i`s are correctly created. -/// -/// CAVEAT: Since the same blinding `r'` is used for `H` in both the chunked commitment `J` and the commitment -/// to the full message, they can be divided to get a value that is unique to the message and thus can -/// be used to link 2 different proofs created for the same message. One solution to this is to generate a -/// different `G` for each proof by hashing an agreed upon string appended with a counter. #[serde_as] #[derive( Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, @@ -52,7 +47,7 @@ pub struct ChunkedCommitment( impl ChunkedCommitment { /// Decompose a given field element `message` to `chunks_count` chunks each of size `chunk_bit_size` and /// create a Pedersen commitment to those chunks. say `m` is decomposed as `m_1`, `m_2`, .. `m_n`. - /// Create commitment key as multiples of `g` as `g_n, g_{n-1}, ..., g_2, g_1` using `create_gs`. Now commit as `m_1 * g_1 + m_2 * g_2 + ... + m_n * g_n + r * h` + /// Create commitment key as multiples of `g` as `g_n, g_{n-1}, ..., g_2, g_1` using `Self::commitment_key`. Now commit as `m_1 * g_1 + m_2 * g_2 + ... + m_n * g_n + r * h` /// Return the commitment and commitment key pub fn new( message: &G::ScalarField, diff --git a/schnorr_pok/src/discrete_log.rs b/schnorr_pok/src/discrete_log.rs index ef794b50..4a951f63 100644 --- a/schnorr_pok/src/discrete_log.rs +++ b/schnorr_pok/src/discrete_log.rs @@ -56,7 +56,15 @@ pub struct PokDiscreteLogProtocol { /// Proof of knowledge of discrete log #[serde_as] #[derive( - Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, + Default, + Clone, + PartialEq, + Eq, + Debug, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, )] pub struct PokDiscreteLog { #[serde_as(as = "ArkObjectBytes")] diff --git a/schnorr_pok/src/discrete_log_pairing.rs b/schnorr_pok/src/discrete_log_pairing.rs index 91d2b966..db373476 100644 --- a/schnorr_pok/src/discrete_log_pairing.rs +++ b/schnorr_pok/src/discrete_log_pairing.rs @@ -61,7 +61,7 @@ macro_rules! impl_protocol { #[serde_as] #[derive( - Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, + Default, Clone, PartialEq, Eq, Debug, CanonicalSerialize, CanonicalDeserialize, Serialize, Deserialize, )] pub struct $proof { #[serde_as(as = "ArkObjectBytes")] diff --git a/secret_sharing_and_dkg/src/bagheri_pvss/README.md b/secret_sharing_and_dkg/src/bagheri_pvss/README.md new file mode 100644 index 00000000..511aa796 --- /dev/null +++ b/secret_sharing_and_dkg/src/bagheri_pvss/README.md @@ -0,0 +1,26 @@ +# Publicly verifiable secret sharing protocols + +These allow a dealer to share commitments to the secret shares (`commitment_key * secret_share`) with a group such that a threshold number of group +members can get commitment to the secret. This sharing can happen on a public bulletin board as the dealer's +shares are encrypted for the corresponding party and anyone can verify that the shares are created correctly because the dealer +also outputs a proof. This primitive is useful for sharing secrets on a blockchain as the blockchain can verify the proof. + +Based on Fig. 7 of the paper [A Unified Framework for Verifiable Secret Sharing](https://eprint.iacr.org/2023/1669). +Implements the protocol in the paper and a variation. + +The dealer in the protocol in Fig 7. wants to share commitments to the shares of secrets of `k` - (`k_1`, `k_2`, ..., `k_n`) as (`g * k_1`, `g * k_2`, ..., `g * k_n`) +to `n` parties with secret and public keys (`s_i`, `h_i = g * s_i`) such that any `t` parties can reconstruct commitment to the secret `g * k`. +Notice the base `g` is the same in the public keys, the share commitments and the reconstructed commitment to the secret. This is implemented in [same_base](./same_base.rs) + +Let's say the dealer wants to share `j * k` where base `j` is also a group generator and discrete log of `j` wrt. `g` is not known +such that party `i` gets `j * k_i` +The dealer follows a similar protocol as above and broadcasts `y'_i = j * k_i . g * k_i = (j + g) * k_i` in addition +to `y_i = h_i * k_i` and a proof that `k_i` is the same in both `y'_i` and `y_i`. Then each party can +compute `g * k_i` as described in the paper and compute `j * k_i = y'_i - g * k_i`. Essentially, `y'_i` is +an Elgamal ciphertext, `g * k_i` is the ephemeral secret key (between the dealer and party `i`) and +`j * k_i` is the message. This is implemented in [different_base](./different_base.rs). Note that both `j` and `g` must be in the same group. + +The proof in the protocol described in the paper contains a polynomial of degree `t-1`. This adds to the proof `t` field +elements (polynomial coefficients) and requires evaluation of a `t-1` degree polynomial during proving and verification. +An alternate implementation is to have the proof contain `n` fields elements and avoid the polynomial evaluation during +proving and verification making these faster but the proofs bigger. These are implemented in [same_base_alt](./same_base_alt.rs) and [different_base_alt](./different_base_alt.rs) \ No newline at end of file diff --git a/secret_sharing_and_dkg/src/bagheri_pvss/different_base.rs b/secret_sharing_and_dkg/src/bagheri_pvss/different_base.rs new file mode 100644 index 00000000..738621c3 --- /dev/null +++ b/secret_sharing_and_dkg/src/bagheri_pvss/different_base.rs @@ -0,0 +1,335 @@ +use crate::{ + bagheri_pvss::{validate_threshold, Share}, + common::ShareId, + error::SSError, + shamir_ss, +}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{Field, PrimeField}; +use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec, vec::Vec, UniformRand}; +use digest::Digest; +use dock_crypto_utils::{expect_equality, msm::WindowTable, serde_utils::ArkObjectBytes}; +use schnorr_pok::compute_random_oracle_challenge; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Share encrypted for the party +#[serde_as] +#[derive( + Default, + Clone, + Debug, + PartialEq, + Eq, + Zeroize, + ZeroizeOnDrop, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct EncryptedShare { + #[zeroize(skip)] + pub id: ShareId, + #[zeroize(skip)] + pub threshold: ShareId, + /// Masked share `y'_i = j * k_i . g * k_i = (j + g) * k_i` + #[serde_as(as = "ArkObjectBytes")] + pub masked_share: G, + /// Mask `y_i = h_i * k_i` + #[serde_as(as = "ArkObjectBytes")] + pub mask: G, +} + +/// Proof that the correct shares are correctly encrypted for each party +#[serde_as] +#[derive( + Default, + Clone, + Debug, + PartialEq, + Eq, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct Proof { + #[serde_as(as = "ArkObjectBytes")] + pub challenge: F, + #[serde_as(as = "ArkObjectBytes")] + pub resp: DensePolynomial, +} + +/// Generate a random secret with its shares according to Shamir secret sharing and returns encrypted +/// commitments to the shares with one encryption for each public key. Assumes the public keys are given +/// in the increasing order of their ids in the context of secret sharing and number of public keys equals `total`. +/// At least `threshold` number of share-commitments are needed to reconstruct the commitment to the secret. +/// `pk_base` is the base of the public keys (`g`) and `target_base` is the base for the secret share commitment (`j`) +pub fn deal_random_secret<'a, R: RngCore, G: AffineRepr, D: Digest>( + rng: &mut R, + threshold: ShareId, + total: ShareId, + public_keys: Vec, + pk_base: &G, + target_base: &G, +) -> Result< + ( + G::ScalarField, + Vec>, + Proof, + DensePolynomial, + ), + SSError, +> { + let secret = G::ScalarField::rand(rng); + let (enc_shares, proof, poly) = deal_secret::( + rng, + secret, + threshold, + total, + public_keys, + pk_base, + target_base, + )?; + Ok((secret, enc_shares, proof, poly)) +} + +/// Same as `deal_random_secret` above but accepts the secret to share +pub fn deal_secret<'a, R: RngCore, G: AffineRepr, D: Digest>( + rng: &mut R, + secret: G::ScalarField, + threshold: ShareId, + total: ShareId, + public_keys: Vec, + pk_base: &G, + target_base: &G, +) -> Result< + ( + Vec>, + Proof, + DensePolynomial, + ), + SSError, +> { + validate_threshold(threshold, total)?; + let (shares, f) = shamir_ss::deal_secret(rng, secret, threshold, total)?; + let r = as DenseUVPolynomial>::rand( + threshold as usize - 1, + rng, + ); + debug_assert_eq!(f.degree(), r.degree()); + let mut chal_bytes = vec![]; + let mut enc_shares = vec![]; + let mask_base = WindowTable::new(total as usize, *target_base + pk_base); + mask_base.serialize_compressed(&mut chal_bytes)?; + for (i, pk) in public_keys.into_iter().enumerate() { + let share_i = &shares.0[i]; + debug_assert_eq!(share_i.id as usize, i + 1); + // Use same blinding for both relations + let blinding = r.evaluate(&G::ScalarField::from(share_i.id)); + let t_mask = pk * blinding; + // `h_i * k_i` + let mask = (pk * share_i.share).into_affine(); + let t_masked_share = mask_base.multiply(&blinding).into_affine(); + // `(j + g) * k_i` + let masked_share = mask_base.multiply(&share_i.share).into_affine(); + pk.serialize_compressed(&mut chal_bytes)?; + t_mask.serialize_compressed(&mut chal_bytes)?; + t_masked_share.serialize_compressed(&mut chal_bytes)?; + mask.serialize_compressed(&mut chal_bytes)?; + masked_share.serialize_compressed(&mut chal_bytes)?; + enc_shares.push(EncryptedShare { + id: share_i.id, + threshold: share_i.threshold, + masked_share, + mask, + }); + } + let d = compute_random_oracle_challenge::(&chal_bytes); + let z = r + (&f * d); + Ok(( + enc_shares, + Proof { + challenge: d, + resp: z, + }, + f, + )) +} + +impl Proof { + /// Assumes the public keys and encrypted shares are given in the increasing order of their ids in the context + /// of secret sharing and number of public keys equals `total` + /// `pk_base` is the base of the public keys (`g`) and `target_base` is the base for the secret share commitment (`j`) + pub fn verify, D: Digest>( + &self, + threshold: ShareId, + total: ShareId, + public_keys: Vec, + enc_shares: &[EncryptedShare], + pk_base: &G, + target_base: &G, + ) -> Result<(), SSError> { + validate_threshold(threshold, total)?; + expect_equality!( + enc_shares.len(), + public_keys.len(), + SSError::UnequalNoOfSharesAndPublicKeys + ); + if self.resp.degree() != threshold as usize - 1 { + return Err(SSError::DoesNotSupportThreshold(threshold)); + } + let mut chal_bytes = vec![]; + let mask_base = WindowTable::new(total as usize, *target_base + pk_base); + mask_base.serialize_compressed(&mut chal_bytes)?; + for (i, pk) in public_keys.into_iter().enumerate() { + let enc_share_i = &enc_shares[i]; + debug_assert_eq!(enc_share_i.id as usize, i + 1); + let resp_i = self.resp.evaluate(&G::ScalarField::from(enc_share_i.id)); + // h_i * z(i) - y_i * d + let t_mask = (pk * resp_i) - (enc_share_i.mask * self.challenge); + // (j + g) * z(i) - y'_i * d + let t_masked_share = + mask_base.multiply(&resp_i) - (enc_share_i.masked_share * self.challenge); + pk.serialize_compressed(&mut chal_bytes)?; + t_mask.serialize_compressed(&mut chal_bytes)?; + t_masked_share.serialize_compressed(&mut chal_bytes)?; + enc_share_i.mask.serialize_compressed(&mut chal_bytes)?; + enc_share_i + .masked_share + .serialize_compressed(&mut chal_bytes)?; + } + if self.challenge != compute_random_oracle_challenge::(&chal_bytes) { + return Err(SSError::InvalidProof); + } + Ok(()) + } +} + +impl EncryptedShare { + pub fn decrypt(&self, sk: &G::ScalarField) -> Share { + // y_i * 1 / s_i = g * k_i + let mask = self.mask * sk.inverse().unwrap(); + Share { + id: self.id, + threshold: self.threshold, + // (j + g) * k_i - g * k_i = j * k_i + share: (self.masked_share.into_group() - mask).into_affine(), + } + } + + // Proof of knowledge of same secret key in public key and the encrypted share can be done by existing PokTwoDiscreteLogsProtocol protocol +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::common; + use ark_bls12_381::{G1Affine, G2Affine}; + use ark_ec::VariableBaseMSM; + use ark_poly::Polynomial; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress}; + use ark_std::rand::{rngs::StdRng, SeedableRng}; + use blake2::Blake2b512; + use dock_crypto_utils::misc::n_rand; + use std::time::Instant; + use test_utils::test_serialization; + + #[test] + fn pvss_with_different_base_than_public_key() { + let mut rng = StdRng::seed_from_u64(0u64); + + fn check(rng: &mut StdRng, pk_base: G, target_base: G) { + let mut checked_serialization = false; + for (threshold, total) in vec![ + (1, 3), + (1, 4), + (2, 5), + (2, 6), + (3, 7), + (3, 10), + (4, 9), + (4, 15), + (5, 11), + (5, 13), + (7, 15), + (7, 20), + ] { + let sks = n_rand(rng, total).collect::>(); + let pks = (0..total) + .map(|i| (pk_base * &sks[i]).into_affine()) + .collect::>(); + println!("For {}-of-{} sharing", threshold, total); + let start = Instant::now(); + let (secret, enc_shares, proof, poly) = deal_random_secret::<_, G, Blake2b512>( + rng, + threshold as ShareId, + total as ShareId, + pks.clone(), + &pk_base, + &target_base, + ) + .unwrap(); + println!("Time to create shares and proof: {:?}", start.elapsed()); + println!("Proof size is {}", proof.serialized_size(Compress::Yes)); + + let start = Instant::now(); + proof + .verify::( + threshold as ShareId, + total as ShareId, + pks.clone(), + &enc_shares, + &pk_base, + &target_base, + ) + .unwrap(); + println!("Time to create verify proof: {:?}", start.elapsed()); + + let mut decrypted_shares = vec![]; + for (i, enc_share) in enc_shares.iter().enumerate() { + let dec_share = enc_share.decrypt(&sks[i]); + assert_eq!( + dec_share.share, + (target_base * poly.evaluate(&G::ScalarField::from(enc_share.id))) + .into_affine() + ); + decrypted_shares.push(dec_share); + } + + let share_ids = decrypted_shares[0..threshold] + .iter() + .map(|s| s.id) + .collect::>(); + let share_vals = decrypted_shares[0..threshold] + .iter() + .map(|s| s.share) + .collect::>(); + let basis = + common::lagrange_basis_at_0_for_all::(share_ids).unwrap(); + assert_eq!( + G::Group::msm_unchecked(&share_vals, &basis), + target_base * secret + ); + + if !checked_serialization { + test_serialization!(Proof, proof); + test_serialization!(Vec>, enc_shares); + test_serialization!(Vec>, decrypted_shares); + checked_serialization = true; + } + } + } + + let pk_base_g1 = G1Affine::rand(&mut rng); + let target_base_g1 = G1Affine::rand(&mut rng); + let pk_base_g2 = G2Affine::rand(&mut rng); + let target_base_g2 = G2Affine::rand(&mut rng); + check(&mut rng, pk_base_g1, target_base_g1); + check(&mut rng, pk_base_g2, target_base_g2); + } +} diff --git a/secret_sharing_and_dkg/src/bagheri_pvss/different_base_alt.rs b/secret_sharing_and_dkg/src/bagheri_pvss/different_base_alt.rs new file mode 100644 index 00000000..ae9f33e9 --- /dev/null +++ b/secret_sharing_and_dkg/src/bagheri_pvss/different_base_alt.rs @@ -0,0 +1,292 @@ +use crate::{ + bagheri_pvss::{different_base::EncryptedShare, validate_threshold}, + common::ShareId, + error::SSError, + shamir_ss, +}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::PrimeField; +use ark_poly::univariate::DensePolynomial; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec, vec::Vec, UniformRand}; +use digest::Digest; +use dock_crypto_utils::{ + expect_equality, misc::n_rand, msm::WindowTable, serde_utils::ArkObjectBytes, +}; +use schnorr_pok::compute_random_oracle_challenge; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +#[serde_as] +#[derive( + Default, + Clone, + Debug, + PartialEq, + Eq, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct Proof { + #[serde_as(as = "ArkObjectBytes")] + pub challenge: F, + #[serde_as(as = "ArkObjectBytes")] + pub resp: Vec, +} + +/// Generate a random secret with its shares according to Shamir secret sharing and returns encrypted +/// commitments to the shares with one encryption for each public key. Assumes the public keys are given +/// in the increasing order of their ids in the context of secret sharing and number of public keys equals `total`. +/// At least `threshold` number of share-commitments are needed to reconstruct the commitment to the secret. +/// `pk_base` is the base of the public keys (`g`) and `target_base` is the base for the secret share commitment (`j`) +pub fn deal_random_secret<'a, R: RngCore, G: AffineRepr, D: Digest>( + rng: &mut R, + threshold: ShareId, + total: ShareId, + public_keys: Vec, + pk_base: &G, + target_base: &G, +) -> Result< + ( + G::ScalarField, + Vec>, + Proof, + DensePolynomial, + ), + SSError, +> { + let secret = G::ScalarField::rand(rng); + let (enc_shares, proof, poly) = deal_secret::( + rng, + secret, + threshold, + total, + public_keys, + pk_base, + target_base, + )?; + Ok((secret, enc_shares, proof, poly)) +} + +/// Same as `deal_random_secret` above but accepts the secret to share +pub fn deal_secret<'a, R: RngCore, G: AffineRepr, D: Digest>( + rng: &mut R, + secret: G::ScalarField, + threshold: ShareId, + total: ShareId, + public_keys: Vec, + pk_base: &G, + target_base: &G, +) -> Result< + ( + Vec>, + Proof, + DensePolynomial, + ), + SSError, +> { + validate_threshold(threshold, total)?; + let (shares, f) = shamir_ss::deal_secret(rng, secret, threshold, total)?; + let r = n_rand(rng, total).collect::>(); + let mut chal_bytes = vec![]; + let mut enc_shares = vec![]; + let mask_base = WindowTable::new(total as usize, *target_base + pk_base); + mask_base.serialize_compressed(&mut chal_bytes)?; + for (i, pk) in public_keys.into_iter().enumerate() { + let share_i = &shares.0[i]; + debug_assert_eq!(share_i.id as usize, i + 1); + // Use same blinding for both relations + let blinding = &r[i]; + let t_mask = pk * blinding; + // `h_i * k_i` + let mask = (pk * share_i.share).into_affine(); + let t_masked_share = mask_base.multiply(&blinding).into_affine(); + // `(j + g) * k_i` + let masked_share = mask_base.multiply(&share_i.share).into_affine(); + pk.serialize_compressed(&mut chal_bytes)?; + t_mask.serialize_compressed(&mut chal_bytes)?; + t_masked_share.serialize_compressed(&mut chal_bytes)?; + mask.serialize_compressed(&mut chal_bytes)?; + masked_share.serialize_compressed(&mut chal_bytes)?; + enc_shares.push(EncryptedShare { + id: share_i.id, + threshold: share_i.threshold, + masked_share, + mask, + }); + } + let d = compute_random_oracle_challenge::(&chal_bytes); + let z = (0..total as usize) + .map(|i| r[i].clone() + (shares.0[i].share * d)) + .collect::>(); + Ok(( + enc_shares, + Proof { + challenge: d, + resp: z, + }, + f, + )) +} + +impl Proof { + /// Assumes the public keys and encrypted shares are given in the increasing order of their ids in the context + /// of secret sharing and number of public keys equals `total` + /// `pk_base` is the base of the public keys (`g`) and `target_base` is the base for the secret share commitment (`j`) + pub fn verify, D: Digest>( + &self, + threshold: ShareId, + total: ShareId, + public_keys: Vec, + enc_shares: &[EncryptedShare], + pk_base: &G, + target_base: &G, + ) -> Result<(), SSError> { + validate_threshold(threshold, total)?; + expect_equality!( + enc_shares.len(), + public_keys.len(), + SSError::UnequalNoOfSharesAndPublicKeys + ); + expect_equality!( + self.resp.len(), + total as usize, + SSError::UnexpectedNumberOfResponses + ); + let mut chal_bytes = vec![]; + let mask_base = WindowTable::new(total as usize, *target_base + pk_base); + mask_base.serialize_compressed(&mut chal_bytes)?; + for (i, pk) in public_keys.into_iter().enumerate() { + let enc_share_i = &enc_shares[i]; + debug_assert_eq!(enc_share_i.id as usize, i + 1); + let resp_i = &self.resp[i]; + // h_i * z_i - y_i * d + let t_mask = (pk * resp_i) - (enc_share_i.mask * self.challenge); + // (j + g) * z_i - y'_i * d + let t_masked_share = + mask_base.multiply(&resp_i) - (enc_share_i.masked_share * self.challenge); + pk.serialize_compressed(&mut chal_bytes)?; + t_mask.serialize_compressed(&mut chal_bytes)?; + t_masked_share.serialize_compressed(&mut chal_bytes)?; + enc_share_i.mask.serialize_compressed(&mut chal_bytes)?; + enc_share_i + .masked_share + .serialize_compressed(&mut chal_bytes)?; + } + if self.challenge != compute_random_oracle_challenge::(&chal_bytes) { + return Err(SSError::InvalidProof); + } + Ok(()) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::{bagheri_pvss::Share, common}; + use ark_bls12_381::{G1Affine, G2Affine}; + use ark_ec::VariableBaseMSM; + use ark_poly::Polynomial; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress}; + use ark_std::rand::{rngs::StdRng, SeedableRng}; + use blake2::Blake2b512; + use dock_crypto_utils::misc::n_rand; + use std::time::Instant; + use test_utils::test_serialization; + + #[test] + fn pvss_with_different_base_than_public_key() { + let mut rng = StdRng::seed_from_u64(0u64); + + fn check(rng: &mut StdRng, pk_base: G, target_base: G) { + let mut checked_serialization = false; + for (threshold, total) in vec![ + (1, 3), + (1, 4), + (2, 5), + (2, 6), + (3, 7), + (3, 10), + (4, 9), + (4, 15), + (5, 11), + (5, 13), + (7, 15), + (7, 20), + ] { + let sks = n_rand(rng, total).collect::>(); + let pks = (0..total) + .map(|i| (pk_base * &sks[i]).into_affine()) + .collect::>(); + println!("For {}-of-{} sharing", threshold, total); + let start = Instant::now(); + let (secret, enc_shares, proof, poly) = deal_random_secret::<_, G, Blake2b512>( + rng, + threshold as ShareId, + total as ShareId, + pks.clone(), + &pk_base, + &target_base, + ) + .unwrap(); + println!("Time to create shares and proof: {:?}", start.elapsed()); + println!("Proof size is {}", proof.serialized_size(Compress::Yes)); + + let start = Instant::now(); + proof + .verify::( + threshold as ShareId, + total as ShareId, + pks.clone(), + &enc_shares, + &pk_base, + &target_base, + ) + .unwrap(); + println!("Time to create verify proof: {:?}", start.elapsed()); + + let mut decrypted_shares = vec![]; + for (i, enc_share) in enc_shares.iter().enumerate() { + let dec_share = enc_share.decrypt(&sks[i]); + assert_eq!( + dec_share.share, + (target_base * poly.evaluate(&G::ScalarField::from(enc_share.id))) + .into_affine() + ); + decrypted_shares.push(dec_share); + } + + let share_ids = decrypted_shares[0..threshold] + .iter() + .map(|s| s.id) + .collect::>(); + let share_vals = decrypted_shares[0..threshold] + .iter() + .map(|s| s.share) + .collect::>(); + let basis = + common::lagrange_basis_at_0_for_all::(share_ids).unwrap(); + assert_eq!( + G::Group::msm_unchecked(&share_vals, &basis), + target_base * secret + ); + + if !checked_serialization { + test_serialization!(Proof, proof); + test_serialization!(Vec>, enc_shares); + test_serialization!(Vec>, decrypted_shares); + checked_serialization = true; + } + } + } + + let pk_base_g1 = G1Affine::rand(&mut rng); + let target_base_g1 = G1Affine::rand(&mut rng); + let pk_base_g2 = G2Affine::rand(&mut rng); + let target_base_g2 = G2Affine::rand(&mut rng); + check(&mut rng, pk_base_g1, target_base_g1); + check(&mut rng, pk_base_g2, target_base_g2); + } +} diff --git a/secret_sharing_and_dkg/src/bagheri_pvss/mod.rs b/secret_sharing_and_dkg/src/bagheri_pvss/mod.rs new file mode 100644 index 00000000..820cc5b5 --- /dev/null +++ b/secret_sharing_and_dkg/src/bagheri_pvss/mod.rs @@ -0,0 +1,73 @@ +//! # Publicly verifiable secret sharing protocols +//! +//! These allow a dealer to share commitments to the secret shares (`commitment_key * secret_share`) with a group such that a threshold number of group +//! members can get commitment to the secret. This sharing can happen on a public bulletin board as the dealer's +//! shares are encrypted for the corresponding party and anyone can verify that the shares are created correctly because the dealer +//! also outputs a proof. This primitive is useful for sharing secrets on a blockchain as the blockchain can verify the proof. +//! +//! Based on Fig. 7 of the paper [A Unified Framework for Verifiable Secret Sharing](https://eprint.iacr.org/2023/1669). +//! Implements the protocol in the paper and a variation. +//! +//! The dealer in the protocol in Fig 7. wants to share commitments to the shares of secrets of `k` - (`k_1`, `k_2`, ..., `k_n`) as (`g * k_1`, `g * k_2`, ..., `g * k_n`) +//! to `n` parties with secret and public keys (`s_i`, `h_i = g * s_i`) such that any `t` parties can reconstruct commitment to the secret `g * k`. +//! Notice the base `g` is the same in the public keys, the share commitments and the reconstructed commitment to the secret. This is implemented in [same_base](./same_base.rs) +//! +//! Let's say the dealer wants to share `j * k` where base `j` is also a group generator and discrete log of `j` wrt. `g` is not known +//! such that party `i` gets `j * k_i` +//! The dealer follows a similar protocol as above and broadcasts `y'_i = j * k_i . g * k_i = (j + g) * k_i` in addition +//! to `y_i = h_i * k_i` and a proof that `k_i` is the same in both `y'_i` and `y_i`. Then each party can +//! compute `g * k_i` as described in the paper and compute `j * k_i = y'_i - g * k_i`. Essentially, `y'_i` is +//! an Elgamal ciphertext, `g * k_i` is the ephemeral secret key (between the dealer and party `i`) and +//! `j * k_i` is the message. This is implemented in [different_base](./different_base.rs). Note that both `j` and `g` must be in the same group. +//! +//! The proof in the protocol described in the paper contains a polynomial of degree `t-1`. This adds to the proof `t` field +//! elements (polynomial coefficients) and requires evaluation of a `t-1` degree polynomial during proving and verification. +//! An alternate implementation is to have the proof contain `n` fields elements and avoid the polynomial evaluation during +//! proving and verification making these faster but the proofs bigger. These are implemented in [same_base_alt](./same_base_alt.rs) and [different_base_alt](./different_base_alt.rs) + +use crate::{common::ShareId, error::SSError}; +use ark_ec::AffineRepr; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::vec::Vec; +use dock_crypto_utils::serde_utils::ArkObjectBytes; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +pub mod different_base; +pub mod different_base_alt; +pub mod same_base; +pub mod same_base_alt; + +/// A commitment to the share of the secret. The commitment is of the form `g * share_i` where `g` is the public +/// commitment key and `share_i` is the i-th share. +#[serde_as] +#[derive( + Default, + Clone, + Debug, + PartialEq, + Eq, + Zeroize, + ZeroizeOnDrop, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct Share { + #[zeroize(skip)] + pub id: ShareId, + #[zeroize(skip)] + pub threshold: ShareId, + #[serde_as(as = "ArkObjectBytes")] + pub share: G, +} + +pub(crate) fn validate_threshold(threshold: ShareId, total: ShareId) -> Result<(), SSError> { + // This looks different from the paper since paper assume `t+1` parties can reconstruct but the code assumes `t` parties can reconstruct + if total < 2 * threshold { + return Err(SSError::InvalidThresholdOrTotal(threshold, total)); + } + Ok(()) +} diff --git a/secret_sharing_and_dkg/src/bagheri_pvss/same_base.rs b/secret_sharing_and_dkg/src/bagheri_pvss/same_base.rs new file mode 100644 index 00000000..12af0c3c --- /dev/null +++ b/secret_sharing_and_dkg/src/bagheri_pvss/same_base.rs @@ -0,0 +1,287 @@ +use crate::{ + bagheri_pvss::{validate_threshold, Share}, + common::ShareId, + error::SSError, + shamir_ss, +}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{Field, PrimeField}; +use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec, vec::Vec, UniformRand}; +use digest::Digest; +use dock_crypto_utils::{expect_equality, serde_utils::ArkObjectBytes}; +use schnorr_pok::compute_random_oracle_challenge; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +/// Share encrypted for the party +#[serde_as] +#[derive( + Default, + Clone, + Debug, + PartialEq, + Eq, + Zeroize, + ZeroizeOnDrop, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct EncryptedShare { + #[zeroize(skip)] + pub id: ShareId, + #[zeroize(skip)] + pub threshold: ShareId, + #[serde_as(as = "ArkObjectBytes")] + pub share: G, +} + +/// Proof that the correct shares are correctly encrypted for each party +#[serde_as] +#[derive( + Default, + Clone, + Debug, + PartialEq, + Eq, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct Proof { + /// Called `d` in the paper + #[serde_as(as = "ArkObjectBytes")] + pub challenge: F, + /// Called `z` in the paper + #[serde_as(as = "ArkObjectBytes")] + pub resp: DensePolynomial, +} + +/// Generate a random secret with its shares according to Shamir secret sharing and returns encrypted +/// commitments to the shares with one encryption for each public key. Assumes the public keys are given +/// in the increasing order of their ids in the context of secret sharing and number of public keys equals `total`. +/// At least `threshold` number of share-commitments are needed to reconstruct the commitment to the secret. +pub fn deal_random_secret<'a, R: RngCore, G: AffineRepr, D: Digest>( + rng: &mut R, + threshold: ShareId, + total: ShareId, + public_keys: Vec, +) -> Result< + ( + G::ScalarField, + Vec>, + Proof, + DensePolynomial, + ), + SSError, +> { + let secret = G::ScalarField::rand(rng); + let (enc_shares, proof, poly) = + deal_secret::(rng, secret, threshold, total, public_keys)?; + Ok((secret, enc_shares, proof, poly)) +} + +/// Same as `deal_random_secret` above but accepts the secret to share +pub fn deal_secret<'a, R: RngCore, G: AffineRepr, D: Digest>( + rng: &mut R, + secret: G::ScalarField, + threshold: ShareId, + total: ShareId, + public_keys: Vec, +) -> Result< + ( + Vec>, + Proof, + DensePolynomial, + ), + SSError, +> { + validate_threshold(threshold, total)?; + let (shares, f) = shamir_ss::deal_secret(rng, secret, threshold, total)?; + let r = as DenseUVPolynomial>::rand( + threshold as usize - 1, + rng, + ); + debug_assert_eq!(f.degree(), r.degree()); + let mut chal_bytes = vec![]; + let mut enc_shares = vec![]; + for (i, pk) in public_keys.into_iter().enumerate() { + let share_i = &shares.0[i]; + debug_assert_eq!(share_i.id as usize, i + 1); + let t = pk * r.evaluate(&G::ScalarField::from(share_i.id)); + let enc_share_i = (pk * share_i.share).into_affine(); + pk.serialize_compressed(&mut chal_bytes)?; + t.serialize_compressed(&mut chal_bytes)?; + enc_share_i.serialize_compressed(&mut chal_bytes)?; + enc_shares.push(EncryptedShare { + id: share_i.id, + threshold: share_i.threshold, + share: enc_share_i, + }); + } + let d = compute_random_oracle_challenge::(&chal_bytes); + let z = r + (&f * d); + Ok(( + enc_shares, + Proof { + challenge: d, + resp: z, + }, + f, + )) +} + +impl Proof { + /// Assumes the public keys and encrypted shares are given in the increasing order of their ids in the context + /// of secret sharing and number of public keys equals `total` + pub fn verify, D: Digest>( + &self, + threshold: ShareId, + total: ShareId, + public_keys: Vec, + enc_shares: &[EncryptedShare], + ) -> Result<(), SSError> { + validate_threshold(threshold, total)?; + expect_equality!( + enc_shares.len(), + public_keys.len(), + SSError::UnequalNoOfSharesAndPublicKeys + ); + if self.resp.degree() != threshold as usize - 1 { + return Err(SSError::DoesNotSupportThreshold(threshold)); + } + let mut chal_bytes = vec![]; + for (i, pk) in public_keys.into_iter().enumerate() { + let enc_share_i = &enc_shares[i]; + debug_assert_eq!(enc_share_i.id as usize, i + 1); + // pk * r(i) - y_i * d + let t = (pk * self.resp.evaluate(&G::ScalarField::from(enc_share_i.id))) + - (enc_share_i.share * self.challenge); + pk.serialize_compressed(&mut chal_bytes)?; + t.serialize_compressed(&mut chal_bytes)?; + enc_share_i.share.serialize_compressed(&mut chal_bytes)?; + } + if self.challenge != compute_random_oracle_challenge::(&chal_bytes) { + return Err(SSError::InvalidProof); + } + Ok(()) + } +} + +impl EncryptedShare { + /// Use the party's secret key to decrypt the share + pub fn decrypt(&self, sk: &G::ScalarField) -> Share { + Share { + id: self.id, + threshold: self.threshold, + share: (self.share * sk.inverse().unwrap()).into_affine(), + } + } + + // Proof of knowledge of same secret key in public key and the encrypted share can be done by existing PokTwoDiscreteLogsProtocol protocol +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::common; + use ark_bls12_381::{G1Affine, G2Affine}; + use ark_ec::VariableBaseMSM; + use ark_poly::Polynomial; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress}; + use ark_std::rand::{rngs::StdRng, SeedableRng}; + use blake2::Blake2b512; + use dock_crypto_utils::misc::n_rand; + use std::time::Instant; + use test_utils::test_serialization; + + #[test] + fn pvss_with_same_base_as_public_key() { + let mut rng = StdRng::seed_from_u64(0u64); + + fn check(rng: &mut StdRng, g: G) { + let mut checked_serialization = false; + for (threshold, total) in vec![ + (1, 3), + (1, 4), + (2, 5), + (2, 6), + (3, 7), + (3, 10), + (4, 9), + (4, 15), + (5, 11), + (5, 13), + (7, 15), + (7, 20), + ] { + let sks = n_rand(rng, total).collect::>(); + let pks = (0..total) + .map(|i| (g * &sks[i]).into_affine()) + .collect::>(); + + println!("For {}-of-{} sharing", threshold, total); + let start = Instant::now(); + let (secret, enc_shares, proof, poly) = deal_random_secret::<_, G, Blake2b512>( + rng, + threshold as ShareId, + total as ShareId, + pks.clone(), + ) + .unwrap(); + println!("Time to create shares and proof: {:?}", start.elapsed()); + println!("Proof size is {}", proof.serialized_size(Compress::Yes)); + + let start = Instant::now(); + proof + .verify::( + threshold as ShareId, + total as ShareId, + pks.clone(), + &enc_shares, + ) + .unwrap(); + println!("Time to create verify proof: {:?}", start.elapsed()); + + let mut decrypted_shares = vec![]; + for (i, enc_share) in enc_shares.iter().enumerate() { + let dec_share = enc_share.decrypt(&sks[i]); + assert_eq!( + dec_share.share, + (g * poly.evaluate(&G::ScalarField::from(enc_share.id))).into_affine() + ); + decrypted_shares.push(dec_share); + } + + let share_ids = decrypted_shares[0..threshold] + .iter() + .map(|s| s.id) + .collect::>(); + let share_vals = decrypted_shares[0..threshold] + .iter() + .map(|s| s.share) + .collect::>(); + let basis = + common::lagrange_basis_at_0_for_all::(share_ids).unwrap(); + assert_eq!(G::Group::msm_unchecked(&share_vals, &basis), g * secret); + + if !checked_serialization { + test_serialization!(Proof, proof); + test_serialization!(Vec>, enc_shares); + test_serialization!(Vec>, decrypted_shares); + checked_serialization = true; + } + } + } + + let g1 = G1Affine::rand(&mut rng); + let g2 = G2Affine::rand(&mut rng); + check(&mut rng, g1); + check(&mut rng, g2); + } +} diff --git a/secret_sharing_and_dkg/src/bagheri_pvss/same_base_alt.rs b/secret_sharing_and_dkg/src/bagheri_pvss/same_base_alt.rs new file mode 100644 index 00000000..c81f67d4 --- /dev/null +++ b/secret_sharing_and_dkg/src/bagheri_pvss/same_base_alt.rs @@ -0,0 +1,246 @@ +use crate::{ + bagheri_pvss::{same_base::EncryptedShare, validate_threshold}, + common::ShareId, + error::SSError, + shamir_ss, +}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::PrimeField; +use ark_poly::univariate::DensePolynomial; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{rand::RngCore, vec, vec::Vec, UniformRand}; +use digest::Digest; +use dock_crypto_utils::{expect_equality, misc::n_rand, serde_utils::ArkObjectBytes}; +use schnorr_pok::compute_random_oracle_challenge; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +/// Proof that the correct shares are correctly encrypted for each party +#[serde_as] +#[derive( + Default, + Clone, + Debug, + PartialEq, + Eq, + CanonicalSerialize, + CanonicalDeserialize, + Serialize, + Deserialize, +)] +pub struct Proof { + #[serde_as(as = "ArkObjectBytes")] + pub challenge: F, + #[serde_as(as = "ArkObjectBytes")] + pub resp: Vec, +} + +/// Generate a random secret with its shares according to Shamir secret sharing and returns encrypted +/// commitments to the shares with one encryption for each public key. Assumes the public keys are given +/// in the increasing order of their ids in the context of secret sharing +pub fn deal_random_secret<'a, R: RngCore, G: AffineRepr, D: Digest>( + rng: &mut R, + threshold: ShareId, + total: ShareId, + public_keys: Vec, +) -> Result< + ( + G::ScalarField, + Vec>, + Proof, + DensePolynomial, + ), + SSError, +> { + let secret = G::ScalarField::rand(rng); + let (enc_shares, proof, poly) = + deal_secret::(rng, secret, threshold, total, public_keys)?; + Ok((secret, enc_shares, proof, poly)) +} + +/// Same as `deal_random_secret` above but accepts the secret to share +pub fn deal_secret<'a, R: RngCore, G: AffineRepr, D: Digest>( + rng: &mut R, + secret: G::ScalarField, + threshold: ShareId, + total: ShareId, + public_keys: Vec, +) -> Result< + ( + Vec>, + Proof, + DensePolynomial, + ), + SSError, +> { + validate_threshold(threshold, total)?; + let (shares, f) = shamir_ss::deal_secret(rng, secret, threshold, total)?; + let r = n_rand(rng, total).collect::>(); + + let mut chal_bytes = vec![]; + let mut enc_shares = vec![]; + for (i, pk) in public_keys.into_iter().enumerate() { + let share_i = &shares.0[i]; + debug_assert_eq!(share_i.id as usize, i + 1); + let t = pk * &r[i]; + let enc_share_i = (pk * share_i.share).into_affine(); + pk.serialize_compressed(&mut chal_bytes)?; + t.serialize_compressed(&mut chal_bytes)?; + enc_share_i.serialize_compressed(&mut chal_bytes)?; + enc_shares.push(EncryptedShare { + id: share_i.id, + threshold: share_i.threshold, + share: enc_share_i, + }); + } + let d = compute_random_oracle_challenge::(&chal_bytes); + let z = (0..total as usize) + .map(|i| r[i].clone() + (shares.0[i].share * d)) + .collect::>(); + Ok(( + enc_shares, + Proof { + challenge: d, + resp: z, + }, + f, + )) +} + +impl Proof { + /// Assumes the public keys and encrypted shares are given in the increasing order of their ids in the context + /// of secret sharing and number of public keys equals `total` + pub fn verify, D: Digest>( + &self, + threshold: ShareId, + total: ShareId, + public_keys: Vec, + enc_shares: &[EncryptedShare], + ) -> Result<(), SSError> { + validate_threshold(threshold, total)?; + expect_equality!( + enc_shares.len(), + public_keys.len(), + SSError::UnequalNoOfSharesAndPublicKeys + ); + expect_equality!( + self.resp.len(), + total as usize, + SSError::UnexpectedNumberOfResponses + ); + let mut chal_bytes = vec![]; + for (i, pk) in public_keys.into_iter().enumerate() { + let enc_share_i = &enc_shares[i]; + debug_assert_eq!(enc_share_i.id as usize, i + 1); + // pk * r_i - y_i * d + let t = (pk * self.resp[i]) - (enc_share_i.share * self.challenge); + pk.serialize_compressed(&mut chal_bytes)?; + t.serialize_compressed(&mut chal_bytes)?; + enc_share_i.share.serialize_compressed(&mut chal_bytes)?; + } + if self.challenge != compute_random_oracle_challenge::(&chal_bytes) { + return Err(SSError::InvalidProof); + } + Ok(()) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::{bagheri_pvss::Share, common}; + use ark_bls12_381::{G1Affine, G2Affine}; + use ark_ec::VariableBaseMSM; + use ark_poly::Polynomial; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress}; + use ark_std::rand::{rngs::StdRng, SeedableRng}; + use blake2::Blake2b512; + use dock_crypto_utils::misc::n_rand; + use std::time::Instant; + use test_utils::test_serialization; + + #[test] + fn pvss_with_same_base_as_public_key() { + let mut rng = StdRng::seed_from_u64(0u64); + + fn check(rng: &mut StdRng, g: G) { + let mut checked_serialization = false; + for (threshold, total) in vec![ + (1, 3), + (1, 4), + (2, 5), + (2, 6), + (3, 7), + (3, 10), + (4, 9), + (4, 15), + (5, 11), + (5, 13), + (7, 15), + (7, 20), + ] { + let sks = n_rand(rng, total).collect::>(); + let pks = (0..total) + .map(|i| (g * &sks[i]).into_affine()) + .collect::>(); + + println!("For {}-of-{} sharing", threshold, total); + let start = Instant::now(); + let (secret, enc_shares, proof, poly) = deal_random_secret::<_, G, Blake2b512>( + rng, + threshold as ShareId, + total as ShareId, + pks.clone(), + ) + .unwrap(); + println!("Time to create shares and proof: {:?}", start.elapsed()); + println!("Proof size is {}", proof.serialized_size(Compress::Yes)); + + let start = Instant::now(); + proof + .verify::( + threshold as ShareId, + total as ShareId, + pks.clone(), + &enc_shares, + ) + .unwrap(); + println!("Time to create verify proof: {:?}", start.elapsed()); + + let mut decrypted_shares = vec![]; + for (i, enc_share) in enc_shares.iter().enumerate() { + let dec_share = enc_share.decrypt(&sks[i]); + assert_eq!( + dec_share.share, + (g * poly.evaluate(&G::ScalarField::from(enc_share.id))).into_affine() + ); + decrypted_shares.push(dec_share); + } + + let share_ids = decrypted_shares[0..threshold] + .iter() + .map(|s| s.id) + .collect::>(); + let share_vals = decrypted_shares[0..threshold] + .iter() + .map(|s| s.share) + .collect::>(); + let basis = + common::lagrange_basis_at_0_for_all::(share_ids).unwrap(); + assert_eq!(G::Group::msm_unchecked(&share_vals, &basis), g * secret); + + if !checked_serialization { + test_serialization!(Proof, proof); + test_serialization!(Vec>, enc_shares); + test_serialization!(Vec>, decrypted_shares); + checked_serialization = true; + } + } + } + + let g1 = G1Affine::rand(&mut rng); + let g2 = G2Affine::rand(&mut rng); + check(&mut rng, g1); + check(&mut rng, g2); + } +} diff --git a/secret_sharing_and_dkg/src/distributed_dlog_check/maliciously_secure.rs b/secret_sharing_and_dkg/src/distributed_dlog_check/maliciously_secure.rs index 9c120f46..531b8055 100644 --- a/secret_sharing_and_dkg/src/distributed_dlog_check/maliciously_secure.rs +++ b/secret_sharing_and_dkg/src/distributed_dlog_check/maliciously_secure.rs @@ -107,6 +107,7 @@ macro_rules! impl_protocol { /// Proof that the computation on the share was done correctly #[serde_as] #[derive( + Default, Clone, Debug, PartialEq, @@ -325,7 +326,11 @@ macro_rules! impl_protocol { } } - /// Deal a secret. + /// Generate shares of the given secret and return the scalar multiplication of the share and commitment + /// key and thus returning a commitment to the share of the form `comm_key * share`. + /// At least `threshold` number of share-commitment are needed to reconstruct the commitment to secret. + /// Returns the share-commitments, commitments to coefficients of the polynomials for + /// the secret and the polynomial pub fn $deal_secret<'a, R: RngCore, E: Pairing>( rng: &mut R, secret: E::ScalarField, @@ -454,6 +459,7 @@ pub mod tests { macro_rules! check { ($secret_share: ident, $secret_share_comm: ident, $comp_share: ident, $comp_share_proof: ident, $deal_func: ident, $secret_group: ident, $other_group: ident, $pairing: tt, $ck_secret: expr, $ck_poly: expr) => { let base = $other_group::rand(&mut rng); + let mut checked_serialization = true; for (threshold, total) in vec![ (2, 2), (2, 3), @@ -478,7 +484,10 @@ pub mod tests { let (shares, commitments, _) = $deal_func(&mut rng, secret, threshold, total, $ck_secret, $ck_poly).unwrap(); - test_serialization!($secret_share, shares[0]); + + if !checked_serialization { + test_serialization!($secret_share, shares[0]); + } for share in &shares { // Wrong share fails to verify @@ -496,7 +505,11 @@ pub mod tests { let computation_shares = cfg_into_iter!(shares.clone()) .map(|s| $comp_share::new(&s, &base)) .collect::>(); - test_serialization!($comp_share, computation_shares[0]); + + if !checked_serialization { + test_serialization!($comp_share, computation_shares[0]); + } + let result = $comp_share::combine(computation_shares.clone()).unwrap(); assert_eq!(result, expected_result); @@ -537,6 +550,10 @@ pub mod tests { // Verification with incorrect secret share fails assert!(proof.verify::(&shares[0], &share_comms[i], &share_comm_ck, &base).is_err()); + + // if !checked_serialization { + // test_serialization!($comp_share_proof, proof); + // } } shares.push(share); @@ -564,6 +581,8 @@ pub mod tests { println!("Time to verify {} proofs using randomized pairing checker with lazy={}: {:?}", proofs.len(), lazy, start.elapsed()); assert!(checker.verify()); } + + checked_serialization = true; } } } diff --git a/secret_sharing_and_dkg/src/error.rs b/secret_sharing_and_dkg/src/error.rs index a67bd3d2..8727594a 100644 --- a/secret_sharing_and_dkg/src/error.rs +++ b/secret_sharing_and_dkg/src/error.rs @@ -1,4 +1,6 @@ use crate::common::{ParticipantId, ShareId}; +use ark_serialize::SerializationError; +use dock_crypto_utils::serde_utils::ArkSerializationError; use schnorr_pok::error::SchnorrError; use serde::Serialize; @@ -24,6 +26,11 @@ pub enum SSError { UnequalNoOfProofsAndShares(usize, usize), UnequalNoOfProofsAndCommitments(usize, usize), XCordCantBeZero, + InvalidProof, + #[serde(with = "ArkSerializationError")] + Serialization(SerializationError), + UnequalNoOfSharesAndPublicKeys(usize, usize), + UnexpectedNumberOfResponses(usize, usize), } impl From for SSError { @@ -31,3 +38,9 @@ impl From for SSError { Self::SchnorrError(e) } } + +impl From for SSError { + fn from(e: SerializationError) -> Self { + Self::Serialization(e) + } +} diff --git a/secret_sharing_and_dkg/src/feldman_dvss_dkg.rs b/secret_sharing_and_dkg/src/feldman_dvss_dkg.rs index e87a92cf..e5b33f31 100644 --- a/secret_sharing_and_dkg/src/feldman_dvss_dkg.rs +++ b/secret_sharing_and_dkg/src/feldman_dvss_dkg.rs @@ -201,6 +201,7 @@ pub mod tests { let g2 = G2::rand(&mut rng); fn check(rng: &mut StdRng, g: &G) { + let mut checked_serialization = true; for (threshold, total) in vec![ (2, 2), (2, 3), @@ -328,7 +329,10 @@ pub mod tests { } } - test_serialization!(SharesAccumulator, accumulators[0].clone()); + if !checked_serialization { + test_serialization!(SharesAccumulator, accumulators[0].clone()); + checked_serialization = true; + } let mut tk = None; let mut all_pk = vec![]; diff --git a/secret_sharing_and_dkg/src/feldman_vss.rs b/secret_sharing_and_dkg/src/feldman_vss.rs index fb4d9b50..726c379e 100644 --- a/secret_sharing_and_dkg/src/feldman_vss.rs +++ b/secret_sharing_and_dkg/src/feldman_vss.rs @@ -16,6 +16,7 @@ use crate::{ }; /// Generate a random secret with its shares according to Feldman's verifiable secret sharing. +/// At least `threshold` number of shares are needed to reconstruct the secret. /// Returns the secret, shares, and commitments to coefficients of the polynomials for /// the secret and the polynomial pub fn deal_random_secret<'a, R: RngCore, G: AffineRepr>( @@ -106,6 +107,7 @@ pub mod tests { let g2 = ::G2Affine::rand(&mut rng); fn check(rng: &mut StdRng, g: &G) { + let mut checked_serialization = true; for (threshold, total) in vec![ (2, 2), (2, 3), @@ -141,7 +143,10 @@ pub mod tests { assert_eq!(shares.reconstruct_secret().unwrap(), secret); - test_serialization!(CommitmentToCoefficients, commitments); + if !checked_serialization { + test_serialization!(CommitmentToCoefficients, commitments); + checked_serialization = true; + } } } diff --git a/secret_sharing_and_dkg/src/frost_dkg.rs b/secret_sharing_and_dkg/src/frost_dkg.rs index 478715fe..83bffe09 100644 --- a/secret_sharing_and_dkg/src/frost_dkg.rs +++ b/secret_sharing_and_dkg/src/frost_dkg.rs @@ -266,6 +266,7 @@ pub mod tests { let g2 = G2::rand(&mut rng); fn check(rng: &mut StdRng, pub_key_base: &G) { + let mut checked_serialization = true; for (threshold, total) in vec![ (2, 2), (2, 3), @@ -309,8 +310,10 @@ pub mod tests { all_round1_msgs.push(round1_msg); } - test_serialization!(Round1State, all_round1_states[0].clone()); - test_serialization!(Round1Msg, all_round1_msgs[0].clone()); + if !checked_serialization { + test_serialization!(Round1State, all_round1_states[0].clone()); + test_serialization!(Round1Msg, all_round1_msgs[0].clone()); + } // Each participant receives message during Round 1 for i in 0..total { @@ -356,7 +359,9 @@ pub mod tests { } } - test_serialization!(Round1State, all_round1_states[i].clone()); + if !checked_serialization { + test_serialization!(Round1State, all_round1_states[i].clone()); + } } // Each participant ends Round 1 and begins Round 2 @@ -367,7 +372,9 @@ pub mod tests { all_shares.push(shares); } - test_serialization!(Round2State, all_round2_states[0].clone()); + if !checked_serialization { + test_serialization!(Round2State, all_round2_states[0].clone()); + } // Each participant receives shares and commitments during Round2 for i in 0..total { @@ -439,7 +446,9 @@ pub mod tests { assert!(all_round2_states[i].clone().finish(pub_key_base).is_err()); } - test_serialization!(Round2State, all_round2_states[i].clone()); + if !checked_serialization { + test_serialization!(Round2State, all_round2_states[i].clone()); + } } // Each participant ends Round2 @@ -482,6 +491,7 @@ pub mod tests { .unwrap() ) ); + checked_serialization = true; } } diff --git a/secret_sharing_and_dkg/src/lib.rs b/secret_sharing_and_dkg/src/lib.rs index 781d8812..6900982b 100644 --- a/secret_sharing_and_dkg/src/lib.rs +++ b/secret_sharing_and_dkg/src/lib.rs @@ -1,5 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] +pub mod bagheri_pvss; pub mod common; pub mod distributed_dlog_check; pub mod error; diff --git a/secret_sharing_and_dkg/src/pedersen_dvss.rs b/secret_sharing_and_dkg/src/pedersen_dvss.rs index e830ee85..666df4de 100644 --- a/secret_sharing_and_dkg/src/pedersen_dvss.rs +++ b/secret_sharing_and_dkg/src/pedersen_dvss.rs @@ -178,6 +178,7 @@ pub mod tests { let comm_key2 = PedersenCommitmentKey::::new::(b"test"); fn check(rng: &mut StdRng, comm_key: &PedersenCommitmentKey) { + let mut checked_serialization = false; for (threshold, total) in vec![ (2, 2), (2, 3), @@ -314,7 +315,10 @@ pub mod tests { } } - test_serialization!(SharesAccumulator, accumulators[0].clone()); + if !checked_serialization { + test_serialization!(SharesAccumulator, accumulators[0].clone()); + checked_serialization = true; + } for accumulator in accumulators { let share = accumulator.finalize(comm_key).unwrap(); diff --git a/secret_sharing_and_dkg/src/pedersen_vss.rs b/secret_sharing_and_dkg/src/pedersen_vss.rs index bd8a707f..0cc82e98 100644 --- a/secret_sharing_and_dkg/src/pedersen_vss.rs +++ b/secret_sharing_and_dkg/src/pedersen_vss.rs @@ -25,6 +25,7 @@ use crate::{ }; /// Generate a random secret with its shares according to Pedersen's verifiable secret sharing. +/// At least `threshold` number of shares are needed to reconstruct the secret. /// Returns the secret, blinding, shares, Pedersen commitments to coefficients of the polynomials for /// the secret and blinding and the polynomials pub fn deal_random_secret( @@ -164,6 +165,7 @@ pub mod tests { let comm_key2 = PedersenCommitmentKey::::new::(b"test"); fn check(rng: &mut StdRng, comm_key: &PedersenCommitmentKey) { + let mut checked_serialization = false; for (threshold, total) in vec![ (2, 2), (2, 3), @@ -209,9 +211,13 @@ pub mod tests { assert_eq!(s, secret); assert_eq!(t, blinding); - test_serialization!(VerifiableShares, shares); - test_serialization!(VerifiableShare, shares.0[0]); - test_serialization!(CommitmentToCoefficients, commitments); + // Test serialization + if !checked_serialization { + test_serialization!(VerifiableShares, shares); + test_serialization!(VerifiableShare, shares.0[0]); + test_serialization!(CommitmentToCoefficients, commitments); + checked_serialization = true; + } } } diff --git a/secret_sharing_and_dkg/src/shamir_ss.rs b/secret_sharing_and_dkg/src/shamir_ss.rs index 0ac626af..87e6d051 100644 --- a/secret_sharing_and_dkg/src/shamir_ss.rs +++ b/secret_sharing_and_dkg/src/shamir_ss.rs @@ -14,6 +14,7 @@ use crate::{ use rayon::prelude::*; /// Generate a random secret with its shares according to Shamir secret sharing. +/// At least `threshold` number of shares are needed to reconstruct the secret. /// Returns the secret, shares and the polynomial whose evaluations are the secret and the shares pub fn deal_random_secret( rng: &mut R, @@ -93,6 +94,7 @@ pub mod tests { assert!(deal_random_secret::<_, Fr>(&mut rng, 1, 1).is_err()); assert!(deal_random_secret::<_, Fr>(&mut rng, 5, 4).is_err()); + let mut checked_serialization = false; for (threshold, total) in vec![ (2, 2), (2, 3), @@ -128,8 +130,11 @@ pub mod tests { assert_eq!(shares.reconstruct_secret().unwrap(), secret); // Test serialization - test_serialization!(Shares, shares); - test_serialization!(Share, shares.0[0]); + if !checked_serialization { + test_serialization!(Shares, shares); + test_serialization!(Share, shares.0[0]); + checked_serialization = true; + } } } } diff --git a/smc_range_proof/src/cls_range_proof/range_proof.rs b/smc_range_proof/src/cls_range_proof/range_proof.rs index ac6f1d68..8879a3d7 100644 --- a/smc_range_proof/src/cls_range_proof/range_proof.rs +++ b/smc_range_proof/src/cls_range_proof/range_proof.rs @@ -51,6 +51,7 @@ pub struct CLSRangeProof { pub z_r: E::ScalarField, } +#[allow(dead_code)] impl CLSRangeProofProtocol { pub fn init( rng: &mut R, @@ -203,6 +204,7 @@ impl CLSRangeProofProtocol { } } +#[allow(dead_code)] impl CLSRangeProof { pub fn verify( &self, diff --git a/test_utils/src/ot.rs b/test_utils/src/ot.rs index dd90cf18..d3b1599d 100644 --- a/test_utils/src/ot.rs +++ b/test_utils/src/ot.rs @@ -31,6 +31,7 @@ pub fn do_pairwise_base_ot( num_parties: u16, all_party_set: BTreeSet, ) -> Vec { + #[allow(non_snake_case)] let B = ::G1Affine::rand(rng); let mut base_ots = vec![]; let mut sender_pks = BTreeMap::new();