From cf281c4e755da596074a936132daeeb74e8fd6c6 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Mon, 12 Feb 2024 15:17:32 +0100
Subject: [PATCH] refactor: introduce refreshing api in ferveo
---
ferveo-common/src/keypair.rs | 9 +-
ferveo-python/ferveo/__init__.py | 1 +
ferveo-python/ferveo/__init__.pyi | 3 +
ferveo-tdec/benches/tpke.rs | 2 +-
ferveo-tdec/src/combine.rs | 2 -
ferveo-tdec/src/decryption.rs | 3 -
ferveo-tdec/src/key_share.rs | 11 +-
ferveo-tdec/src/lib.rs | 8 +-
ferveo/src/api.rs | 706 +++++++++++++++++++++++++++---
ferveo/src/bindings_python.rs | 9 +-
ferveo/src/bindings_wasm.rs | 1 -
ferveo/src/dkg.rs | 47 +-
ferveo/src/lib.rs | 81 ++--
ferveo/src/pvss.rs | 42 +-
ferveo/src/refresh.rs | 107 ++---
ferveo/src/validator.rs | 9 +-
16 files changed, 819 insertions(+), 222 deletions(-)
diff --git a/ferveo-common/src/keypair.rs b/ferveo-common/src/keypair.rs
index 9b251125..582a0cb8 100644
--- a/ferveo-common/src/keypair.rs
+++ b/ferveo-common/src/keypair.rs
@@ -6,7 +6,10 @@ use ark_std::{
rand::{prelude::StdRng, RngCore, SeedableRng},
UniformRand,
};
-use generic_array::{typenum::U96, GenericArray};
+use generic_array::{
+ typenum::{Unsigned, U96},
+ GenericArray,
+};
use serde::*;
use serde_with::serde_as;
@@ -55,7 +58,7 @@ impl PublicKey {
}
pub fn serialized_size() -> usize {
- 96
+ U96::to_usize()
}
}
@@ -106,7 +109,6 @@ impl Ord for Keypair {
impl Keypair {
/// Returns the public session key for the publicly verifiable DKG participant
-
pub fn public_key(&self) -> PublicKey {
PublicKey:: {
encryption_key: E::G2Affine::generator()
@@ -116,7 +118,6 @@ impl Keypair {
}
/// Creates a new ephemeral session key for participating in the DKG
-
pub fn new(rng: &mut R) -> Self {
Self {
decryption_key: E::ScalarField::rand(rng),
diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py
index fd906e54..58a3a140 100644
--- a/ferveo-python/ferveo/__init__.py
+++ b/ferveo-python/ferveo/__init__.py
@@ -39,4 +39,5 @@
DuplicatedShareIndex,
NoTranscriptsToAggregate,
InvalidAggregateVerificationParameters,
+ UnknownValidator,
)
diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi
index ba7e7403..894f71ed 100644
--- a/ferveo-python/ferveo/__init__.pyi
+++ b/ferveo-python/ferveo/__init__.pyi
@@ -219,3 +219,6 @@ class NoTranscriptsToAggregate(Exception):
class InvalidAggregateVerificationParameters(Exception):
pass
+
+class UnknownValidator(Exception):
+ pass
diff --git a/ferveo-tdec/benches/tpke.rs b/ferveo-tdec/benches/tpke.rs
index 0bbba434..db8a7424 100644
--- a/ferveo-tdec/benches/tpke.rs
+++ b/ferveo-tdec/benches/tpke.rs
@@ -550,7 +550,7 @@ pub fn bench_decryption_share_validity_checks(c: &mut Criterion) {
// for &shares_num in NUM_SHARES_CASES.iter() {
// let setup = SetupSimple::new(shares_num, msg_size, rng);
// let threshold = setup.shared.threshold;
-// let polynomial = make_random_polynomial_with_root::(
+// let polynomial = create_random_polynomial_with_root::(
// threshold - 1,
// &Fr::zero(),
// rng,
diff --git a/ferveo-tdec/src/combine.rs b/ferveo-tdec/src/combine.rs
index f9d8ddbb..a46477fb 100644
--- a/ferveo-tdec/src/combine.rs
+++ b/ferveo-tdec/src/combine.rs
@@ -56,8 +56,6 @@ pub fn prepare_combine_fast(
.collect::>()
}
-// TODO: Combine `tpke::prepare_combine_simple` and `tpke::share_combine_simple` into
-// one function and expose it in the tpke::api?
pub fn prepare_combine_simple(
domain: &[E::ScalarField],
) -> Vec {
diff --git a/ferveo-tdec/src/decryption.rs b/ferveo-tdec/src/decryption.rs
index eb9068bd..dc93fee4 100644
--- a/ferveo-tdec/src/decryption.rs
+++ b/ferveo-tdec/src/decryption.rs
@@ -37,9 +37,6 @@ impl ValidatorShareChecksum {
// C_i = dk_i^{-1} * U
let checksum = ciphertext_header
.commitment
- // TODO: Should we panic here? I think we should since that would mean that the decryption key is invalid.
- // And so, the validator should not be able to create a decryption share.
- // And so, the validator should remake their keypair.
.mul(
validator_decryption_key
.inverse()
diff --git a/ferveo-tdec/src/key_share.rs b/ferveo-tdec/src/key_share.rs
index 64a9e3e4..4c164f6e 100644
--- a/ferveo-tdec/src/key_share.rs
+++ b/ferveo-tdec/src/key_share.rs
@@ -3,8 +3,11 @@ use std::ops::Mul;
use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
use ark_ff::One;
use ark_std::UniformRand;
+use ferveo_common::serialization;
use rand_core::RngCore;
-use zeroize::ZeroizeOnDrop;
+use serde::{Deserialize, Serialize};
+use serde_with::serde_as;
+use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, Clone)]
// TODO: Should we rename it to PublicKey or SharedPublicKey?
@@ -49,9 +52,13 @@ impl BlindedKeyShare {
}
}
-#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
+#[serde_as]
+#[derive(
+ Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop,
+)]
pub struct PrivateKeyShare {
// TODO: Replace with a tuple?
+ #[serde_as(as = "serialization::SerdeAs")]
pub private_key_share: E::G2Affine,
}
diff --git a/ferveo-tdec/src/lib.rs b/ferveo-tdec/src/lib.rs
index b5ffed22..322d1bf9 100644
--- a/ferveo-tdec/src/lib.rs
+++ b/ferveo-tdec/src/lib.rs
@@ -290,7 +290,7 @@ pub mod test_common {
setup_simple::(shares_num, shares_num, rng)
}
- pub fn make_shared_secret(
+ pub fn create_shared_secret(
pub_contexts: &[PublicDecryptionContextSimple],
decryption_shares: &[DecryptionShareSimple],
) -> SharedSecret {
@@ -308,7 +308,7 @@ mod tests {
use ark_std::{test_rng, UniformRand};
use ferveo_common::{FromBytes, ToBytes};
- use crate::test_common::{make_shared_secret, setup_simple, *};
+ use crate::test_common::{create_shared_secret, setup_simple, *};
type E = ark_bls12_381::Bls12_381;
type TargetField = ::TargetField;
@@ -481,7 +481,7 @@ mod tests {
let pub_contexts =
contexts[0].public_decryption_contexts[..threshold].to_vec();
let shared_secret =
- make_shared_secret(&pub_contexts, &decryption_shares);
+ create_shared_secret(&pub_contexts, &decryption_shares);
test_ciphertext_validation_fails(
&msg,
@@ -495,7 +495,7 @@ mod tests {
let decryption_shares = decryption_shares[..threshold - 1].to_vec();
let pub_contexts = pub_contexts[..threshold - 1].to_vec();
let shared_secret =
- make_shared_secret(&pub_contexts, &decryption_shares);
+ create_shared_secret(&pub_contexts, &decryption_shares);
let result =
decrypt_with_shared_secret(&ciphertext, aad, &shared_secret, g_inv);
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index 36cdc7c5..fe25a931 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -1,5 +1,6 @@
use std::{fmt, io};
+use ark_ec::CurveGroup;
use ark_poly::{EvaluationDomain, GeneralEvaluationDomain};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::UniformRand;
@@ -15,7 +16,7 @@ use generic_array::{
typenum::{Unsigned, U48},
GenericArray,
};
-use rand::RngCore;
+use rand::{thread_rng, RngCore};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
@@ -25,8 +26,8 @@ use crate::bindings_python;
use crate::bindings_wasm;
pub use crate::EthereumAddress;
use crate::{
- do_verify_aggregation, Error, Message, PVSSMap, PubliclyVerifiableParams,
- PubliclyVerifiableSS, Result,
+ do_verify_aggregation, DomainPoint, Error, Message, PVSSMap,
+ PubliclyVerifiableParams, PubliclyVerifiableSS, Result,
};
pub type PublicKey = ferveo_common::PublicKey;
@@ -95,7 +96,6 @@ impl Ciphertext {
}
}
-#[serde_as] // TODO: Redundant serde_as?
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CiphertextHeader(ferveo_tdec::api::CiphertextHeader);
@@ -229,7 +229,6 @@ impl Dkg {
pub fn generate_transcript(
&mut self,
rng: &mut R,
- // TODO: Replace with Message::Deal?
) -> Result {
match self.0.share(rng) {
Ok(Message::Deal(transcript)) => Ok(transcript),
@@ -241,7 +240,6 @@ impl Dkg {
pub fn aggregate_transcripts(
&mut self,
messages: &[ValidatorMessage],
- // TODO: Replace with Message::Aggregate?
) -> Result {
// We must use `deal` here instead of to produce AggregatedTranscript instead of simply
// creating an AggregatedTranscript from the messages, because `deal` also updates the
@@ -266,6 +264,14 @@ impl Dkg {
g1_inv: self.0.pvss_params.g_inv(),
}
}
+
+ pub fn me(&self) -> &Validator {
+ &self.0.me
+ }
+
+ pub fn domain_points(&self) -> Vec> {
+ self.0.domain_points()
+ }
}
fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap {
@@ -330,6 +336,7 @@ impl AggregatedTranscript {
Ok(is_valid)
}
+ // TODO: Consider deprecating in favor of PrivateKeyShare::create_decryption_share_simple
pub fn create_decryption_share_precomputed(
&self,
dkg: &Dkg,
@@ -346,7 +353,7 @@ impl AggregatedTranscript {
dkg.0.dkg_params.security_threshold(),
));
}
- self.0.make_decryption_share_simple_precomputed(
+ self.0.create_decryption_share_simple_precomputed(
&ciphertext_header.0,
aad,
validator_keypair,
@@ -356,6 +363,7 @@ impl AggregatedTranscript {
)
}
+ // TODO: Consider deprecating in favor of PrivateKeyShare::create_decryption_share_simple
pub fn create_decryption_share_simple(
&self,
dkg: &Dkg,
@@ -363,7 +371,7 @@ impl AggregatedTranscript {
aad: &[u8],
validator_keypair: &Keypair,
) -> Result {
- let share = self.0.make_decryption_share_simple(
+ let share = self.0.create_decryption_share_simple(
&ciphertext_header.0,
aad,
validator_keypair,
@@ -376,6 +384,19 @@ impl AggregatedTranscript {
domain_point,
})
}
+
+ pub fn get_private_key_share(
+ &self,
+ validator_keypair: &Keypair,
+ share_index: u32,
+ ) -> Result {
+ Ok(PrivateKeyShare(
+ self.0
+ .decrypt_private_key_share(validator_keypair, share_index)?
+ .0
+ .clone(),
+ ))
+ }
}
#[serde_as]
@@ -386,6 +407,7 @@ pub struct DecryptionShareSimple {
domain_point: Fr,
}
+// TODO: Deprecate?
#[serde_as]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DkgPublicParameters {
@@ -425,14 +447,209 @@ pub fn combine_shares_simple(shares: &[DecryptionShareSimple]) -> SharedSecret {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SharedSecret(pub ferveo_tdec::api::SharedSecret);
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+// TODO: serde is failing to serialize E = ark_bls12_381::Bls12_381
+// pub struct ShareRecoveryUpdate(pub crate::refresh::ShareRecoveryUpdate);
+pub struct ShareRecoveryUpdate(pub ferveo_tdec::PrivateKeyShare);
+
+impl ShareRecoveryUpdate {
+ // TODO: There are two recovery scenarios: at random and at a specific point. Do we ever want
+ // to recover at a specific point? What scenario would that be? Validator rotation?
+ pub fn create_share_updates(
+ // TODO: Decouple from Dkg? We don't need any specific Dkg instance here, just some params etc
+ dkg: &Dkg,
+ x_r: &DomainPoint,
+ ) -> Result> {
+ let rng = &mut thread_rng();
+ let updates =
+ crate::refresh::ShareRecoveryUpdate::create_share_updates(
+ &dkg.0.domain_points(),
+ &dkg.0.pvss_params.h.into_affine(),
+ x_r,
+ dkg.0.dkg_params.security_threshold(),
+ rng,
+ )
+ .iter()
+ .map(|update| ShareRecoveryUpdate(update.0.clone()))
+ .collect();
+ Ok(updates)
+ }
+
+ pub fn to_bytes(&self) -> Result> {
+ bincode::serialize(self).map_err(|e| e.into())
+ }
+
+ pub fn from_bytes(bytes: &[u8]) -> Result {
+ bincode::deserialize(bytes).map_err(|e| e.into())
+ }
+}
+
+#[serde_as]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct ShareRefreshUpdate(pub ferveo_tdec::PrivateKeyShare);
+
+impl ShareRefreshUpdate {
+ pub fn create_share_updates(dkg: &Dkg) -> Result> {
+ let rng = &mut thread_rng();
+ let updates = crate::refresh::ShareRefreshUpdate::create_share_updates(
+ &dkg.0.domain_points(),
+ &dkg.0.pvss_params.h.into_affine(),
+ dkg.0.dkg_params.security_threshold(),
+ rng,
+ )
+ .iter()
+ .map(|update| ShareRefreshUpdate(update.0.clone()))
+ .collect();
+ Ok(updates)
+ }
+
+ pub fn to_bytes(&self) -> Result> {
+ bincode::serialize(self).map_err(|e| e.into())
+ }
+
+ pub fn from_bytes(bytes: &[u8]) -> Result {
+ bincode::deserialize(bytes).map_err(|e| e.into())
+ }
+}
+
+#[serde_as]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct UpdatedPrivateKeyShare(pub ferveo_tdec::PrivateKeyShare);
+
+impl UpdatedPrivateKeyShare {
+ pub fn into_private_key_share(self) -> PrivateKeyShare {
+ PrivateKeyShare(self.0)
+ }
+ pub fn to_bytes(&self) -> Result> {
+ bincode::serialize(self).map_err(|e| e.into())
+ }
+
+ pub fn from_bytes(bytes: &[u8]) -> Result {
+ bincode::deserialize(bytes).map_err(|e| e.into())
+ }
+}
+
+#[serde_as]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct PrivateKeyShare(pub ferveo_tdec::PrivateKeyShare);
+
+impl PrivateKeyShare {
+ pub fn create_updated_private_key_share_for_recovery(
+ &self,
+ share_updates: &[ShareRecoveryUpdate],
+ ) -> Result {
+ let share_updates: Vec<_> = share_updates
+ .iter()
+ .map(|update| crate::refresh::ShareRecoveryUpdate(update.0.clone()))
+ .collect();
+ // TODO: Remove this wrapping after figuring out serde_as
+ let updated_key_share = crate::PrivateKeyShare(self.0.clone())
+ .create_updated_key_share(&share_updates);
+ Ok(UpdatedPrivateKeyShare(updated_key_share.0.clone()))
+ }
+
+ pub fn create_updated_private_key_share_for_refresh(
+ &self,
+ share_updates: &[ShareRefreshUpdate],
+ ) -> Result {
+ let share_updates: Vec<_> = share_updates
+ .iter()
+ .map(|update| crate::refresh::ShareRefreshUpdate(update.0.clone()))
+ .collect();
+ let updated_key_share = crate::PrivateKeyShare(self.0.clone())
+ .create_updated_key_share(&share_updates);
+ Ok(UpdatedPrivateKeyShare(updated_key_share.0.clone()))
+ }
+
+ /// Recover a private key share from updated private key shares
+ pub fn recover_share_from_updated_private_shares(
+ x_r: &DomainPoint,
+ domain_points: &[DomainPoint],
+ updated_shares: &[UpdatedPrivateKeyShare],
+ ) -> Result {
+ let updated_shares: Vec<_> = updated_shares
+ .iter()
+ // TODO: Remove this wrapping after figuring out serde_as
+ .map(|s| crate::refresh::UpdatedPrivateKeyShare(s.0.clone()))
+ .collect();
+ let share =
+ crate::PrivateKeyShare::recover_share_from_updated_private_shares(
+ x_r,
+ domain_points,
+ &updated_shares[..],
+ );
+ Ok(PrivateKeyShare(share.0.clone()))
+ }
+
+ /// Make a decryption share (simple variant) for a given ciphertext
+ pub fn create_decryption_share_simple(
+ &self,
+ dkg: &Dkg,
+ ciphertext_header: &CiphertextHeader,
+ validator_keypair: &Keypair,
+ aad: &[u8],
+ ) -> Result {
+ let share = crate::PrivateKeyShare(self.0.clone())
+ .create_decryption_share_simple(
+ &ciphertext_header.0,
+ aad,
+ validator_keypair,
+ &dkg.public_params().g1_inv,
+ )?;
+ let domain_point = dkg.0.get_domain_point(dkg.0.me.share_index)?;
+ Ok(DecryptionShareSimple {
+ share,
+ domain_point,
+ })
+ }
+
+ /// Make a decryption share (precomputed variant) for a given ciphertext
+ pub fn create_decryption_share_simple_precomputed(
+ &self,
+ ciphertext_header: &CiphertextHeader,
+ aad: &[u8],
+ validator_keypair: &Keypair,
+ share_index: u32,
+ domain_points: &[DomainPoint],
+ ) -> Result {
+ let dkg_public_params = DkgPublicParameters::default();
+ let share = crate::PrivateKeyShare(self.0.clone())
+ .create_decryption_share_simple_precomputed(
+ &ciphertext_header.0,
+ aad,
+ validator_keypair,
+ share_index,
+ domain_points,
+ &dkg_public_params.g1_inv,
+ )?;
+ Ok(share)
+ }
+
+ pub fn to_bytes(&self) -> Result> {
+ bincode::serialize(self).map_err(|e| e.into())
+ }
+
+ pub fn from_bytes(bytes: &[u8]) -> Result {
+ bincode::deserialize(bytes).map_err(|e| e.into())
+ }
+}
+
#[cfg(test)]
mod test_ferveo_api {
+ use std::collections::HashMap;
+
use ferveo_tdec::SecretBox;
- use itertools::izip;
- use rand::{prelude::StdRng, SeedableRng};
+ use itertools::{izip, Itertools};
+ use rand::{
+ prelude::{SliceRandom, StdRng},
+ SeedableRng,
+ };
use test_case::test_case;
- use crate::{api::*, test_common::*};
+ use crate::{
+ api::*,
+ test_common::{gen_address, gen_keypairs, AAD, MSG, TAU},
+ };
type TestInputs = (Vec, Vec, Vec);
@@ -455,7 +672,7 @@ mod test_ferveo_api {
.collect::>();
// Each validator holds their own DKG instance and generates a transcript every
- // every validator, including themselves
+ // validator, including themselves
let messages: Vec<_> = validators
.iter()
.map(|sender| {
@@ -519,33 +736,36 @@ mod test_ferveo_api {
.unwrap();
// Having aggregated the transcripts, the validators can now create decryption shares
- let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
- .map(|(validator, validator_keypair)| {
- // Each validator holds their own instance of DKG and creates their own aggregate
- let mut dkg = Dkg::new(
- TAU,
- shares_num,
- security_threshold,
- &validators,
- validator,
- )
- .unwrap();
- let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(pvss_aggregated
- .verify(validators_num, &messages)
- .unwrap());
-
- // And then each validator creates their own decryption share
- aggregate
- .create_decryption_share_precomputed(
- &dkg,
- &ciphertext.header().unwrap(),
- AAD,
- validator_keypair,
+ let mut decryption_shares: Vec<_> =
+ izip!(&validators, &validator_keypairs)
+ .map(|(validator, validator_keypair)| {
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let mut dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ &validators,
+ validator,
)
- .unwrap()
- })
- .collect();
+ .unwrap();
+ let aggregate =
+ dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated
+ .verify(validators_num, &messages)
+ .unwrap());
+
+ // And then each validator creates their own decryption share
+ aggregate
+ .create_decryption_share_precomputed(
+ &dkg,
+ &ciphertext.header().unwrap(),
+ AAD,
+ validator_keypair,
+ )
+ .unwrap()
+ })
+ .collect();
+ decryption_shares.shuffle(rng);
// Now, the decryption share can be used to decrypt the ciphertext
// This part is part of the client API
@@ -561,9 +781,9 @@ mod test_ferveo_api {
// Since we're using a precomputed variant, we need all the shares to be able to decrypt
// So if we remove one share, we should not be able to decrypt
+
let decryption_shares =
decryption_shares[..shares_num as usize - 1].to_vec();
-
let shared_secret = share_combine_precomputed(&decryption_shares);
let result = decrypt_with_shared_secret(
&ciphertext,
@@ -578,7 +798,6 @@ mod test_ferveo_api {
#[test_case(4, 6; "number of validators greater than the number of shares")]
fn test_server_api_tdec_simple(shares_num: u32, validators_num: u32) {
let rng = &mut StdRng::seed_from_u64(0);
-
let security_threshold = shares_num / 2 + 1;
let (messages, validators, validator_keypairs) = make_test_inputs(
@@ -611,29 +830,34 @@ mod test_ferveo_api {
encrypt(SecretBox::new(MSG.to_vec()), AAD, &public_key).unwrap();
// Having aggregated the transcripts, the validators can now create decryption shares
- let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs)
- .map(|(validator, validator_keypair)| {
- // Each validator holds their own instance of DKG and creates their own aggregate
- let mut dkg = Dkg::new(
- TAU,
- shares_num,
- security_threshold,
- &validators,
- validator,
- )
- .unwrap();
- let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
- assert!(aggregate.verify(validators_num, &messages).unwrap());
- aggregate
- .create_decryption_share_simple(
- &dkg,
- &ciphertext.header().unwrap(),
- AAD,
- validator_keypair,
+ let mut decryption_shares: Vec<_> =
+ izip!(&validators, &validator_keypairs)
+ .map(|(validator, validator_keypair)| {
+ // Each validator holds their own instance of DKG and creates their own aggregate
+ let mut dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ &validators,
+ validator,
)
- .unwrap()
- })
- .collect();
+ .unwrap();
+ let aggregate =
+ dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(aggregate
+ .verify(validators_num, &messages)
+ .unwrap());
+ aggregate
+ .create_decryption_share_simple(
+ &dkg,
+ &ciphertext.header().unwrap(),
+ AAD,
+ validator_keypair,
+ )
+ .unwrap()
+ })
+ .collect();
+ decryption_shares.shuffle(rng);
// Now, the decryption share can be used to decrypt the ciphertext
// This part is part of the client API
@@ -796,4 +1020,362 @@ mod test_ferveo_api {
let result = bad_aggregate.verify(validators_num, messages);
assert!(result.is_err());
}
+
+ fn make_share_update_test_inputs(
+ shares_num: u32,
+ validators_num: u32,
+ rng: &mut StdRng,
+ security_threshold: u32,
+ ) -> (
+ Vec,
+ Vec,
+ Vec,
+ Vec,
+ CiphertextHeader,
+ SharedSecret,
+ ) {
+ let (messages, validators, validator_keypairs) = make_test_inputs(
+ rng,
+ TAU,
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
+ let mut dkgs = validators
+ .iter()
+ .map(|validator| {
+ Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ &validators,
+ validator,
+ )
+ .unwrap()
+ })
+ .collect::>();
+ let pvss_aggregated = dkgs[0].aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated.verify(validators_num, &messages).unwrap());
+
+ // Create an initial shared secret for testing purposes
+ let public_key = &dkgs[0].public_key();
+ let ciphertext =
+ encrypt(SecretBox::new(MSG.to_vec()), AAD, public_key).unwrap();
+ let ciphertext_header = ciphertext.header().unwrap();
+ let (_, _, old_shared_secret) =
+ crate::test_dkg_full::create_shared_secret_simple_tdec(
+ &dkgs[0].0,
+ AAD,
+ &ciphertext_header.0,
+ validator_keypairs.as_slice(),
+ );
+
+ (
+ messages,
+ validators,
+ validator_keypairs,
+ dkgs,
+ ciphertext_header,
+ SharedSecret(old_shared_secret),
+ )
+ }
+
+ #[test_case(4, 4, true; "number of shares (validators) is a power of 2")]
+ #[test_case(7, 7, true; "number of shares (validators) is not a power of 2")]
+ #[test_case(4, 6, true; "number of validators greater than the number of shares")]
+ #[test_case(4, 6, false; "recovery at a specific point")]
+ fn test_dkg_simple_tdec_share_recovery(
+ shares_num: u32,
+ validators_num: u32,
+ recover_at_random_point: bool,
+ ) {
+ let rng = &mut StdRng::seed_from_u64(0);
+ let security_threshold = shares_num / 2 + 1;
+
+ let (
+ mut messages,
+ mut validators,
+ mut validator_keypairs,
+ mut dkgs,
+ ciphertext_header,
+ old_shared_secret,
+ ) = make_share_update_test_inputs(
+ shares_num,
+ validators_num,
+ rng,
+ security_threshold,
+ );
+
+ // We assume that all participants have the same aggregate, and that participants created
+ // their own aggregates before the off-boarding of the validator
+ // If we didn't create this aggregate here, we risk having a "dangling validator message"
+ // later when we off-board the validator
+ let aggregated_transcript =
+ dkgs[0].clone().aggregate_transcripts(&messages).unwrap();
+ assert!(aggregated_transcript
+ .verify(validators_num, &messages)
+ .unwrap());
+
+ // We need to save this domain point to be user in the recovery testing scenario
+ let mut domain_points = dkgs[0].domain_points();
+ let removed_domain_point = domain_points.pop().unwrap();
+
+ // Remove one participant from the contexts and all nested structure
+ // to simulate off-boarding a validator
+ messages.pop().unwrap();
+ dkgs.pop();
+ validator_keypairs.pop().unwrap();
+
+ let removed_validator = validators.pop().unwrap();
+ for dkg in dkgs.iter_mut() {
+ dkg.0
+ .offboard_validator(&removed_validator.address)
+ .expect("Unable to off-board a validator from the DKG context");
+ }
+
+ // Now, we're going to recover a new share at a random point or at a specific point
+ // and check that the shared secret is still the same.
+ let x_r = if recover_at_random_point {
+ // Onboarding a validator with a completely new private key share
+ DomainPoint::::rand(rng)
+ } else {
+ // Onboarding a validator with a private key share recovered from the removed validator
+ removed_domain_point
+ };
+
+ // Each participant prepares an update for each other participant
+ let share_updates = dkgs
+ .iter()
+ .map(|validator_dkg| {
+ let share_update = ShareRecoveryUpdate::create_share_updates(
+ validator_dkg,
+ &x_r,
+ )
+ .unwrap();
+ (validator_dkg.me().address.clone(), share_update)
+ })
+ .collect::>();
+
+ // Participants share updates and update their shares
+
+ // Now, every participant separately:
+ let updated_shares: Vec<_> = dkgs
+ .iter()
+ .map(|validator_dkg| {
+ // Current participant receives updates from other participants
+ let updates_for_participant: Vec<_> = share_updates
+ .values()
+ .map(|updates| {
+ updates
+ .get(validator_dkg.me().share_index as usize)
+ .unwrap()
+ })
+ .cloned()
+ .collect();
+
+ // Each validator uses their decryption key to update their share
+ let validator_keypair = validator_keypairs
+ .get(validator_dkg.me().share_index as usize)
+ .unwrap();
+
+ // And creates updated private key shares
+ aggregated_transcript
+ .get_private_key_share(
+ validator_keypair,
+ validator_dkg.me().share_index,
+ )
+ .unwrap()
+ .create_updated_private_key_share_for_recovery(
+ &updates_for_participant,
+ )
+ .unwrap()
+ })
+ .collect();
+
+ // Now, we have to combine new share fragments into a new share
+ let recovered_key_share =
+ PrivateKeyShare::recover_share_from_updated_private_shares(
+ &x_r,
+ &domain_points,
+ &updated_shares,
+ )
+ .unwrap();
+
+ // Get decryption shares from remaining participants
+ let mut decryption_shares: Vec =
+ validator_keypairs
+ .iter()
+ .zip_eq(dkgs.iter())
+ .map(|(validator_keypair, validator_dkg)| {
+ aggregated_transcript
+ .create_decryption_share_simple(
+ validator_dkg,
+ &ciphertext_header,
+ AAD,
+ validator_keypair,
+ )
+ .unwrap()
+ })
+ .collect();
+ decryption_shares.shuffle(rng);
+
+ // In order to test the recovery, we need to create a new decryption share from the recovered
+ // private key share. To do that, we need a new validator
+
+ // Let's create and onboard a new validator
+ // TODO: Add test scenarios for onboarding and offboarding validators
+ let new_validator_keypair = Keypair::random();
+ // Normally, we would get these from the Coordinator:
+ let new_validator_share_index = removed_validator.share_index;
+ let new_validator = Validator {
+ address: gen_address(new_validator_share_index as usize),
+ public_key: new_validator_keypair.public_key(),
+ share_index: new_validator_share_index,
+ };
+ validators.push(new_validator.clone());
+ let new_validator_dkg = Dkg::new(
+ TAU,
+ shares_num,
+ security_threshold,
+ &validators,
+ &new_validator,
+ )
+ .unwrap();
+
+ let new_decryption_share = recovered_key_share
+ .create_decryption_share_simple(
+ &new_validator_dkg,
+ &ciphertext_header,
+ &new_validator_keypair,
+ AAD,
+ )
+ .unwrap();
+ decryption_shares.push(new_decryption_share);
+ domain_points.push(x_r);
+ assert_eq!(domain_points.len(), validators_num as usize);
+ assert_eq!(decryption_shares.len(), validators_num as usize);
+
+ 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 new_shared_secret = combine_shares_simple(decryption_shares);
+ assert_eq!(
+ old_shared_secret, new_shared_secret,
+ "Shared secret reconstruction failed"
+ );
+ }
+
+ #[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_refresh(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
+ let rng = &mut StdRng::seed_from_u64(0);
+ let security_threshold = shares_num / 2 + 1;
+
+ let (
+ messages,
+ _validators,
+ validator_keypairs,
+ dkgs,
+ ciphertext_header,
+ old_shared_secret,
+ ) = make_share_update_test_inputs(
+ shares_num,
+ validators_num,
+ rng,
+ security_threshold,
+ );
+
+ // Each participant prepares an update for each other participant
+ let share_updates = dkgs
+ .iter()
+ .map(|validator_dkg| {
+ let share_update =
+ ShareRefreshUpdate::create_share_updates(validator_dkg)
+ .unwrap();
+ (validator_dkg.me().address.clone(), share_update)
+ })
+ .collect::>();
+
+ // Participants share updates and update their shares
+
+ // Now, every participant separately:
+ let updated_shares: Vec<_> = dkgs
+ .iter()
+ .map(|validator_dkg| {
+ // Current participant receives updates from other participants
+ let updates_for_participant: Vec<_> = share_updates
+ .values()
+ .map(|updates| {
+ updates
+ .get(validator_dkg.me().share_index as usize)
+ .unwrap()
+ })
+ .cloned()
+ .collect();
+
+ // Each validator uses their decryption key to update their share
+ let validator_keypair = validator_keypairs
+ .get(validator_dkg.me().share_index as usize)
+ .unwrap();
+
+ // And creates updated private key shares
+ // We need an aggregate for that
+ let aggregate = validator_dkg
+ .clone()
+ .aggregate_transcripts(&messages)
+ .unwrap();
+ assert!(aggregate.verify(validators_num, &messages).unwrap());
+
+ aggregate
+ .get_private_key_share(
+ validator_keypair,
+ validator_dkg.me().share_index,
+ )
+ .unwrap()
+ .create_updated_private_key_share_for_refresh(
+ &updates_for_participant,
+ )
+ .unwrap()
+ })
+ .collect();
+
+ // Participants create decryption shares
+ let mut decryption_shares: Vec =
+ validator_keypairs
+ .iter()
+ .zip_eq(dkgs.iter())
+ .map(|(validator_keypair, validator_dkg)| {
+ let pks = updated_shares
+ .get(validator_dkg.me().share_index as usize)
+ .unwrap()
+ .clone()
+ .into_private_key_share();
+ pks.create_decryption_share_simple(
+ validator_dkg,
+ &ciphertext_header,
+ validator_keypair,
+ AAD,
+ )
+ .unwrap()
+ })
+ .collect();
+ decryption_shares.shuffle(rng);
+
+ let decryption_shares =
+ &decryption_shares[..security_threshold as usize];
+ assert_eq!(decryption_shares.len(), security_threshold as usize);
+
+ let new_shared_secret = combine_shares_simple(decryption_shares);
+ assert_eq!(
+ old_shared_secret, new_shared_secret,
+ "Shared secret reconstruction failed"
+ );
+ }
}
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index fe534af8..f6fce72c 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -120,7 +120,12 @@ impl From for PyErr {
InvalidAggregateVerificationParameters::new_err(format!(
"validators_num: {validators_num}, messages_num: {messages_num}"
))
- }
+ },
+ Error::UnknownValidator(validator) => {
+ UnknownValidator::new_err(validator.to_string())
+ },
+ // Remember to create Python exceptions using `create_exception!` macro, and to register them in the
+ // `make_ferveo_py_module` function. You will have to update the `ferveo/__init__.{py, pyi}` files too.
},
_ => default(),
}
@@ -168,6 +173,7 @@ create_exception!(
InvalidAggregateVerificationParameters,
PyValueError
);
+create_exception!(exceptions, UnknownValidator, PyValueError);
fn from_py_bytes(bytes: &[u8]) -> PyResult {
T::from_bytes(bytes)
@@ -782,6 +788,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
"InvalidAggregateVerificationParameters",
py.get_type::(),
)?;
+ m.add("UnknownValidator", py.get_type::())?;
Ok(())
}
diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs
index 07e22e3f..3c885269 100644
--- a/ferveo/src/bindings_wasm.rs
+++ b/ferveo/src/bindings_wasm.rs
@@ -460,7 +460,6 @@ impl Validator {
}
}
-// TODO: Consider removing and replacing with tuple
#[derive(TryFromJsValue)]
#[wasm_bindgen]
#[derive(Clone, Debug, derive_more::AsRef, derive_more::From)]
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index bc4acdfd..1ee6ec6e 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -11,10 +11,12 @@ use serde_with::serde_as;
use crate::{
aggregate, assert_no_share_duplicates, AggregatedPvss, Error,
- EthereumAddress, PrivateKeyShare, PubliclyVerifiableParams,
- PubliclyVerifiableSS, Result, Validator,
+ EthereumAddress, PubliclyVerifiableParams, PubliclyVerifiableSS, Result,
+ Validator,
};
+pub type DomainPoint = ::ScalarField;
+
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct DkgParams {
tau: u32,
@@ -89,7 +91,7 @@ impl DkgState {
}
}
-/// The DKG context that holds all of the local state for participating in the DKG
+/// The DKG context that holds all the local state for participating in the DKG
// TODO: Consider removing Clone to avoid accidentally NOT-mutating state.
// Currently, we're assuming that the DKG is only mutated by the owner of the instance.
// Consider removing Clone after finalizing ferveo::api
@@ -116,13 +118,12 @@ impl PubliclyVerifiableDkg {
dkg_params: &DkgParams,
me: &Validator,
) -> Result {
+ assert_no_share_duplicates(validators)?;
+
let domain = ark_poly::GeneralEvaluationDomain::::new(
validators.len(),
)
.expect("unable to construct domain");
-
- assert_no_share_duplicates(validators)?;
-
let validators: ValidatorsMap = validators
.iter()
.map(|validator| (validator.address.clone(), validator.clone()))
@@ -165,7 +166,7 @@ impl PubliclyVerifiableDkg {
match self.state {
DkgState::Sharing { .. } | DkgState::Dealt => {
let vss = PubliclyVerifiableSS::::new(
- &E::ScalarField::rand(rng),
+ &DomainPoint::::rand(rng),
self,
rng,
)?;
@@ -203,29 +204,28 @@ impl PubliclyVerifiableDkg {
}
/// Return a domain point for the share_index
- pub fn get_domain_point(&self, share_index: u32) -> Result {
- let domain_points = self.domain_points();
- domain_points
+ pub fn get_domain_point(&self, share_index: u32) -> Result> {
+ self.domain_points()
.get(share_index as usize)
.ok_or_else(|| Error::InvalidShareIndex(share_index))
.copied()
}
/// Return an appropriate amount of domain points for the DKG
- pub fn domain_points(&self) -> Vec {
+ pub fn domain_points(&self) -> Vec> {
self.domain.elements().take(self.validators.len()).collect()
}
- /// 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 offboard_validator(
+ &mut self,
+ address: &EthereumAddress,
+ ) -> Result> {
+ if let Some(validator) = self.validators.remove(address) {
+ self.vss.remove(address);
+ Ok(validator)
+ } else {
+ Err(Error::UnknownValidator(address.clone()))
+ }
}
pub fn verify_message(
@@ -274,9 +274,8 @@ impl PubliclyVerifiableDkg {
}
}
- /// After consensus has agreed to include a verified
- /// message on the blockchain, we apply the chains
- /// to the state machine
+ /// After consensus has agreed to include a verified message on the blockchain,
+ /// we apply the chains to the state machine
pub fn apply_message(
&mut self,
sender: &Validator,
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 8ef4c8e7..ed39fc4b 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -121,6 +121,10 @@ pub enum Error {
/// The number of messages may not be greater than the number of validators
#[error("Invalid aggregate verification parameters: number of validators {0}, number of messages: {1}")]
InvalidAggregateVerificationParameters(u32, u32),
+
+ /// Validator not found in the DKG set of validators
+ #[error("Validator not found: {0}")]
+ UnknownValidator(EthereumAddress),
}
pub type Result = std::result::Result;
@@ -146,7 +150,7 @@ mod test_dkg_full {
use super::*;
use crate::test_common::*;
- fn make_shared_secret_simple_tdec(
+ pub fn create_shared_secret_simple_tdec(
dkg: &PubliclyVerifiableDkg,
aad: &[u8],
ciphertext_header: &ferveo_tdec::CiphertextHeader,
@@ -168,7 +172,7 @@ mod test_dkg_full {
.get_validator(&validator_keypair.public_key())
.unwrap();
pvss_aggregated
- .make_decryption_share_simple(
+ .create_decryption_share_simple(
ciphertext_header,
aad,
validator_keypair,
@@ -186,9 +190,6 @@ mod test_dkg_full {
.collect::>();
assert_eq!(domain_points.len(), decryption_shares.len());
- // TODO: Consider refactor this part into ferveo_tdec::combine_simple and expose it
- // as a public API in ferveo_tdec::api
-
let lagrange_coeffs =
ferveo_tdec::prepare_combine_simple::(domain_points);
let shared_secret = ferveo_tdec::share_combine_simple::(
@@ -221,7 +222,7 @@ mod test_dkg_full {
)
.unwrap();
- let (_, _, shared_secret) = make_shared_secret_simple_tdec(
+ let (_, _, shared_secret) = create_shared_secret_simple_tdec(
&dkg,
AAD,
&ciphertext.header().unwrap(),
@@ -277,7 +278,7 @@ mod test_dkg_full {
.get_validator(&validator_keypair.public_key())
.unwrap();
pvss_aggregated
- .make_decryption_share_simple_precomputed(
+ .create_decryption_share_simple_precomputed(
&ciphertext.header().unwrap(),
AAD,
validator_keypair,
@@ -330,7 +331,7 @@ mod test_dkg_full {
.unwrap();
let (pvss_aggregated, decryption_shares, _) =
- make_shared_secret_simple_tdec(
+ create_shared_secret_simple_tdec(
&dkg,
AAD,
&ciphertext.header().unwrap(),
@@ -402,7 +403,7 @@ mod test_dkg_full {
.unwrap();
// Create an initial shared secret
- let (_, _, old_shared_secret) = make_shared_secret_simple_tdec(
+ let (_, _, old_shared_secret) = create_shared_secret_simple_tdec(
&dkg,
AAD,
&ciphertext.header().unwrap(),
@@ -410,14 +411,17 @@ 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();
remaining_validators
.remove(&removed_validator_addr)
.unwrap();
- // dkg.vss.remove(&removed_validator_addr); // TODO: Test whether it makes any difference
+
+ let mut remaining_validator_keypairs = validator_keypairs.clone();
+ remaining_validator_keypairs
+ .pop()
+ .expect("Should have a keypair");
// Remember to remove one domain point too
let mut domain_points = dkg.domain_points();
@@ -433,14 +437,13 @@ mod test_dkg_full {
let share_updates = remaining_validators
.keys()
.map(|v_addr| {
- let deltas_i =
- ShareRecoveryUpdate::make_share_updates_for_recovery(
- &domain_points,
- &dkg.pvss_params.h.into_affine(),
- &x_r,
- dkg.dkg_params.security_threshold(),
- rng,
- );
+ let deltas_i = ShareRecoveryUpdate::create_share_updates(
+ &domain_points,
+ &dkg.pvss_params.h.into_affine(),
+ &x_r,
+ dkg.dkg_params.security_threshold(),
+ rng,
+ );
(v_addr.clone(), deltas_i)
})
.collect::>();
@@ -448,7 +451,6 @@ mod test_dkg_full {
// Participants share updates and update their shares
// Now, every participant separately:
- // TODO: Move this logic outside tests (see #162, #163)
let updated_shares: Vec<_> = remaining_validators
.values()
.map(|validator| {
@@ -471,7 +473,7 @@ mod test_dkg_full {
let pvss_list = dkg.vss.values().cloned().collect::>();
let pvss_aggregated = aggregate(&pvss_list).unwrap();
pvss_aggregated
- .make_updated_private_key_share(
+ .create_updated_private_key_share(
validator_keypair,
validator.share_index,
updates_for_participant.as_slice(),
@@ -489,10 +491,6 @@ mod test_dkg_full {
);
// Get decryption shares from remaining participants
- let mut remaining_validator_keypairs = validator_keypairs;
- remaining_validator_keypairs
- .pop()
- .expect("Should have a keypair");
let mut decryption_shares: Vec> =
remaining_validator_keypairs
.iter()
@@ -503,7 +501,7 @@ mod test_dkg_full {
dkg.vss.values().cloned().collect::>();
let pvss_aggregated = aggregate(&pvss_list).unwrap();
pvss_aggregated
- .make_decryption_share_simple(
+ .create_decryption_share_simple(
&ciphertext.header().unwrap(),
AAD,
validator_keypair,
@@ -575,7 +573,7 @@ mod test_dkg_full {
.unwrap();
// Create an initial shared secret
- let (_, _, old_shared_secret) = make_shared_secret_simple_tdec(
+ let (_, _, old_shared_secret) = create_shared_secret_simple_tdec(
&dkg,
AAD,
&ciphertext.header().unwrap(),
@@ -587,13 +585,12 @@ mod test_dkg_full {
.validators
.keys()
.map(|v_addr| {
- let deltas_i =
- ShareRefreshUpdate::make_share_updates_for_refresh(
- &dkg.domain_points(),
- &dkg.pvss_params.h.into_affine(),
- dkg.dkg_params.security_threshold(),
- rng,
- );
+ let deltas_i = ShareRefreshUpdate::create_share_updates(
+ &dkg.domain_points(),
+ &dkg.pvss_params.h.into_affine(),
+ dkg.dkg_params.security_threshold(),
+ rng,
+ );
(v_addr.clone(), deltas_i)
})
.collect::>();
@@ -601,7 +598,6 @@ mod test_dkg_full {
// Participants share updates and update their shares
// Now, every participant separately:
- // TODO: Move this logic outside tests (see #162, #163)
let updated_private_key_shares: Vec<_> = dkg
.validators
.values()
@@ -627,7 +623,7 @@ mod test_dkg_full {
let pvss_list = dkg.vss.values().cloned().collect::>();
let pvss_aggregated = aggregate(&pvss_list).unwrap();
pvss_aggregated
- .make_updated_private_key_share(
+ .create_updated_private_key_share(
validator_keypair,
validator.share_index,
updates_for_participant.as_slice(),
@@ -642,14 +638,15 @@ mod test_dkg_full {
.iter()
.enumerate()
.map(|(share_index, validator_keypair)| {
+ // In order to proceed with the decryption, we need to convert the updated private key shares
+ let private_key_share = &updated_private_key_shares
+ .get(share_index)
+ .unwrap()
+ .inner()
+ .0;
DecryptionShareSimple::create(
&validator_keypair.decryption_key,
- // 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,
+ private_key_share,
&ciphertext.header().unwrap(),
AAD,
&dkg.pvss_params.g_inv(),
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 52f4279c..e98dffe4 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -6,7 +6,7 @@ use ark_poly::{
polynomial::univariate::DensePolynomial, DenseUVPolynomial,
EvaluationDomain, Polynomial,
};
-use ferveo_common::Keypair;
+use ferveo_common::{serialization, Keypair};
use ferveo_tdec::{
CiphertextHeader, DecryptionSharePrecomputed, DecryptionShareSimple,
};
@@ -19,7 +19,7 @@ use zeroize::{self, Zeroize, ZeroizeOnDrop};
use crate::{
assert_no_share_duplicates, batch_to_projective_g1, batch_to_projective_g2,
- Error, PVSSMap, PrivateKeyShare, PrivateKeyShareUpdate,
+ DomainPoint, Error, PVSSMap, PrivateKeyShare, PrivateKeyShareUpdate,
PubliclyVerifiableDkg, Result, UpdatedPrivateKeyShare, Validator,
};
@@ -68,16 +68,16 @@ impl Default for PubliclyVerifiableParams {
/// Secret polynomial used in the PVSS protocol
/// We wrap this in a struct so that we can zeroize it after use
-pub struct SecretPolynomial(pub DensePolynomial);
+pub struct SecretPolynomial(pub DensePolynomial>);
impl SecretPolynomial {
pub fn new(
- s: &E::ScalarField,
+ s: &DomainPoint,
degree: usize,
rng: &mut impl RngCore,
) -> Self {
// Our random polynomial, \phi(x) = s + \sum_{i=1}^{t-1} a_i x^i
- let mut phi = DensePolynomial::::rand(degree, rng);
+ let mut phi = DensePolynomial::>::rand(degree, rng);
phi.coeffs[0] = *s; // setting the first coefficient to secret value
Self(phi)
}
@@ -107,16 +107,17 @@ impl ZeroizeOnDrop for SecretPolynomial {}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PubliclyVerifiableSS {
/// Used in Feldman commitment to the VSS polynomial, F = g^{\phi}
- #[serde_as(as = "ferveo_common::serialization::SerdeAs")]
+ #[serde_as(as = "serialization::SerdeAs")]
pub coeffs: Vec,
/// The shares to be dealt to each validator
- #[serde_as(as = "ferveo_common::serialization::SerdeAs")]
- // pub shares: Vec>, // TODO: Using a custom type instead of referring to E:G2Affine breaks the serialization
+ #[serde_as(as = "serialization::SerdeAs")]
+ // TODO: Using a custom type instead of referring to E:G2Affine breaks the serialization
+ // pub shares: Vec>,
pub shares: Vec,
/// Proof of Knowledge
- #[serde_as(as = "ferveo_common::serialization::SerdeAs")]
+ #[serde_as(as = "serialization::SerdeAs")]
pub sigma: E::G2Affine,
/// Marker struct to distinguish between aggregated and
@@ -171,7 +172,7 @@ impl PubliclyVerifiableSS {
// TODO: Cross check proof of knowledge check with the whitepaper; this check proves that there is a relationship between the secret and the pvss transcript
// Sigma is a proof of knowledge of the secret, sigma = h^s
- let sigma = E::G2Affine::generator().mul(*s).into(); //todo hash to curve
+ let sigma = E::G2Affine::generator().mul(*s).into(); // TODO: Use hash-to-curve here
let vss = Self {
coeffs,
shares,
@@ -323,17 +324,18 @@ impl PubliclyVerifiableSS {
/// 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(
+ // TODO: Consider deprecating to use PrivateKeyShare method directly
+ pub fn create_decryption_share_simple(
&self,
- ciphertext: &CiphertextHeader,
+ ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
share_index: u32,
g_inv: &E::G1Prepared,
) -> Result> {
self.decrypt_private_key_share(validator_keypair, share_index)?
- .make_decryption_share_simple(
- ciphertext,
+ .create_decryption_share_simple(
+ ciphertext_header,
aad,
validator_keypair,
g_inv,
@@ -342,17 +344,18 @@ impl PubliclyVerifiableSS {
/// 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(
+ // TODO: Consider deprecating to use PrivateKeyShare method directly
+ pub fn create_decryption_share_simple_precomputed(
&self,
ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
share_index: u32,
- domain_points: &[E::ScalarField],
+ domain_points: &[DomainPoint],
g_inv: &E::G1Prepared,
) -> Result> {
self.decrypt_private_key_share(validator_keypair, share_index)?
- .make_decryption_share_simple_precomputed(
+ .create_decryption_share_simple_precomputed(
ciphertext_header,
aad,
validator_keypair,
@@ -362,7 +365,8 @@ impl PubliclyVerifiableSS {
)
}
- pub fn make_updated_private_key_share(
+ // TODO: Consider deprecating to use PrivateKeyShare method directly
+ pub fn create_updated_private_key_share(
&self,
validator_keypair: &Keypair,
share_index: u32,
@@ -371,7 +375,7 @@ impl PubliclyVerifiableSS {
// Retrieve the private key share and apply the updates
Ok(self
.decrypt_private_key_share(validator_keypair, share_index)?
- .make_updated_key_share(share_updates))
+ .create_updated_key_share(share_updates))
}
}
diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs
index 126e281a..87797a9b 100644
--- a/ferveo/src/refresh.rs
+++ b/ferveo/src/refresh.rs
@@ -10,9 +10,10 @@ use ferveo_tdec::{
};
use itertools::zip_eq;
use rand_core::RngCore;
+use serde::{Deserialize, Serialize};
use zeroize::ZeroizeOnDrop;
-use crate::{Error, Result};
+use crate::{DomainPoint, Error, Result};
// TODO: Rename refresh.rs to key_share.rs?
@@ -30,7 +31,7 @@ impl PrivateKeyShare {
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(
+ pub fn create_updated_key_share(
&self,
share_updates: &[impl PrivateKeyShareUpdate],
) -> UpdatedPrivateKeyShare {
@@ -46,9 +47,10 @@ impl PrivateKeyShare {
}
/// From the PSS paper, section 4.2.4, (https://link.springer.com/content/pdf/10.1007/3-540-44750-4_27.pdf)
+ /// `x_r` is the point at which the share is to be recovered
pub fn recover_share_from_updated_private_shares(
- x_r: &E::ScalarField,
- domain_points: &[E::ScalarField],
+ x_r: &DomainPoint,
+ domain_points: &[DomainPoint],
updated_private_shares: &[UpdatedPrivateKeyShare],
) -> PrivateKeyShare {
// Interpolate new shares to recover y_r
@@ -61,9 +63,9 @@ impl PrivateKeyShare {
})
}
- pub fn make_decryption_share_simple(
+ pub fn create_decryption_share_simple(
&self,
- ciphertext: &CiphertextHeader,
+ ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
g_inv: &E::G1Prepared,
@@ -71,20 +73,20 @@ impl PrivateKeyShare {
DecryptionShareSimple::create(
&validator_keypair.decryption_key,
&self.0,
- ciphertext,
+ ciphertext_header,
aad,
g_inv,
)
.map_err(|e| e.into())
}
- pub fn make_decryption_share_simple_precomputed(
+ pub fn create_decryption_share_simple_precomputed(
&self,
ciphertext_header: &CiphertextHeader,
aad: &[u8],
validator_keypair: &Keypair,
share_index: u32,
- domain_points: &[E::ScalarField],
+ domain_points: &[DomainPoint],
g_inv: &E::G1Prepared,
) -> Result> {
// In precomputed variant, we offload the some of the decryption related computation to the server-side:
@@ -107,9 +109,10 @@ impl PrivateKeyShare {
}
/// 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);
+pub struct UpdatedPrivateKeyShare(
+ pub(crate) InnerPrivateKeyShare,
+);
impl UpdatedPrivateKeyShare {
/// One-way conversion from `UpdatedPrivateKeyShare` to `PrivateKeyShare`.
@@ -128,12 +131,12 @@ impl UpdatedPrivateKeyShare {
// 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?
+ fn inner(&self) -> &InnerPrivateKeyShare;
}
/// 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 struct ShareRecoveryUpdate(pub(crate) InnerPrivateKeyShare);
impl PrivateKeyShareUpdate for ShareRecoveryUpdate {
fn inner(&self) -> &InnerPrivateKeyShare {
@@ -143,10 +146,10 @@ impl PrivateKeyShareUpdate for ShareRecoveryUpdate {
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],
+ pub fn create_share_updates(
+ domain_points: &[DomainPoint],
h: &E::G2Affine,
- x_r: &E::ScalarField,
+ x_r: &DomainPoint,
threshold: u32,
rng: &mut impl RngCore,
) -> Vec> {
@@ -165,8 +168,12 @@ impl ShareRecoveryUpdate {
}
/// 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);
+#[derive(
+ Serialize, Deserialize, Debug, Clone, PartialEq, Eq, ZeroizeOnDrop,
+)]
+pub struct ShareRefreshUpdate(
+ pub(crate) ferveo_tdec::PrivateKeyShare,
+);
impl PrivateKeyShareUpdate for ShareRefreshUpdate {
fn inner(&self) -> &InnerPrivateKeyShare {
@@ -176,8 +183,8 @@ impl PrivateKeyShareUpdate for ShareRefreshUpdate {
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],
+ pub fn create_share_updates(
+ domain_points: &[DomainPoint],
h: &E::G2Affine,
threshold: u32,
rng: &mut impl RngCore,
@@ -186,7 +193,7 @@ impl ShareRefreshUpdate {
prepare_share_updates_with_root::(
domain_points,
h,
- &E::ScalarField::zero(),
+ &DomainPoint::::zero(),
threshold,
rng,
)
@@ -198,14 +205,14 @@ impl ShareRefreshUpdate {
}
/// 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`
+/// This is a helper function for `ShareRecoveryUpdate::create_share_updates_for_recovery` and `ShareRefreshUpdate::create_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],
+ domain_points: &[DomainPoint],
h: &E::G2Affine,
- root: &E::ScalarField,
+ root: &DomainPoint,
threshold: u32,
rng: &mut impl RngCore,
) -> Vec> {
@@ -228,22 +235,22 @@ fn prepare_share_updates_with_root(
/// Generate a random polynomial with a given root
fn make_random_polynomial_with_root(
degree: u32,
- root: &E::ScalarField,
+ root: &DomainPoint,
rng: &mut impl RngCore,
-) -> DensePolynomial {
+) -> DensePolynomial> {
// [c_0, c_1, ..., c_{degree}] (Random polynomial)
let mut poly =
- DensePolynomial::::rand(degree as usize, rng);
+ DensePolynomial::>::rand(degree as usize, rng);
// [0, c_1, ... , c_{degree}] (We zeroize the free term)
- poly[0] = E::ScalarField::zero();
+ poly[0] = DomainPoint::::zero();
// Now, we calculate a new free term so that `poly(root) = 0`
- let new_c_0 = E::ScalarField::zero() - poly.evaluate(root);
+ let new_c_0 = DomainPoint::::zero() - poly.evaluate(root);
poly[0] = new_c_0;
// Evaluating the polynomial at the root should result in 0
- debug_assert!(poly.evaluate(root) == E::ScalarField::zero());
+ debug_assert!(poly.evaluate(root) == DomainPoint::::zero());
debug_assert!(poly.coeffs.len() == (degree + 1) as usize);
poly
@@ -267,16 +274,13 @@ mod tests_refresh {
};
/// 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(
+ fn create_updated_private_key_shares(
rng: &mut R,
threshold: u32,
x_r: &Fr,
remaining_participants: &[PrivateDecryptionContextSimple],
) -> Vec> {
// Each participant prepares an update for each other participant
- // TODO: Extract as parameter
let domain_points = remaining_participants[0]
.public_decryption_contexts
.iter()
@@ -286,15 +290,14 @@ mod tests_refresh {
let share_updates = remaining_participants
.iter()
.map(|p| {
- let deltas_i =
- ShareRecoveryUpdate::make_share_updates_for_recovery(
- &domain_points,
- &h,
- x_r,
- threshold,
- rng,
- );
- (p.index, deltas_i)
+ let share_updates = ShareRecoveryUpdate::create_share_updates(
+ &domain_points,
+ &h,
+ x_r,
+ threshold,
+ rng,
+ );
+ (p.index, share_updates)
})
.collect::>();
@@ -309,9 +312,8 @@ mod tests_refresh {
.collect();
// And updates their share
- // TODO: Remove wrapping after migrating to dkg based tests
PrivateKeyShare(p.private_key_share.clone())
- .make_updated_key_share(&updates_for_participant)
+ .create_updated_key_share(&updates_for_participant)
})
.collect();
@@ -354,7 +356,7 @@ mod tests_refresh {
}
// Each participant prepares an update for each other participant, and uses it to create a new share fragment
- let updated_private_key_shares = make_updated_private_key_shares(
+ let updated_private_key_shares = create_updated_private_key_shares(
rng,
security_threshold,
&x_r,
@@ -390,7 +392,7 @@ mod tests_refresh {
}
/// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share.
- /// The new share is independent from the previously existing shares. We can use this to on-board a new participant into an existing cohort.
+ /// The new share is independent of the previously existing shares. We can use this to on-board a new participant into an existing cohort.
#[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")]
fn tdec_simple_variant_share_recovery_at_random_point(
@@ -418,7 +420,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 share_recovery_fragmetns = make_updated_private_key_shares(
+ let share_recovery_fragmetns = create_updated_private_key_shares(
rng,
threshold,
&x_r,
@@ -488,14 +490,14 @@ mod tests_refresh {
let share_updates = contexts
.iter()
.map(|p| {
- let deltas_i =
- ShareRefreshUpdate::::make_share_updates_for_refresh(
+ let share_updates =
+ ShareRefreshUpdate::