From 8b26396cc26ceeddca52dc37ac9461f0bb93ecfe Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 6 Feb 2024 12:47:40 +0100
Subject: [PATCH 1/3] refactor: avoid using crypto primitives directly, part 1
---
ferveo-tdec/benches/tpke.rs | 6 +-
ferveo-tdec/src/ciphertext.rs | 14 +-
ferveo-tdec/src/decryption.rs | 34 ++--
ferveo-tdec/src/key_share.rs | 13 +-
ferveo-tdec/src/lib.rs | 34 +++-
ferveo/src/api.rs | 33 ++--
ferveo/src/dkg.rs | 50 +++--
ferveo/src/lib.rs | 64 ++++---
ferveo/src/pvss.rs | 47 +++--
ferveo/src/refresh.rs | 332 +++++++++++++++++++++-------------
10 files changed, 378 insertions(+), 249 deletions(-)
diff --git a/ferveo-tdec/benches/tpke.rs b/ferveo-tdec/benches/tpke.rs
index 420bf869..0bbba434 100644
--- a/ferveo-tdec/benches/tpke.rs
+++ b/ferveo-tdec/benches/tpke.rs
@@ -1,6 +1,6 @@
#![allow(clippy::redundant_closure)]
-use ark_bls12_381::{Bls12_381, Fr, G1Affine as G1, G2Affine as G2};
+use ark_bls12_381::{Bls12_381, Fr};
use ark_ec::pairing::Pairing;
use criterion::{
black_box, criterion_group, criterion_main, BenchmarkId, Criterion,
@@ -25,8 +25,8 @@ struct SetupShared {
shares_num: usize,
msg: Vec,
aad: Vec,
- pubkey: G1,
- privkey: G2,
+ pubkey: PublicKeyShare,
+ privkey: PrivateKeyShare,
ciphertext: Ciphertext,
shared_secret: SharedSecret,
}
diff --git a/ferveo-tdec/src/ciphertext.rs b/ferveo-tdec/src/ciphertext.rs
index 81f79389..cdaf956c 100644
--- a/ferveo-tdec/src/ciphertext.rs
+++ b/ferveo-tdec/src/ciphertext.rs
@@ -13,7 +13,10 @@ use serde_with::serde_as;
use sha2::{digest::Digest, Sha256};
use zeroize::ZeroizeOnDrop;
-use crate::{htp_bls12381_g2, Error, Result, SecretBox, SharedSecret};
+use crate::{
+ htp_bls12381_g2, Error, PrivateKeyShare, PublicKeyShare, Result, SecretBox,
+ SharedSecret,
+};
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -95,7 +98,7 @@ impl CiphertextHeader {
pub fn encrypt(
message: SecretBox>,
aad: &[u8],
- pubkey: &E::G1Affine,
+ pubkey: &PublicKeyShare,
rng: &mut impl rand::Rng,
) -> Result> {
// r
@@ -105,7 +108,8 @@ pub fn encrypt(
// h
let h_gen = E::G2Affine::generator();
- let ry_prep = E::G1Prepared::from(pubkey.mul(rand_element).into());
+ let ry_prep =
+ E::G1Prepared::from(pubkey.public_key_share.mul(rand_element).into());
// s
let product = E::pairing(ry_prep, h_gen).0;
// u
@@ -140,13 +144,13 @@ pub fn encrypt(
pub fn decrypt_symmetric(
ciphertext: &Ciphertext,
aad: &[u8],
- private_key: &E::G2Affine,
+ private_key: &PrivateKeyShare,
g_inv: &E::G1Prepared,
) -> Result> {
ciphertext.check(aad, g_inv)?;
let shared_secret = E::pairing(
E::G1Prepared::from(ciphertext.commitment),
- E::G2Prepared::from(*private_key),
+ E::G2Prepared::from(private_key.private_key_share),
)
.0;
let shared_secret = SharedSecret(shared_secret);
diff --git a/ferveo-tdec/src/decryption.rs b/ferveo-tdec/src/decryption.rs
index 0622e6a8..eb9068bd 100644
--- a/ferveo-tdec/src/decryption.rs
+++ b/ferveo-tdec/src/decryption.rs
@@ -2,6 +2,7 @@ use std::ops::Mul;
use ark_ec::{pairing::Pairing, CurveGroup};
use ark_ff::{Field, One, Zero};
+use ark_std::UniformRand;
use ferveo_common::serialization;
use itertools::{izip, zip_eq};
use rand_core::RngCore;
@@ -9,8 +10,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_with::serde_as;
use crate::{
- generate_random, Ciphertext, CiphertextHeader, PrivateKeyShare,
- PublicDecryptionContextFast, PublicDecryptionContextSimple, Result,
+ Ciphertext, CiphertextHeader, PrivateKeyShare, PublicDecryptionContextFast,
+ PublicDecryptionContextSimple, Result,
};
#[serde_as]
@@ -226,6 +227,15 @@ impl DecryptionSharePrecomputed {
}
}
+pub fn generate_random_scalars(
+ n: usize,
+ rng: &mut R,
+) -> Vec {
+ (0..n)
+ .map(|_| E::ScalarField::rand(rng))
+ .collect::>()
+}
+
// TODO: Remove this code? Currently only used in benchmarks. Move to benchmark suite?
pub fn batch_verify_decryption_shares(
pub_contexts: &[PublicDecryptionContextFast],
@@ -240,16 +250,17 @@ pub fn batch_verify_decryption_shares(
let blinding_keys = decryption_shares[0]
.iter()
.map(|d| {
- pub_contexts[d.decrypter_index]
- .blinded_key_share
- .blinding_key_prepared
- .clone()
+ E::G2Prepared::from(
+ pub_contexts[d.decrypter_index]
+ .blinded_key_share
+ .blinding_key,
+ )
})
.collect::>();
// For each ciphertext, generate num_shares random scalars
let alpha_ij = (0..num_ciphertexts)
- .map(|_| generate_random::<_, E>(num_shares, rng))
+ .map(|_| generate_random_scalars::<_, E>(num_shares, rng))
.collect::>();
let mut pairings_a = Vec::with_capacity(num_shares + 1);
@@ -302,10 +313,11 @@ pub fn verify_decryption_shares_fast(
let blinding_keys = decryption_shares
.iter()
.map(|d| {
- pub_contexts[d.decrypter_index]
- .blinded_key_share
- .blinding_key_prepared
- .clone()
+ E::G2Prepared::from(
+ pub_contexts[d.decrypter_index]
+ .blinded_key_share
+ .blinding_key,
+ )
})
.collect::>();
diff --git a/ferveo-tdec/src/key_share.rs b/ferveo-tdec/src/key_share.rs
index 2daaae56..64a9e3e4 100644
--- a/ferveo-tdec/src/key_share.rs
+++ b/ferveo-tdec/src/key_share.rs
@@ -7,6 +7,7 @@ use rand_core::RngCore;
use zeroize::ZeroizeOnDrop;
#[derive(Debug, Clone)]
+// TODO: Should we rename it to PublicKey or SharedPublicKey?
pub struct PublicKeyShare {
pub public_key_share: E::G1Affine, // A_{i, \omega_i}
}
@@ -15,16 +16,6 @@ pub struct PublicKeyShare {
pub struct BlindedKeyShare {
pub blinding_key: E::G2Affine, // [b] H
pub blinded_key_share: E::G2Affine, // [b] Z_{i, \omega_i}
- pub blinding_key_prepared: E::G2Prepared,
-}
-
-pub fn generate_random(
- n: usize,
- rng: &mut R,
-) -> Vec {
- (0..n)
- .map(|_| E::ScalarField::rand(rng))
- .collect::>()
}
impl BlindedKeyShare {
@@ -60,6 +51,7 @@ impl BlindedKeyShare {
#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
pub struct PrivateKeyShare {
+ // TODO: Replace with a tuple?
pub private_key_share: E::G2Affine,
}
@@ -68,7 +60,6 @@ impl PrivateKeyShare {
let blinding_key = E::G2Affine::generator().mul(b).into_affine();
BlindedKeyShare:: {
blinding_key,
- blinding_key_prepared: E::G2Prepared::from(blinding_key),
blinded_key_share: self.private_key_share.mul(b).into_affine(),
}
}
diff --git a/ferveo-tdec/src/lib.rs b/ferveo-tdec/src/lib.rs
index 297b066c..b5ffed22 100644
--- a/ferveo-tdec/src/lib.rs
+++ b/ferveo-tdec/src/lib.rs
@@ -77,8 +77,8 @@ pub mod test_common {
shares_num: usize,
rng: &mut impl RngCore,
) -> (
- E::G1Affine,
- E::G2Affine,
+ PublicKeyShare,
+ PrivateKeyShare,
Vec>,
) {
assert!(shares_num >= threshold);
@@ -138,7 +138,7 @@ pub mod test_common {
)
.enumerate()
{
- let private_key_share = PrivateKeyShare:: {
+ let private_key_share = PrivateKeyShare {
private_key_share: *private,
};
let b = E::ScalarField::rand(rng);
@@ -171,7 +171,15 @@ pub mod test_common {
private.public_decryption_contexts = public_contexts.clone();
}
- (pubkey.into(), privkey.into(), private_contexts)
+ (
+ PublicKeyShare {
+ public_key_share: pubkey.into(),
+ },
+ PrivateKeyShare {
+ private_key_share: privkey.into(),
+ },
+ private_contexts,
+ )
}
pub fn setup_simple(
@@ -179,8 +187,8 @@ pub mod test_common {
shares_num: usize,
rng: &mut impl rand::Rng,
) -> (
- E::G1Affine,
- E::G2Affine,
+ PublicKeyShare,
+ PrivateKeyShare,
Vec>,
) {
assert!(shares_num >= threshold);
@@ -259,15 +267,23 @@ pub mod test_common {
private.public_decryption_contexts = public_contexts.clone();
}
- (pubkey.into(), privkey.into(), private_contexts)
+ (
+ PublicKeyShare {
+ public_key_share: pubkey.into(),
+ },
+ PrivateKeyShare {
+ private_key_share: privkey.into(),
+ },
+ private_contexts,
+ )
}
pub fn setup_precomputed(
shares_num: usize,
rng: &mut impl rand::Rng,
) -> (
- E::G1Affine,
- E::G2Affine,
+ PublicKeyShare,
+ PrivateKeyShare,
Vec>,
) {
// In precomputed variant, the security threshold is equal to the number of shares
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index 7328515e..ad9d23b6 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -7,8 +7,10 @@ use bincode;
use ferveo_common::serialization;
pub use ferveo_tdec::api::{
prepare_combine_simple, share_combine_precomputed, share_combine_simple,
- Fr, G1Affine, G1Prepared, G2Affine, SecretBox, E,
+ DecryptionSharePrecomputed, Fr, G1Affine, G1Prepared, G2Affine, SecretBox,
+ E,
};
+use ferveo_tdec::PublicKeyShare;
use generic_array::{
typenum::{Unsigned, U48},
GenericArray,
@@ -17,13 +19,6 @@ use rand::RngCore;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
-pub type PublicKey = ferveo_common::PublicKey;
-pub type Keypair = ferveo_common::Keypair;
-pub type Validator = crate::Validator;
-pub type Transcript = PubliclyVerifiableSS;
-
-pub type ValidatorMessage = (Validator, Transcript);
-
#[cfg(feature = "bindings-python")]
use crate::bindings_python;
#[cfg(feature = "bindings-wasm")]
@@ -34,8 +29,11 @@ use crate::{
PubliclyVerifiableSS, Result,
};
-pub type DecryptionSharePrecomputed =
- ferveo_tdec::api::DecryptionSharePrecomputed;
+pub type PublicKey = ferveo_common::PublicKey;
+pub type Keypair = ferveo_common::Keypair;
+pub type Validator = crate::Validator;
+pub type Transcript = PubliclyVerifiableSS;
+pub type ValidatorMessage = (Validator, Transcript);
// Normally, we would use a custom trait for this, but we can't because
// the arkworks will not let us create a blanket implementation for G1Affine
@@ -58,8 +56,14 @@ pub fn encrypt(
pubkey: &DkgPublicKey,
) -> Result {
let mut rng = rand::thread_rng();
- let ciphertext =
- ferveo_tdec::api::encrypt(message, aad, &pubkey.0, &mut rng)?;
+ let ciphertext = ferveo_tdec::api::encrypt(
+ message,
+ aad,
+ &PublicKeyShare {
+ public_key_share: pubkey.0,
+ },
+ &mut rng,
+ )?;
Ok(Ciphertext(ciphertext))
}
@@ -91,7 +95,7 @@ impl Ciphertext {
}
}
-#[serde_as]
+#[serde_as] // TODO: Redundant serde_as?
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CiphertextHeader(ferveo_tdec::api::CiphertextHeader);
@@ -146,6 +150,7 @@ impl From for FerveoVariant {
#[serde_as]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct DkgPublicKey(
+ // TODO: Consider not using G1Affine directly here
#[serde_as(as = "serialization::SerdeAs")] pub(crate) G1Affine,
);
@@ -218,7 +223,7 @@ impl Dkg {
}
pub fn public_key(&self) -> DkgPublicKey {
- DkgPublicKey(self.0.public_key())
+ DkgPublicKey(self.0.public_key().public_key_share)
}
pub fn generate_transcript(
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index e8afbe30..b309449e 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -69,9 +69,14 @@ pub type PVSSMap = BTreeMap>;
#[derive(Debug, Clone)]
pub enum DkgState {
// TODO: Do we need to keep track of the block number?
- Sharing { accumulated_shares: u32, block: u32 },
+ Sharing {
+ accumulated_shares: u32,
+ block: u32,
+ },
Dealt,
- Success { public_key: E::G1Affine },
+ Success {
+ public_key: ferveo_tdec::PublicKeyShare,
+ },
Invalid,
}
@@ -178,7 +183,7 @@ impl PubliclyVerifiableDkg {
let pvss_list = self.vss.values().cloned().collect::>();
Ok(Message::Aggregate(Aggregation {
vss: aggregate(&pvss_list)?,
- public_key,
+ public_key: public_key.public_key_share,
}))
}
_ => Err(Error::InvalidDkgStateToAggregate),
@@ -186,12 +191,15 @@ impl PubliclyVerifiableDkg {
}
/// Returns the public key generated by the DKG
- pub fn public_key(&self) -> E::G1Affine {
- self.vss
- .values()
- .map(|vss| vss.coeffs[0].into_group())
- .sum::()
- .into_affine()
+ pub fn public_key(&self) -> ferveo_tdec::PublicKeyShare {
+ ferveo_tdec::PublicKeyShare {
+ public_key_share: self
+ .vss
+ .values()
+ .map(|vss| vss.coeffs[0].into_group())
+ .sum::()
+ .into_affine(),
+ }
}
/// Return a domain point for the share_index
@@ -245,7 +253,7 @@ impl PubliclyVerifiableDkg {
))
} else if vss.verify_aggregation(self).is_err() {
Err(Error::InvalidTranscriptAggregate)
- } else if &self.public_key() == public_key {
+ } else if &self.public_key().public_key_share == public_key {
Ok(())
} else {
Err(Error::InvalidDkgPublicKey)
@@ -382,6 +390,7 @@ mod test_dkg_init {
#[cfg(test)]
mod test_dealing {
use ark_ec::AffineRepr;
+ use ferveo_tdec::PublicKeyShare;
use crate::{
test_common::*, DkgParams, DkgState, DkgState::Dealt, Error,
@@ -587,7 +596,9 @@ mod test_dealing {
));
dkg.state = DkgState::Success {
- public_key: G1::zero(),
+ public_key: PublicKeyShare {
+ public_key_share: G1::zero(),
+ },
};
assert!(dkg.share(rng).is_err());
@@ -614,7 +625,9 @@ mod test_dealing {
let sender = dkg.me.clone();
dkg.state = DkgState::Success {
- public_key: G1::zero(),
+ public_key: PublicKeyShare {
+ public_key_share: G1::zero(),
+ },
};
assert!(dkg.verify_message(&sender, &pvss).is_err());
assert!(dkg.apply_message(&sender, &pvss).is_err());
@@ -631,6 +644,7 @@ mod test_dealing {
#[cfg(test)]
mod test_aggregation {
use ark_ec::AffineRepr;
+ use ferveo_tdec::PublicKeyShare;
use test_case::test_case;
use crate::{dkg::*, test_common::*, DkgState, Message};
@@ -649,7 +663,7 @@ mod test_aggregation {
if let Message::Aggregate(Aggregation { public_key, .. }) =
&aggregate_msg
{
- assert_eq!(public_key, &dkg.public_key());
+ assert_eq!(public_key, &dkg.public_key().public_key_share);
} else {
panic!("Expected aggregate message")
}
@@ -669,7 +683,9 @@ mod test_aggregation {
};
assert!(dkg.aggregate().is_err());
dkg.state = DkgState::Success {
- public_key: G1::zero(),
+ public_key: PublicKeyShare {
+ public_key_share: G1::zero(),
+ },
};
assert!(dkg.aggregate().is_err());
}
@@ -690,7 +706,9 @@ mod test_aggregation {
assert!(dkg.apply_message(&sender, &aggregate).is_err());
dkg.state = DkgState::Success {
- public_key: G1::zero(),
+ public_key: PublicKeyShare {
+ public_key_share: G1::zero(),
+ },
};
assert!(dkg.verify_message(&sender, &aggregate).is_err());
assert!(dkg.apply_message(&sender, &aggregate).is_err())
@@ -713,7 +731,7 @@ mod test_aggregation {
fn test_aggregate_wont_verify_if_wrong_key() {
let (dkg, _) = setup_dealt_dkg();
let mut aggregate = dkg.aggregate().unwrap();
- while dkg.public_key() == G1::zero() {
+ while dkg.public_key().public_key_share == G1::zero() {
let (_dkg, _) = setup_dealt_dkg();
}
if let Message::Aggregate(Aggregation { public_key, .. }) =
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index f9d6c1a5..0e9cc3ca 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -422,13 +422,14 @@ mod test_dkg_full {
let share_updates = remaining_validators
.keys()
.map(|v_addr| {
- let deltas_i = prepare_share_updates_for_recovery::(
- &domain_points,
- &dkg.pvss_params.h.into_affine(),
- &x_r,
- dkg.dkg_params.security_threshold() as usize,
- rng,
- );
+ let deltas_i =
+ ShareRecoveryUpdate::make_share_updates_for_recovery(
+ &domain_points,
+ &dkg.pvss_params.h.into_affine(),
+ &x_r,
+ dkg.dkg_params.security_threshold() as usize,
+ rng,
+ );
(v_addr.clone(), deltas_i)
})
.collect::>();
@@ -444,8 +445,9 @@ mod test_dkg_full {
let updates_for_participant: Vec<_> = share_updates
.values()
.map(|updates| {
- *updates.get(validator.share_index as usize).unwrap()
+ updates.get(validator.share_index as usize).unwrap()
})
+ .cloned()
.collect();
// Each validator uses their decryption key to update their share
@@ -459,7 +461,7 @@ mod test_dkg_full {
let pvss_list = dkg.vss.values().cloned().collect::>();
let pvss_aggregated = aggregate(&pvss_list).unwrap();
pvss_aggregated
- .update_private_key_share_for_recovery(
+ .make_updated_private_key_share(
&decryption_key,
validator.share_index as usize,
updates_for_participant.as_slice(),
@@ -468,14 +470,13 @@ mod test_dkg_full {
})
.collect();
- // TODO: Rename updated_private_shares to something that doesn't imply mutation (see #162, #163)
-
// Now, we have to combine new share fragments into a new share
- let new_private_key_share = recover_share_from_updated_private_shares(
- &x_r,
- &domain_points,
- &updated_shares,
- );
+ let recovered_key_share =
+ PrivateKeyShare::recover_share_from_updated_private_shares(
+ &x_r,
+ &domain_points,
+ &updated_shares,
+ );
// Get decryption shares from remaining participants
let mut remaining_validator_keypairs = validator_keypairs;
@@ -508,7 +509,7 @@ mod test_dkg_full {
decryption_shares.push(
DecryptionShareSimple::create(
&new_validator_decryption_key,
- &new_private_key_share,
+ &recovered_key_share,
&ciphertext.header().unwrap(),
AAD,
&dkg.pvss_params.g_inv(),
@@ -566,12 +567,13 @@ mod test_dkg_full {
.validators
.keys()
.map(|v_addr| {
- let deltas_i = prepare_share_updates_for_refresh::(
- &dkg.domain_points(),
- &dkg.pvss_params.h.into_affine(),
- dkg.dkg_params.security_threshold() as usize,
- rng,
- );
+ let deltas_i =
+ ShareRefreshUpdate::make_share_updates_for_refresh(
+ &dkg.domain_points(),
+ &dkg.pvss_params.h.into_affine(),
+ dkg.dkg_params.security_threshold() as usize,
+ rng,
+ );
(v_addr.clone(), deltas_i)
})
.collect::>();
@@ -580,7 +582,7 @@ mod test_dkg_full {
// Now, every participant separately:
// TODO: Move this logic outside tests (see #162, #163)
- let updated_shares: Vec<_> = dkg
+ let updated_private_key_shares: Vec<_> = dkg
.validators
.values()
.map(|validator| {
@@ -588,7 +590,10 @@ mod test_dkg_full {
let updates_for_participant: Vec<_> = share_updates
.values()
.map(|updates| {
- *updates.get(validator.share_index as usize).unwrap()
+ updates
+ .get(validator.share_index as usize)
+ .cloned()
+ .unwrap()
})
.collect();
@@ -603,7 +608,7 @@ mod test_dkg_full {
let pvss_list = dkg.vss.values().cloned().collect::>();
let pvss_aggregated = aggregate(&pvss_list).unwrap();
pvss_aggregated
- .update_private_key_share_for_recovery(
+ .make_updated_private_key_share(
&decryption_key,
validator.share_index as usize,
updates_for_participant.as_slice(),
@@ -620,7 +625,12 @@ mod test_dkg_full {
.map(|(share_index, validator_keypair)| {
DecryptionShareSimple::create(
&validator_keypair.decryption_key,
- updated_shares.get(share_index).unwrap(),
+ // In order to proceed with the decryption, we need to convert the updated private key shares
+ &updated_private_key_shares
+ .get(share_index)
+ .unwrap()
+ .inner()
+ .0,
&ciphertext.header().unwrap(),
AAD,
&dkg.pvss_params.g_inv(),
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 5108f6b9..1d68622e 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -8,7 +8,7 @@ use ark_poly::{
};
use ferveo_tdec::{
prepare_combine_simple, CiphertextHeader, DecryptionSharePrecomputed,
- DecryptionShareSimple, PrivateKeyShare,
+ DecryptionShareSimple,
};
use itertools::Itertools;
use rand::RngCore;
@@ -18,9 +18,9 @@ use subproductdomain::fast_multiexp;
use zeroize::{self, Zeroize, ZeroizeOnDrop};
use crate::{
- apply_updates_to_private_share, assert_no_share_duplicates,
- batch_to_projective_g1, batch_to_projective_g2, Error, PVSSMap,
- PubliclyVerifiableDkg, Result, Validator,
+ assert_no_share_duplicates, batch_to_projective_g1, batch_to_projective_g2,
+ Error, PVSSMap, PrivateKeyShare, PrivateKeyShareUpdate,
+ PubliclyVerifiableDkg, Result, UpdatedPrivateKeyShare, Validator,
};
/// These are the blinded evaluations of shares of a single random polynomial
@@ -305,7 +305,7 @@ impl PubliclyVerifiableSS {
validator_decryption_key: &E::ScalarField,
share_index: usize,
) -> Result> {
- // Decrypt private key shares https://nikkolasg.github.io/ferveo/pvss.html#validator-decryption-of-private-key-shares
+ // Decrypt private key share https://nikkolasg.github.io/ferveo/pvss.html#validator-decryption-of-private-key-shares
let private_key_share = self
.shares
.get(share_index)
@@ -316,7 +316,10 @@ impl PubliclyVerifiableSS {
.expect("Validator decryption key must have an inverse"),
)
.into_affine();
- Ok(PrivateKeyShare { private_key_share })
+ // TODO: Consider adding a from trait to simplify this conversion
+ let private_key_share =
+ ferveo_tdec::PrivateKeyShare { private_key_share };
+ Ok(PrivateKeyShare(private_key_share))
}
pub fn make_decryption_share_simple(
@@ -331,7 +334,7 @@ impl PubliclyVerifiableSS {
.decrypt_private_key_share(validator_decryption_key, share_index)?;
DecryptionShareSimple::create(
validator_decryption_key,
- &private_key_share,
+ &private_key_share.0,
ciphertext,
aad,
g_inv,
@@ -357,7 +360,7 @@ impl PubliclyVerifiableSS {
DecryptionSharePrecomputed::new(
share_index,
validator_decryption_key,
- &private_key_share,
+ &private_key_share.0,
ciphertext_header,
aad,
&lagrange_coeffs[share_index],
@@ -366,22 +369,16 @@ impl PubliclyVerifiableSS {
.map_err(|e| e.into())
}
- // TODO: Consider relocate to different place, maybe PrivateKeyShare? (see #162, #163)
- pub fn update_private_key_share_for_recovery(
+ pub fn make_updated_private_key_share(
&self,
validator_decryption_key: &E::ScalarField,
share_index: usize,
- share_updates: &[E::G2],
- ) -> Result> {
- // Retrieves their private key share
- let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index)?;
-
- // And updates their share
- Ok(apply_updates_to_private_share::(
- &private_key_share,
- share_updates,
- ))
+ share_updates: &[impl PrivateKeyShareUpdate],
+ ) -> Result> {
+ // Retrieve the private key share and apply the updates
+ Ok(self
+ .decrypt_private_key_share(validator_decryption_key, share_index)?
+ .make_updated_key_share(share_updates))
}
}
@@ -436,8 +433,8 @@ mod test_pvss {
/// Test the happy flow such that the PVSS with the correct form is created
/// and that appropriate validations pass
- #[test_case(4,4; "number of validators is equal to the number of shares")]
- #[test_case(4,6; "number of validators is greater than the number of shares")]
+ #[test_case(4, 4; "number of validators is equal to the number of shares")]
+ #[test_case(4, 6; "number of validators is greater than the number of shares")]
fn test_new_pvss(shares_num: u32, validators_num: u32) {
let rng = &mut ark_std::test_rng();
let security_threshold = shares_num - 1;
@@ -511,8 +508,8 @@ mod test_pvss {
/// Check that happy flow of aggregating PVSS transcripts
/// has the correct form and it's validations passes
- #[test_case(4,4; "number of validators is equal to the number of shares")]
- #[test_case(4,6; "number of validators is greater than the number of shares")]
+ #[test_case(4, 4; "number of validators is equal to the number of shares")]
+ #[test_case(4, 6; "number of validators is greater than the number of shares")]
fn test_aggregate_pvss(shares_num: u32, validators_num: u32) {
let security_threshold = shares_num - 1;
let (dkg, _) = setup_dealt_dkg_with_n_validators(
diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs
index b02eba3b..761481fa 100644
--- a/ferveo/src/refresh.rs
+++ b/ferveo/src/refresh.rs
@@ -1,86 +1,155 @@
use std::{ops::Mul, usize};
-use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
+use ark_ec::{pairing::Pairing, CurveGroup};
use ark_ff::Zero;
use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial};
-use ferveo_tdec::{lagrange_basis_at, PrivateKeyShare};
+use ferveo_tdec::lagrange_basis_at;
use itertools::zip_eq;
use rand_core::RngCore;
+use zeroize::ZeroizeOnDrop;
-// SHARE UPDATE FUNCTIONS:
+type InnerPrivateKeyShare = ferveo_tdec::PrivateKeyShare;
-/// From PSS paper, section 4.2.1, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
-pub fn prepare_share_updates_for_recovery(
- domain_points: &[E::ScalarField],
- h: &E::G2Affine,
- x_r: &E::ScalarField,
- threshold: usize,
- rng: &mut impl RngCore,
-) -> Vec {
- // Update polynomial has root at x_r
- prepare_share_updates_with_root::(domain_points, h, x_r, threshold, rng)
+/// Private key share held by a participant in the DKG protocol.
+#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
+pub struct PrivateKeyShare(pub InnerPrivateKeyShare);
+
+impl PrivateKeyShare {
+ /// From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
+ pub fn make_updated_key_share(
+ &self,
+ share_updates: &[impl PrivateKeyShareUpdate],
+ ) -> UpdatedPrivateKeyShare {
+ let updated_key_share = share_updates
+ .iter()
+ .fold(self.0.private_key_share, |acc, delta| {
+ (acc + delta.inner().private_key_share).into()
+ });
+ let updated_key_share = InnerPrivateKeyShare {
+ private_key_share: updated_key_share,
+ };
+ UpdatedPrivateKeyShare(updated_key_share)
+ }
+
+ /// From the PSS paper, section 4.2.4, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
+ pub fn recover_share_from_updated_private_shares(
+ x_r: &E::ScalarField,
+ domain_points: &[E::ScalarField],
+ updated_private_shares: &[UpdatedPrivateKeyShare],
+ ) -> ferveo_tdec::PrivateKeyShare {
+ // Interpolate new shares to recover y_r
+ let lagrange = lagrange_basis_at::(domain_points, x_r);
+ let prods = zip_eq(updated_private_shares, lagrange)
+ .map(|(y_j, l)| y_j.0.private_key_share.mul(l));
+ let y_r = prods.fold(E::G2::zero(), |acc, y_j| acc + y_j);
+ ferveo_tdec::PrivateKeyShare {
+ private_key_share: y_r.into_affine(),
+ }
+ }
}
-// TODO: Consider relocating to PrivateKeyShare (see #162, #163)
-/// From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
-pub fn apply_updates_to_private_share(
- private_key_share: &PrivateKeyShare,
- share_updates: &[E::G2],
-) -> PrivateKeyShare {
- let private_key_share = share_updates
- .iter()
- .fold(
- private_key_share.private_key_share.into_group(),
- |acc, delta| acc + delta,
- )
- .into_affine();
- PrivateKeyShare { private_key_share }
+/// An updated private key share, resulting from an intermediate step in a share recovery or refresh operation.
+#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
+pub struct UpdatedPrivateKeyShare(InnerPrivateKeyShare);
+
+impl UpdatedPrivateKeyShare {
+ /// One-way conversion from `UpdatedPrivateKeyShare` to `PrivateKeyShare`.
+ /// Use this method to eject from the `UpdatedPrivateKeyShare` type and use the resulting `PrivateKeyShare` in further operations.
+ pub fn inner(&self) -> PrivateKeyShare {
+ PrivateKeyShare(self.0.clone())
+ }
}
-/// From the PSS paper, section 4.2.4, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
-pub fn recover_share_from_updated_private_shares(
- x_r: &E::ScalarField,
- domain_points: &[E::ScalarField],
- updated_private_shares: &[PrivateKeyShare],
-) -> PrivateKeyShare {
- // Interpolate new shares to recover y_r
- let lagrange = lagrange_basis_at::(domain_points, x_r);
- let prods = zip_eq(updated_private_shares, lagrange)
- .map(|(y_j, l)| y_j.private_key_share.mul(l));
- let y_r = prods.fold(E::G2::zero(), |acc, y_j| acc + y_j);
-
- PrivateKeyShare {
- private_key_share: y_r.into_affine(),
+impl UpdatedPrivateKeyShare {
+ pub fn new(private_key_share: InnerPrivateKeyShare) -> Self {
+ Self(private_key_share)
}
}
-// SHARE REFRESH FUNCTIONS:
+// TODO: Replace with an into trait?
+/// Trait for types that can be used to update a private key share.
+pub trait PrivateKeyShareUpdate {
+ fn inner(&self) -> &InnerPrivateKeyShare; // TODO: Should we return g2affine instead?
+}
+
+/// An update to a private key share generated by a participant in a share recovery operation.
+#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
+pub struct ShareRecoveryUpdate(InnerPrivateKeyShare);
-pub fn prepare_share_updates_for_refresh(
- domain_points: &[E::ScalarField],
- h: &E::G2Affine,
- threshold: usize,
- rng: &mut impl RngCore,
-) -> Vec {
- // Update polynomial has root at 0
- prepare_share_updates_with_root::(
- domain_points,
- h,
- &E::ScalarField::zero(),
- threshold,
- rng,
- )
+impl PrivateKeyShareUpdate for ShareRecoveryUpdate {
+ fn inner(&self) -> &InnerPrivateKeyShare {
+ &self.0
+ }
+}
+
+impl ShareRecoveryUpdate {
+ /// From PSS paper, section 4.2.1, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
+ pub fn make_share_updates_for_recovery(
+ domain_points: &[E::ScalarField],
+ h: &E::G2Affine,
+ x_r: &E::ScalarField,
+ threshold: usize,
+ rng: &mut impl RngCore,
+ ) -> Vec> {
+ // Update polynomial has root at x_r
+ prepare_share_updates_with_root::(
+ domain_points,
+ h,
+ x_r,
+ threshold,
+ rng,
+ )
+ .iter()
+ .map(|p| Self(p.clone()))
+ .collect()
+ }
}
-// UTILS:
+/// An update to a private key share generated by a participant in a share refresh operation.
+#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
+pub struct ShareRefreshUpdate(InnerPrivateKeyShare);
+impl PrivateKeyShareUpdate for ShareRefreshUpdate {
+ fn inner(&self) -> &InnerPrivateKeyShare {
+ &self.0
+ }
+}
+
+impl ShareRefreshUpdate {
+ /// From PSS paper, section 4.2.1, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
+ pub fn make_share_updates_for_refresh(
+ domain_points: &[E::ScalarField],
+ h: &E::G2Affine,
+ threshold: usize,
+ rng: &mut impl RngCore,
+ ) -> Vec> {
+ // Update polynomial has root at 0
+ prepare_share_updates_with_root::(
+ domain_points,
+ h,
+ &E::ScalarField::zero(),
+ threshold,
+ rng,
+ )
+ .iter()
+ .cloned()
+ .map(|p| ShareRefreshUpdate(p))
+ .collect()
+ }
+}
+
+/// Prepare share updates with a given root
+/// This is a helper function for `ShareRecoveryUpdate::make_share_updates_for_recovery` and `ShareRefreshUpdate::make_share_updates_for_refresh`
+/// It generates a new random polynomial with a defined root and evaluates it at each of the participants' indices.
+/// The result is a list of share updates.
+/// We represent the share updates as `InnerPrivateKeyShare` to avoid dependency on the concrete implementation of `PrivateKeyShareUpdate`.
fn prepare_share_updates_with_root(
domain_points: &[E::ScalarField],
h: &E::G2Affine,
root: &E::ScalarField,
threshold: usize,
rng: &mut impl RngCore,
-) -> Vec {
+) -> Vec> {
// Generate a new random polynomial with defined root
let d_i = make_random_polynomial_with_root::(threshold - 1, root, rng);
@@ -89,12 +158,16 @@ fn prepare_share_updates_with_root(
.iter()
.map(|x_i| {
let eval = d_i.evaluate(x_i);
- h.mul(eval)
+ h.mul(eval).into_affine()
+ })
+ .map(|p| InnerPrivateKeyShare {
+ private_key_share: p,
})
.collect()
}
-pub fn make_random_polynomial_with_root(
+/// Generate a random polynomial with a given root
+fn make_random_polynomial_with_root(
degree: usize,
root: &E::ScalarField,
rng: &mut impl RngCore,
@@ -124,23 +197,21 @@ mod tests_refresh {
use ark_std::{test_rng, UniformRand, Zero};
use ferveo_tdec::{
test_common::setup_simple, PrivateDecryptionContextSimple,
- PrivateKeyShare,
};
use rand_core::RngCore;
use test_case::test_matrix;
use crate::{
- apply_updates_to_private_share, prepare_share_updates_for_recovery,
- prepare_share_updates_for_refresh,
- recover_share_from_updated_private_shares, test_common::*,
+ test_common::*, PrivateKeyShare, ShareRecoveryUpdate,
+ ShareRefreshUpdate, UpdatedPrivateKeyShare,
};
- fn make_new_share_fragments_for_recovery(
+ fn make_updated_private_key_shares(
rng: &mut R,
threshold: usize,
x_r: &Fr,
remaining_participants: &[PrivateDecryptionContextSimple],
- ) -> Vec> {
+ ) -> Vec> {
// Each participant prepares an update for each other participant
// TODO: Extract as parameter
let domain_points = remaining_participants[0]
@@ -152,36 +223,36 @@ mod tests_refresh {
let share_updates = remaining_participants
.iter()
.map(|p| {
- let deltas_i = prepare_share_updates_for_recovery::(
- &domain_points,
- &h,
- x_r,
- threshold,
- rng,
- );
+ let deltas_i =
+ ShareRecoveryUpdate::make_share_updates_for_recovery(
+ &domain_points,
+ &h,
+ x_r,
+ threshold,
+ rng,
+ );
(p.index, deltas_i)
})
.collect::>();
// Participants share updates and update their shares
- let new_share_fragments: Vec<_> = remaining_participants
+ let updated_private_key_shares: Vec<_> = remaining_participants
.iter()
.map(|p| {
// Current participant receives updates from other participants
let updates_for_participant: Vec<_> = share_updates
.values()
- .map(|updates| *updates.get(p.index).unwrap())
+ .map(|updates| updates.get(p.index).cloned().unwrap())
.collect();
// And updates their share
- apply_updates_to_private_share::(
- &p.private_key_share,
- &updates_for_participant,
- )
+ // TODO: Remove wrapping after migrating to dkg based tests
+ PrivateKeyShare(p.private_key_share.clone())
+ .make_updated_key_share(&updates_for_participant)
})
.collect();
- new_share_fragments
+ updated_private_key_shares
}
/// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share.
@@ -205,14 +276,14 @@ mod tests_refresh {
.domain;
let original_private_key_share = selected_participant.private_key_share;
- // Remove one participant from the contexts and all nested structures
+ // Remove the selected participant from the contexts and all nested structures
let mut remaining_participants = contexts;
for p in &mut remaining_participants {
p.public_decryption_contexts.pop().unwrap();
}
// Each participant prepares an update for each other participant, and uses it to create a new share fragment
- let new_share_fragments = make_new_share_fragments_for_recovery(
+ let updated_private_key_shares = make_updated_private_key_shares(
rng,
security_threshold,
&x_r,
@@ -225,21 +296,22 @@ mod tests_refresh {
.iter()
.map(|ctxt| ctxt.domain)
.collect::>();
- let new_private_key_share = recover_share_from_updated_private_shares(
- &x_r,
- &domain_points[..security_threshold],
- &new_share_fragments[..security_threshold],
- );
+ let new_private_key_share =
+ PrivateKeyShare::recover_share_from_updated_private_shares(
+ &x_r,
+ &domain_points[..security_threshold],
+ &updated_private_key_shares[..security_threshold],
+ );
assert_eq!(new_private_key_share, original_private_key_share);
// If we don't have enough private share updates, the resulting private share will be incorrect
- assert_eq!(domain_points.len(), new_share_fragments.len());
+ assert_eq!(domain_points.len(), updated_private_key_shares.len());
let incorrect_private_key_share =
- recover_share_from_updated_private_shares(
+ PrivateKeyShare::recover_share_from_updated_private_shares(
&x_r,
&domain_points[..(security_threshold - 1)],
- &new_share_fragments[..(security_threshold - 1)],
+ &updated_private_key_shares[..(security_threshold - 1)],
);
assert_ne!(incorrect_private_key_share, original_private_key_share);
@@ -270,7 +342,7 @@ mod tests_refresh {
let x_r = ScalarField::rand(rng);
// Each participant prepares an update for each other participant, and uses it to create a new share fragment
- let new_share_fragments = make_new_share_fragments_for_recovery(
+ let share_recovery_fragmetns = make_updated_private_key_shares(
rng,
threshold,
&x_r,
@@ -283,11 +355,12 @@ mod tests_refresh {
.iter()
.map(|ctxt| ctxt.domain)
.collect::>();
- let new_private_key_share = recover_share_from_updated_private_shares(
- &x_r,
- &domain_points[..threshold],
- &new_share_fragments[..threshold],
- );
+ let recovered_private_key_share =
+ PrivateKeyShare::recover_share_from_updated_private_shares(
+ &x_r,
+ &domain_points[..threshold],
+ &share_recovery_fragmetns[..threshold],
+ );
let mut private_shares = contexts
.iter()
@@ -297,18 +370,23 @@ mod tests_refresh {
// Finally, let's recreate the shared private key from some original shares and the recovered one
domain_points.push(x_r);
- private_shares.push(new_private_key_share);
+ private_shares.push(recovered_private_key_share);
+ // This is a workaround for a type mismatch - We need to convert the private shares to updated private shares
+ // This is just to test that we are able to recover the shared private key from the updated private shares
+ let updated_private_key_shares = private_shares
+ .iter()
+ .cloned()
+ .map(UpdatedPrivateKeyShare::new)
+ .collect::>();
let start_from = shares_num - threshold;
- let new_shared_private_key = recover_share_from_updated_private_shares(
- &ScalarField::zero(),
- &domain_points[start_from..],
- &private_shares[start_from..],
- );
+ let new_shared_private_key =
+ PrivateKeyShare::recover_share_from_updated_private_shares(
+ &ScalarField::zero(),
+ &domain_points[start_from..],
+ &updated_private_key_shares[start_from..],
+ );
- assert_eq!(
- shared_private_key,
- new_shared_private_key.private_key_share
- );
+ assert_eq!(shared_private_key, new_shared_private_key);
}
/// Ñ parties (where t <= Ñ <= N) jointly execute a "share refresh" algorithm.
@@ -319,7 +397,7 @@ mod tests_refresh {
let rng = &mut test_rng();
let threshold = shares_num * 2 / 3;
- let (_, shared_private_key, contexts) =
+ let (_, private_key_share, contexts) =
setup_simple::(threshold, shares_num, rng);
let domain_points = &contexts[0]
@@ -333,12 +411,13 @@ mod tests_refresh {
let share_updates = contexts
.iter()
.map(|p| {
- let deltas_i = prepare_share_updates_for_refresh::(
- domain_points,
- &h,
- threshold,
- rng,
- );
+ let deltas_i =
+ ShareRefreshUpdate::::make_share_updates_for_refresh(
+ domain_points,
+ &h,
+ threshold,
+ rng,
+ );
(p.index, deltas_i)
})
.collect::>();
@@ -350,27 +429,24 @@ mod tests_refresh {
// Current participant receives updates from other participants
let updates_for_participant: Vec<_> = share_updates
.values()
- .map(|updates| *updates.get(p.index).unwrap())
+ .map(|updates| updates.get(p.index).cloned().unwrap())
.collect();
- // And updates their share
- apply_updates_to_private_share::(
- &p.private_key_share,
- &updates_for_participant,
- )
+ // And creates a new, refreshed share
+ // TODO: Remove wrapping after migrating to dkg based tests
+ PrivateKeyShare(p.private_key_share.clone())
+ .make_updated_key_share(&updates_for_participant)
})
.collect();
// Finally, let's recreate the shared private key from the refreshed shares
- let new_shared_private_key = recover_share_from_updated_private_shares(
- &ScalarField::zero(),
- &domain_points[..threshold],
- &refreshed_shares[..threshold],
- );
+ let new_shared_private_key =
+ PrivateKeyShare::recover_share_from_updated_private_shares(
+ &ScalarField::zero(),
+ &domain_points[..threshold],
+ &refreshed_shares[..threshold],
+ );
- assert_eq!(
- shared_private_key,
- new_shared_private_key.private_key_share
- );
+ assert_eq!(private_key_share, new_shared_private_key);
}
}
From cfa8c990aa166623d4c596f2a4eb5638ab8a8848 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 7 Feb 2024 13:26:06 +0100
Subject: [PATCH 2/3] refactor: avoid using crypto primitives directly, part 2
---
ferveo/src/api.rs | 8 +--
ferveo/src/dkg.rs | 25 +++++---
ferveo/src/lib.rs | 99 +++++++++++++++++------------
ferveo/src/pvss.rs | 94 +++++++++++++---------------
ferveo/src/refresh.rs | 141 ++++++++++++++++++++++++++++++++----------
5 files changed, 234 insertions(+), 133 deletions(-)
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index ad9d23b6..01c6f0ab 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -351,8 +351,8 @@ impl AggregatedTranscript {
self.0.make_decryption_share_simple_precomputed(
&ciphertext_header.0,
aad,
- &validator_keypair.decryption_key,
- dkg.0.me.share_index as usize,
+ validator_keypair,
+ dkg.0.me.share_index,
&dkg.0.domain_points(),
&dkg.0.pvss_params.g_inv(),
)
@@ -368,8 +368,8 @@ impl AggregatedTranscript {
let share = self.0.make_decryption_share_simple(
&ciphertext_header.0,
aad,
- &validator_keypair.decryption_key,
- dkg.0.me.share_index as usize,
+ validator_keypair,
+ dkg.0.me.share_index,
&dkg.0.pvss_params.g_inv(),
)?;
let domain_point = dkg.0.get_domain_point(dkg.0.me.share_index)?;
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index b309449e..bc4acdfd 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -11,8 +11,8 @@ use serde_with::serde_as;
use crate::{
aggregate, assert_no_share_duplicates, AggregatedPvss, Error,
- EthereumAddress, PubliclyVerifiableParams, PubliclyVerifiableSS, Result,
- Validator,
+ EthereumAddress, PrivateKeyShare, PubliclyVerifiableParams,
+ PubliclyVerifiableSS, Result, Validator,
};
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
@@ -216,13 +216,24 @@ impl PubliclyVerifiableDkg {
self.domain.elements().take(self.validators.len()).collect()
}
- /// `payload` is the content of the message
+ /// Return a private key for the share_index
+ pub fn get_private_key_share(
+ &self,
+ keypair: &ferveo_common::Keypair,
+ ) -> Result> {
+ // TODO: Use self.aggregate upon simplifying Message handling
+ let pvss_list = self.vss.values().cloned().collect::>();
+ aggregate(&pvss_list)
+ .unwrap()
+ .decrypt_private_key_share(keypair, self.me.share_index)
+ }
+
pub fn verify_message(
&self,
sender: &Validator,
- payload: &Message,
+ message: &Message,
) -> Result<()> {
- match payload {
+ match message {
Message::Deal(pvss)
if matches!(
self.state,
@@ -650,8 +661,8 @@ mod test_aggregation {
use crate::{dkg::*, test_common::*, DkgState, Message};
/// Test that if the security threshold is met, we can create a final key
- #[test_case(4,4; "number of validators equal to the number of shares")]
- #[test_case(4,6; "number of validators greater than the number of shares")]
+ #[test_case(4, 4; "number of validators equal to the number of shares")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_aggregate(shares_num: u32, validators_num: u32) {
let security_threshold = shares_num - 1;
let (mut dkg, _) = setup_dealt_dkg_with_n_validators(
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 0e9cc3ca..8ef4c8e7 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -171,8 +171,8 @@ mod test_dkg_full {
.make_decryption_share_simple(
ciphertext_header,
aad,
- &validator_keypair.decryption_key,
- validator.share_index as usize,
+ validator_keypair,
+ validator.share_index,
&dkg.pvss_params.g_inv(),
)
.unwrap()
@@ -280,8 +280,8 @@ mod test_dkg_full {
.make_decryption_share_simple_precomputed(
&ciphertext.header().unwrap(),
AAD,
- &validator_keypair.decryption_key,
- validator.share_index as usize,
+ validator_keypair,
+ validator.share_index,
&domain_points,
&dkg.pvss_params.g_inv(),
)
@@ -305,7 +305,8 @@ mod test_dkg_full {
assert_eq!(plaintext, MSG);
}
- #[test_case(4, 4; "number of validators equal to the number of shares")]
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
#[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_dkg_simple_tdec_share_verification(
shares_num: u32,
@@ -376,12 +377,21 @@ mod test_dkg_full {
));
}
- #[test]
- fn test_dkg_simple_tdec_share_recovery() {
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
+ fn test_dkg_simple_tdec_share_recovery(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
let rng = &mut test_rng();
+ let security_threshold = shares_num / 2 + 1;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM);
+ let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let public_key = &dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
@@ -400,6 +410,7 @@ mod test_dkg_full {
);
// Remove one participant from the contexts and all nested structure
+ // TODO: Improve by removing a random participant and/or adding test cases
let removed_validator_addr =
dkg.validators.keys().last().unwrap().clone();
let mut remaining_validators = dkg.validators.clone();
@@ -427,7 +438,7 @@ mod test_dkg_full {
&domain_points,
&dkg.pvss_params.h.into_affine(),
&x_r,
- dkg.dkg_params.security_threshold() as usize,
+ dkg.dkg_params.security_threshold(),
rng,
);
(v_addr.clone(), deltas_i)
@@ -451,19 +462,18 @@ mod test_dkg_full {
.collect();
// Each validator uses their decryption key to update their share
- let decryption_key = validator_keypairs
+ let validator_keypair = validator_keypairs
.get(validator.share_index as usize)
- .unwrap()
- .decryption_key;
+ .unwrap();
// Creates updated private key shares
- // TODO: Why not using dkg.aggregate()?
+ // TODO: Use self.aggregate upon simplifying Message handling
let pvss_list = dkg.vss.values().cloned().collect::>();
let pvss_aggregated = aggregate(&pvss_list).unwrap();
pvss_aggregated
.make_updated_private_key_share(
- &decryption_key,
- validator.share_index as usize,
+ validator_keypair,
+ validator.share_index,
updates_for_participant.as_slice(),
)
.unwrap()
@@ -488,7 +498,7 @@ mod test_dkg_full {
.iter()
.enumerate()
.map(|(share_index, validator_keypair)| {
- // TODO: Why not using dkg.aggregate()?
+ // TODO: Use self.aggregate upon simplifying Message handling
let pvss_list =
dkg.vss.values().cloned().collect::>();
let pvss_aggregated = aggregate(&pvss_list).unwrap();
@@ -496,8 +506,8 @@ mod test_dkg_full {
.make_decryption_share_simple(
&ciphertext.header().unwrap(),
AAD,
- &validator_keypair.decryption_key,
- share_index,
+ validator_keypair,
+ share_index as u32,
&dkg.pvss_params.g_inv(),
)
.unwrap()
@@ -509,7 +519,7 @@ mod test_dkg_full {
decryption_shares.push(
DecryptionShareSimple::create(
&new_validator_decryption_key,
- &recovered_key_share,
+ &recovered_key_share.0,
&ciphertext.header().unwrap(),
AAD,
&dkg.pvss_params.g_inv(),
@@ -518,14 +528,15 @@ mod test_dkg_full {
);
domain_points.push(x_r);
- assert_eq!(domain_points.len(), SHARES_NUM as usize);
- assert_eq!(decryption_shares.len(), SHARES_NUM as usize);
+ assert_eq!(domain_points.len(), validators_num as usize);
+ assert_eq!(decryption_shares.len(), validators_num as usize);
- // Maybe parametrize this test with [1..] and [..threshold]
- let domain_points = &domain_points[1..];
- let decryption_shares = &decryption_shares[1..];
- assert_eq!(domain_points.len(), SECURITY_THRESHOLD as usize);
- assert_eq!(decryption_shares.len(), SECURITY_THRESHOLD as usize);
+ // TODO: Maybe parametrize this test with [1..] and [..threshold]
+ let domain_points = &domain_points[..security_threshold as usize];
+ let decryption_shares =
+ &decryption_shares[..security_threshold as usize];
+ assert_eq!(domain_points.len(), security_threshold as usize);
+ assert_eq!(decryption_shares.len(), security_threshold as usize);
let lagrange = ferveo_tdec::prepare_combine_simple::(domain_points);
let new_shared_secret = ferveo_tdec::share_combine_simple::(
@@ -539,12 +550,21 @@ mod test_dkg_full {
);
}
- #[test]
- fn test_dkg_simple_tdec_share_refreshing() {
+ #[test_case(4, 4; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6; "number of validators greater than the number of shares")]
+ fn test_dkg_simple_tdec_share_refreshing(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
let rng = &mut test_rng();
+ let security_threshold = shares_num / 2 + 1;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM);
+ let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let public_key = &dkg.public_key();
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
@@ -571,7 +591,7 @@ mod test_dkg_full {
ShareRefreshUpdate::make_share_updates_for_refresh(
&dkg.domain_points(),
&dkg.pvss_params.h.into_affine(),
- dkg.dkg_params.security_threshold() as usize,
+ dkg.dkg_params.security_threshold(),
rng,
);
(v_addr.clone(), deltas_i)
@@ -598,19 +618,18 @@ mod test_dkg_full {
.collect();
// Each validator uses their decryption key to update their share
- let decryption_key = validator_keypairs
+ let validator_keypair = validator_keypairs
.get(validator.share_index as usize)
- .unwrap()
- .decryption_key;
+ .unwrap();
// Creates updated private key shares
- // TODO: Why not using dkg.aggregate()?
+ // TODO: Use self.aggregate upon simplifying Message handling
let pvss_list = dkg.vss.values().cloned().collect::>();
let pvss_aggregated = aggregate(&pvss_list).unwrap();
pvss_aggregated
.make_updated_private_key_share(
- &decryption_key,
- validator.share_index as usize,
+ validator_keypair,
+ validator.share_index,
updates_for_participant.as_slice(),
)
.unwrap()
@@ -640,10 +659,10 @@ mod test_dkg_full {
.collect();
let lagrange = ferveo_tdec::prepare_combine_simple::(
- &dkg.domain_points()[..SECURITY_THRESHOLD as usize],
+ &dkg.domain_points()[..security_threshold as usize],
);
let new_shared_secret = ferveo_tdec::share_combine_simple::(
- &decryption_shares[..SECURITY_THRESHOLD as usize],
+ &decryption_shares[..security_threshold as usize],
&lagrange,
);
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 1d68622e..39919f9a 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -6,9 +6,9 @@ use ark_poly::{
polynomial::univariate::DensePolynomial, DenseUVPolynomial,
EvaluationDomain, Polynomial,
};
+use ferveo_common::Keypair;
use ferveo_tdec::{
- prepare_combine_simple, CiphertextHeader, DecryptionSharePrecomputed,
- DecryptionShareSimple,
+ CiphertextHeader, DecryptionSharePrecomputed, DecryptionShareSimple,
};
use itertools::Itertools;
use rand::RngCore;
@@ -24,6 +24,7 @@ use crate::{
};
/// These are the blinded evaluations of shares of a single random polynomial
+// TODO: Are these really blinded like in tdec or encrypted?
pub type ShareEncryptions = ::G2Affine;
/// Marker struct for unaggregated PVSS transcripts
@@ -302,82 +303,75 @@ impl PubliclyVerifiableSS {
pub fn decrypt_private_key_share(
&self,
- validator_decryption_key: &E::ScalarField,
- share_index: usize,
+ validator_keypair: &Keypair,
+ share_index: u32,
) -> Result> {
// Decrypt private key share https://nikkolasg.github.io/ferveo/pvss.html#validator-decryption-of-private-key-shares
- let private_key_share = self
- .shares
- .get(share_index)
- .ok_or(Error::InvalidShareIndex(share_index as u32))?
- .mul(
- validator_decryption_key
- .inverse()
- .expect("Validator decryption key must have an inverse"),
- )
- .into_affine();
- // TODO: Consider adding a from trait to simplify this conversion
let private_key_share =
- ferveo_tdec::PrivateKeyShare { private_key_share };
- Ok(PrivateKeyShare(private_key_share))
+ self.shares
+ .get(share_index as usize)
+ .ok_or(Error::InvalidShareIndex(share_index))?
+ .mul(
+ validator_keypair.decryption_key.inverse().expect(
+ "Validator decryption key must have an inverse",
+ ),
+ )
+ .into_affine();
+ Ok(PrivateKeyShare(ferveo_tdec::PrivateKeyShare {
+ private_key_share,
+ }))
}
+ /// Make a decryption share (simple variant) for a given ciphertext
+ /// With this method, we wrap the PrivateKeyShare method to avoid exposing the private key share
pub fn make_decryption_share_simple(
&self,
ciphertext: &CiphertextHeader,
aad: &[u8],
- validator_decryption_key: &E::ScalarField,
- share_index: usize,
+ validator_keypair: &Keypair,
+ share_index: u32,
g_inv: &E::G1Prepared,
) -> Result> {
- let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index)?;
- DecryptionShareSimple::create(
- validator_decryption_key,
- &private_key_share.0,
- ciphertext,
- aad,
- g_inv,
- )
- .map_err(|e| e.into())
+ self.decrypt_private_key_share(validator_keypair, share_index)?
+ .make_decryption_share_simple(
+ ciphertext,
+ aad,
+ validator_keypair,
+ g_inv,
+ )
}
+ /// Make a decryption share (precomputed variant) for a given ciphertext
+ /// With this method, we wrap the PrivateKeyShare method to avoid exposing the private key share
pub fn make_decryption_share_simple_precomputed(
&self,
ciphertext_header: &CiphertextHeader,
aad: &[u8],
- validator_decryption_key: &E::ScalarField,
- share_index: usize,
+ validator_keypair: &Keypair,
+ share_index: u32,
domain_points: &[E::ScalarField],
g_inv: &E::G1Prepared,
) -> Result> {
- let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index)?;
-
- // We use the `prepare_combine_simple` function to precompute the lagrange coefficients
- let lagrange_coeffs = prepare_combine_simple::(domain_points);
-
- DecryptionSharePrecomputed::new(
- share_index,
- validator_decryption_key,
- &private_key_share.0,
- ciphertext_header,
- aad,
- &lagrange_coeffs[share_index],
- g_inv,
- )
- .map_err(|e| e.into())
+ self.decrypt_private_key_share(validator_keypair, share_index)?
+ .make_decryption_share_simple_precomputed(
+ ciphertext_header,
+ aad,
+ validator_keypair,
+ share_index,
+ domain_points,
+ g_inv,
+ )
}
pub fn make_updated_private_key_share(
&self,
- validator_decryption_key: &E::ScalarField,
- share_index: usize,
+ validator_keypair: &Keypair,
+ share_index: u32,
share_updates: &[impl PrivateKeyShareUpdate],
) -> Result> {
// Retrieve the private key share and apply the updates
Ok(self
- .decrypt_private_key_share(validator_decryption_key, share_index)?
+ .decrypt_private_key_share(validator_keypair, share_index)?
.make_updated_key_share(share_updates))
}
}
diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs
index 761481fa..126e281a 100644
--- a/ferveo/src/refresh.rs
+++ b/ferveo/src/refresh.rs
@@ -3,17 +3,31 @@ use std::{ops::Mul, usize};
use ark_ec::{pairing::Pairing, CurveGroup};
use ark_ff::Zero;
use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial};
-use ferveo_tdec::lagrange_basis_at;
+use ferveo_common::Keypair;
+use ferveo_tdec::{
+ lagrange_basis_at, prepare_combine_simple, CiphertextHeader,
+ DecryptionSharePrecomputed, DecryptionShareSimple,
+};
use itertools::zip_eq;
use rand_core::RngCore;
use zeroize::ZeroizeOnDrop;
+use crate::{Error, Result};
+
+// TODO: Rename refresh.rs to key_share.rs?
+
type InnerPrivateKeyShare = ferveo_tdec::PrivateKeyShare;
/// Private key share held by a participant in the DKG protocol.
#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
pub struct PrivateKeyShare(pub InnerPrivateKeyShare);
+impl PrivateKeyShare {
+ pub fn new(private_key_share: InnerPrivateKeyShare) -> Self {
+ Self(private_key_share)
+ }
+}
+
impl PrivateKeyShare {
/// From PSS paper, section 4.2.3, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
pub fn make_updated_key_share(
@@ -36,19 +50,64 @@ impl PrivateKeyShare {
x_r: &E::ScalarField,
domain_points: &[E::ScalarField],
updated_private_shares: &[UpdatedPrivateKeyShare],
- ) -> ferveo_tdec::PrivateKeyShare {
+ ) -> PrivateKeyShare {
// Interpolate new shares to recover y_r
let lagrange = lagrange_basis_at::(domain_points, x_r);
let prods = zip_eq(updated_private_shares, lagrange)
.map(|(y_j, l)| y_j.0.private_key_share.mul(l));
let y_r = prods.fold(E::G2::zero(), |acc, y_j| acc + y_j);
- ferveo_tdec::PrivateKeyShare {
+ PrivateKeyShare(ferveo_tdec::PrivateKeyShare {
private_key_share: y_r.into_affine(),
- }
+ })
+ }
+
+ pub fn make_decryption_share_simple(
+ &self,
+ ciphertext: &CiphertextHeader,
+ aad: &[u8],
+ validator_keypair: &Keypair,
+ g_inv: &E::G1Prepared,
+ ) -> Result> {
+ DecryptionShareSimple::create(
+ &validator_keypair.decryption_key,
+ &self.0,
+ ciphertext,
+ aad,
+ g_inv,
+ )
+ .map_err(|e| e.into())
+ }
+
+ pub fn make_decryption_share_simple_precomputed(
+ &self,
+ ciphertext_header: &CiphertextHeader,
+ aad: &[u8],
+ validator_keypair: &Keypair,
+ share_index: u32,
+ domain_points: &[E::ScalarField],
+ g_inv: &E::G1Prepared,
+ ) -> Result> {
+ // In precomputed variant, we offload the some of the decryption related computation to the server-side:
+ // We use the `prepare_combine_simple` function to precompute the lagrange coefficients
+ let lagrange_coeffs = prepare_combine_simple::(domain_points);
+ let lagrange_coeff = &lagrange_coeffs
+ .get(share_index as usize)
+ .ok_or(Error::InvalidShareIndex(share_index))?;
+ DecryptionSharePrecomputed::new(
+ share_index as usize,
+ &validator_keypair.decryption_key,
+ &self.0,
+ ciphertext_header,
+ aad,
+ lagrange_coeff,
+ g_inv,
+ )
+ .map_err(|e| e.into())
}
}
/// An updated private key share, resulting from an intermediate step in a share recovery or refresh operation.
+// TODO: After recovery, should we replace existing private key shares with updated ones?
#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
pub struct UpdatedPrivateKeyShare(InnerPrivateKeyShare);
@@ -88,7 +147,7 @@ impl ShareRecoveryUpdate {
domain_points: &[E::ScalarField],
h: &E::G2Affine,
x_r: &E::ScalarField,
- threshold: usize,
+ threshold: u32,
rng: &mut impl RngCore,
) -> Vec> {
// Update polynomial has root at x_r
@@ -120,7 +179,7 @@ impl ShareRefreshUpdate {
pub fn make_share_updates_for_refresh(
domain_points: &[E::ScalarField],
h: &E::G2Affine,
- threshold: usize,
+ threshold: u32,
rng: &mut impl RngCore,
) -> Vec> {
// Update polynomial has root at 0
@@ -147,7 +206,7 @@ fn prepare_share_updates_with_root(
domain_points: &[E::ScalarField],
h: &E::G2Affine,
root: &E::ScalarField,
- threshold: usize,
+ threshold: u32,
rng: &mut impl RngCore,
) -> Vec> {
// Generate a new random polynomial with defined root
@@ -168,12 +227,13 @@ fn prepare_share_updates_with_root(
/// Generate a random polynomial with a given root
fn make_random_polynomial_with_root(
- degree: usize,
+ degree: u32,
root: &E::ScalarField,
rng: &mut impl RngCore,
) -> DensePolynomial {
// [c_0, c_1, ..., c_{degree}] (Random polynomial)
- let mut poly = DensePolynomial::::rand(degree, rng);
+ let mut poly =
+ DensePolynomial::::rand(degree as usize, rng);
// [0, c_1, ... , c_{degree}] (We zeroize the free term)
poly[0] = E::ScalarField::zero();
@@ -184,7 +244,7 @@ fn make_random_polynomial_with_root(
// Evaluating the polynomial at the root should result in 0
debug_assert!(poly.evaluate(root) == E::ScalarField::zero());
- debug_assert!(poly.coeffs.len() == degree + 1);
+ debug_assert!(poly.coeffs.len() == (degree + 1) as usize);
poly
}
@@ -199,16 +259,19 @@ mod tests_refresh {
test_common::setup_simple, PrivateDecryptionContextSimple,
};
use rand_core::RngCore;
- use test_case::test_matrix;
+ use test_case::{test_case, test_matrix};
use crate::{
test_common::*, PrivateKeyShare, ShareRecoveryUpdate,
ShareRefreshUpdate, UpdatedPrivateKeyShare,
};
+ /// Using tdec test utilities here instead of PVSS to test the internals of the shared key recovery
+ // TODO: Can I fix that using combine_shared_secret?
+
fn make_updated_private_key_shares(
rng: &mut R,
- threshold: usize,
+ threshold: u32,
x_r: &Fr,
remaining_participants: &[PrivateDecryptionContextSimple