From 315d2b4cc2825e13820d9c64639490c44b538385 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 14 Feb 2024 16:10:01 +0100
Subject: [PATCH 1/6] feature!: remove state from dkg, part 1
---
.../examples/server_api_precomputed.py | 2 +-
ferveo-python/examples/server_api_simple.py | 2 +-
ferveo-python/ferveo/__init__.py | 1 +
ferveo-python/ferveo/__init__.pyi | 4 +
ferveo-python/test/test_ferveo.py | 2 +-
ferveo-python/test/test_serialization.py | 6 +-
ferveo-tdec/src/ciphertext.rs | 5 +-
ferveo-tdec/src/decryption.rs | 30 +-
ferveo-tdec/src/key_share.rs | 21 +-
ferveo-tdec/src/lib.rs | 32 +-
ferveo-wasm/examples/node/src/main.test.ts | 2 +-
ferveo-wasm/tests/node.rs | 3 +-
ferveo/benches/benchmarks/validity_checks.rs | 21 +-
ferveo/examples/bench_primitives_size.rs | 25 +-
ferveo/src/api.rs | 195 +++---
ferveo/src/bindings_python.rs | 25 +-
ferveo/src/bindings_wasm.rs | 13 +-
ferveo/src/dkg.rs | 583 ++++--------------
ferveo/src/lib.rs | 84 ++-
ferveo/src/pvss.rs | 95 ++-
ferveo/src/refresh.rs | 18 +-
ferveo/src/test_common.rs | 60 +-
22 files changed, 483 insertions(+), 746 deletions(-)
diff --git a/ferveo-python/examples/server_api_precomputed.py b/ferveo-python/examples/server_api_precomputed.py
index a9b98001..a37ad573 100644
--- a/ferveo-python/examples/server_api_precomputed.py
+++ b/ferveo-python/examples/server_api_precomputed.py
@@ -59,7 +59,7 @@ def gen_eth_addr(i: int) -> str:
# In the meantime, the client creates a ciphertext and decryption request
msg = "abc".encode()
aad = "my-aad".encode()
-ciphertext = encrypt(msg, aad, dkg.public_key)
+ciphertext = encrypt(msg, aad, client_aggregate.public_key)
# Having aggregated the transcripts, the validators can now create decryption shares
decryption_shares = []
diff --git a/ferveo-python/examples/server_api_simple.py b/ferveo-python/examples/server_api_simple.py
index 44fb69c4..beda8133 100644
--- a/ferveo-python/examples/server_api_simple.py
+++ b/ferveo-python/examples/server_api_simple.py
@@ -62,7 +62,7 @@ def gen_eth_addr(i: int) -> str:
# In the meantime, the client creates a ciphertext and decryption request
msg = "abc".encode()
aad = "my-aad".encode()
-ciphertext = encrypt(msg, aad, dkg.public_key)
+ciphertext = encrypt(msg, aad, client_aggregate.public_key)
# The client can serialize/deserialize ciphertext for transport
ciphertext_ser = bytes(ciphertext)
diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py
index 58a3a140..5088675d 100644
--- a/ferveo-python/ferveo/__init__.py
+++ b/ferveo-python/ferveo/__init__.py
@@ -40,4 +40,5 @@
NoTranscriptsToAggregate,
InvalidAggregateVerificationParameters,
UnknownValidator,
+ TooManyTranscripts,
)
diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi
index 894f71ed..f4b5fd9f 100644
--- a/ferveo-python/ferveo/__init__.pyi
+++ b/ferveo-python/ferveo/__init__.pyi
@@ -105,6 +105,7 @@ class DecryptionSharePrecomputed:
@final
class AggregatedTranscript:
+ public_key: DkgPublicKey
def __init__(self, messages: Sequence[ValidatorMessage]): ...
def verify(
self, validators_num: int, messages: Sequence[ValidatorMessage]
@@ -222,3 +223,6 @@ class InvalidAggregateVerificationParameters(Exception):
class UnknownValidator(Exception):
pass
+
+class TooManyTranscripts(Exception):
+ pass
diff --git a/ferveo-python/test/test_ferveo.py b/ferveo-python/test/test_ferveo.py
index 51af3867..82cbc4f1 100644
--- a/ferveo-python/test/test_ferveo.py
+++ b/ferveo-python/test/test_ferveo.py
@@ -89,7 +89,7 @@ def scenario_for_variant(
# Client creates a ciphertext and requests decryption shares from validators
msg = "abc".encode()
aad = "my-aad".encode()
- ciphertext = encrypt(msg, aad, dkg.public_key)
+ ciphertext = encrypt(msg, aad, client_aggregate.public_key)
# Having aggregated the transcripts, the validators can now create decryption shares
decryption_shares = []
diff --git a/ferveo-python/test/test_serialization.py b/ferveo-python/test/test_serialization.py
index 6c600771..d188ea4d 100644
--- a/ferveo-python/test/test_serialization.py
+++ b/ferveo-python/test/test_serialization.py
@@ -5,6 +5,7 @@
DkgPublicKey,
FerveoPublicKey,
FerveoVariant,
+ ValidatorMessage
)
@@ -32,7 +33,10 @@ def make_dkg_public_key():
validators=validators,
me=me,
)
- return dkg.public_key
+ transcripts = [ValidatorMessage(v, dkg.generate_transcript()) for v in validators]
+ aggregate = dkg.aggregate_transcripts(transcripts)
+ assert aggregate.verify(shares_num, transcripts)
+ return aggregate.public_key
def make_shared_secret():
diff --git a/ferveo-tdec/src/ciphertext.rs b/ferveo-tdec/src/ciphertext.rs
index cdaf956c..d5563132 100644
--- a/ferveo-tdec/src/ciphertext.rs
+++ b/ferveo-tdec/src/ciphertext.rs
@@ -108,8 +108,7 @@ pub fn encrypt(
// h
let h_gen = E::G2Affine::generator();
- let ry_prep =
- E::G1Prepared::from(pubkey.public_key_share.mul(rand_element).into());
+ let ry_prep = E::G1Prepared::from(pubkey.0.mul(rand_element).into());
// s
let product = E::pairing(ry_prep, h_gen).0;
// u
@@ -150,7 +149,7 @@ pub fn decrypt_symmetric(
ciphertext.check(aad, g_inv)?;
let shared_secret = E::pairing(
E::G1Prepared::from(ciphertext.commitment),
- E::G2Prepared::from(private_key.private_key_share),
+ E::G2Prepared::from(private_key.0),
)
.0;
let shared_secret = SharedSecret(shared_secret);
diff --git a/ferveo-tdec/src/decryption.rs b/ferveo-tdec/src/decryption.rs
index dc93fee4..316d82f1 100644
--- a/ferveo-tdec/src/decryption.rs
+++ b/ferveo-tdec/src/decryption.rs
@@ -74,13 +74,13 @@ impl ValidatorShareChecksum {
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(bound(
- serialize = "ValidatorShareChecksum: Serialize",
- deserialize = "ValidatorShareChecksum: DeserializeOwned"
-))]
pub struct DecryptionShareSimple {
#[serde_as(as = "serialization::SerdeAs")]
pub decryption_share: E::TargetField,
+ #[serde(bound(
+ serialize = "ValidatorShareChecksum: Serialize",
+ deserialize = "ValidatorShareChecksum: DeserializeOwned"
+ ))]
pub validator_checksum: ValidatorShareChecksum,
}
@@ -110,11 +110,8 @@ impl DecryptionShareSimple {
ciphertext_header: &CiphertextHeader,
) -> Result {
// D_i = e(U, Z_i)
- let decryption_share = E::pairing(
- ciphertext_header.commitment,
- private_key_share.private_key_share,
- )
- .0;
+ let decryption_share =
+ E::pairing(ciphertext_header.commitment, private_key_share.0).0;
let validator_checksum = ValidatorShareChecksum::new(
validator_decryption_key,
@@ -146,14 +143,14 @@ impl DecryptionShareSimple {
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(bound(
- serialize = "ValidatorShareChecksum: Serialize",
- deserialize = "ValidatorShareChecksum: DeserializeOwned"
-))]
pub struct DecryptionSharePrecomputed {
pub decrypter_index: usize,
#[serde_as(as = "serialization::SerdeAs")]
pub decryption_share: E::TargetField,
+ #[serde(bound(
+ serialize = "ValidatorShareChecksum: Serialize",
+ deserialize = "ValidatorShareChecksum: DeserializeOwned"
+ ))]
pub validator_checksum: ValidatorShareChecksum,
}
@@ -188,11 +185,8 @@ impl DecryptionSharePrecomputed {
let u_to_lagrange_coeff =
ciphertext_header.commitment.mul(lagrange_coeff);
// C_{λ_i} = e(U_{λ_i}, Z_i)
- let decryption_share = E::pairing(
- u_to_lagrange_coeff,
- private_key_share.private_key_share,
- )
- .0;
+ let decryption_share =
+ E::pairing(u_to_lagrange_coeff, private_key_share.0).0;
let validator_checksum = ValidatorShareChecksum::new(
validator_decryption_key,
diff --git a/ferveo-tdec/src/key_share.rs b/ferveo-tdec/src/key_share.rs
index 4c164f6e..236386ae 100644
--- a/ferveo-tdec/src/key_share.rs
+++ b/ferveo-tdec/src/key_share.rs
@@ -9,11 +9,12 @@ use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use zeroize::{Zeroize, ZeroizeOnDrop};
-#[derive(Debug, Clone)]
+#[serde_as]
+#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
// TODO: Should we rename it to PublicKey or SharedPublicKey?
-pub struct PublicKeyShare {
- pub public_key_share: E::G1Affine, // A_{i, \omega_i}
-}
+pub struct PublicKeyShare(
+ #[serde_as(as = "serialization::SerdeAs")] pub E::G1Affine, // A_{i, \omega_i}
+);
#[derive(Debug, Clone)]
pub struct BlindedKeyShare {
@@ -31,7 +32,7 @@ impl BlindedKeyShare {
let alpha = E::ScalarField::rand(rng);
let alpha_a = E::G1Prepared::from(
- g + public_key_share.public_key_share.mul(alpha).into_affine(),
+ g + public_key_share.0.mul(alpha).into_affine(),
);
// \sum_i(Y_i)
@@ -56,18 +57,16 @@ impl BlindedKeyShare {
#[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,
-}
+pub struct PrivateKeyShare(
+ #[serde_as(as = "serialization::SerdeAs")] pub E::G2Affine,
+);
impl PrivateKeyShare {
pub fn blind(&self, b: E::ScalarField) -> BlindedKeyShare {
let blinding_key = E::G2Affine::generator().mul(b).into_affine();
BlindedKeyShare:: {
blinding_key,
- blinded_key_share: self.private_key_share.mul(b).into_affine(),
+ blinded_key_share: self.0.mul(b).into_affine(),
}
}
}
diff --git a/ferveo-tdec/src/lib.rs b/ferveo-tdec/src/lib.rs
index 322d1bf9..465b3717 100644
--- a/ferveo-tdec/src/lib.rs
+++ b/ferveo-tdec/src/lib.rs
@@ -138,9 +138,7 @@ pub mod test_common {
)
.enumerate()
{
- let private_key_share = PrivateKeyShare {
- private_key_share: *private,
- };
+ let private_key_share = PrivateKeyShare(*private);
let b = E::ScalarField::rand(rng);
let mut blinded_key_shares = private_key_share.blind(b);
blinded_key_shares.multiply_by_omega_inv(domain_inv);
@@ -159,9 +157,7 @@ pub mod test_common {
});
public_contexts.push(PublicDecryptionContextFast:: {
domain: *domain,
- public_key_share: PublicKeyShare:: {
- public_key_share: *public,
- },
+ public_key_share: PublicKeyShare::(*public),
blinded_key_share: blinded_key_shares,
lagrange_n_0: *domain,
h_inv: E::G2Prepared::from(-h.into_group()),
@@ -172,12 +168,8 @@ pub mod test_common {
}
(
- PublicKeyShare {
- public_key_share: pubkey.into(),
- },
- PrivateKeyShare {
- private_key_share: privkey.into(),
- },
+ PublicKeyShare(pubkey.into()),
+ PrivateKeyShare(privkey.into()),
private_contexts,
)
}
@@ -235,9 +227,7 @@ pub mod test_common {
izip!(shares_x.iter(), pubkey_shares.iter(), privkey_shares.iter())
.enumerate()
{
- let private_key_share = PrivateKeyShare:: {
- private_key_share: *private,
- };
+ let private_key_share = PrivateKeyShare::(*private);
let b = E::ScalarField::rand(rng);
let blinded_key_share = private_key_share.blind(b);
private_contexts.push(PrivateDecryptionContextSimple:: {
@@ -255,9 +245,7 @@ pub mod test_common {
});
public_contexts.push(PublicDecryptionContextSimple:: {
domain: *domain,
- public_key_share: PublicKeyShare:: {
- public_key_share: *public,
- },
+ public_key_share: PublicKeyShare::(*public),
blinded_key_share,
h,
validator_public_key: h.mul(b),
@@ -268,12 +256,8 @@ pub mod test_common {
}
(
- PublicKeyShare {
- public_key_share: pubkey.into(),
- },
- PrivateKeyShare {
- private_key_share: privkey.into(),
- },
+ PublicKeyShare(pubkey.into()),
+ PrivateKeyShare(privkey.into()),
private_contexts,
)
}
diff --git a/ferveo-wasm/examples/node/src/main.test.ts b/ferveo-wasm/examples/node/src/main.test.ts
index 00da665b..57071421 100644
--- a/ferveo-wasm/examples/node/src/main.test.ts
+++ b/ferveo-wasm/examples/node/src/main.test.ts
@@ -62,7 +62,7 @@ function setupTest(
// Client creates a ciphertext and requests decryption shares from validators
const msg = Buffer.from("my-msg");
const aad = Buffer.from("my-aad");
- const ciphertext = ferveoEncrypt(msg, aad, dkg.publicKey());
+ const ciphertext = ferveoEncrypt(msg, aad, clientAggregate.publicKey);
return {
validatorKeypairs,
diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs
index 7b35efa5..5d4ffbb4 100644
--- a/ferveo-wasm/tests/node.rs
+++ b/ferveo-wasm/tests/node.rs
@@ -80,7 +80,8 @@ fn setup_dkg(
// In the meantime, the client creates a ciphertext and decryption request
let msg = "my-msg".as_bytes().to_vec();
let aad = "my-aad".as_bytes().to_vec();
- let ciphertext = ferveo_encrypt(&msg, &aad, &dkg.public_key()).unwrap();
+ let ciphertext =
+ ferveo_encrypt(&msg, &aad, &client_aggregate.public_key()).unwrap();
(
validator_keypairs,
diff --git a/ferveo/benches/benchmarks/validity_checks.rs b/ferveo/benches/benchmarks/validity_checks.rs
index e6a27b74..72fc4946 100644
--- a/ferveo/benches/benchmarks/validity_checks.rs
+++ b/ferveo/benches/benchmarks/validity_checks.rs
@@ -55,11 +55,14 @@ fn setup_dkg(
fn setup(
shares_num: u32,
rng: &mut StdRng,
-) -> (PubliclyVerifiableDkg, Message) {
+) -> (
+ PubliclyVerifiableDkg,
+ PubliclyVerifiableSS,
+) {
let mut transcripts = vec![];
for i in 0..shares_num {
- let mut dkg = setup_dkg(i as usize, shares_num);
- transcripts.push(dkg.share(rng).expect("Test failed"));
+ let dkg = setup_dkg(i as usize, shares_num);
+ transcripts.push(dkg.generate_transcript(rng).expect("Test failed"));
}
let dkg = setup_dkg(0, shares_num);
let transcript = transcripts[0].clone();
@@ -78,20 +81,12 @@ pub fn bench_verify_full(c: &mut Criterion) {
let pvss_verify_optimistic = {
move || {
- if let Message::Deal(ss) = transcript {
- black_box(ss.verify_optimistic());
- } else {
- panic!("Expected Deal");
- }
+ black_box(transcript.verify_optimistic());
}
};
let pvss_verify_full = {
move || {
- if let Message::Deal(ss) = transcript {
- black_box(ss.verify_full(&dkg));
- } else {
- panic!("Expected Deal");
- }
+ black_box(transcript.verify_full(&dkg).unwrap());
}
};
diff --git a/ferveo/examples/bench_primitives_size.rs b/ferveo/examples/bench_primitives_size.rs
index d44d394c..755d10e1 100644
--- a/ferveo/examples/bench_primitives_size.rs
+++ b/ferveo/examples/bench_primitives_size.rs
@@ -91,20 +91,18 @@ fn setup(
shares_num: u32,
security_threshold: u32,
rng: &mut StdRng,
-) -> PubliclyVerifiableDkg {
+) -> (
+ PubliclyVerifiableDkg,
+ Vec>,
+) {
let mut transcripts = vec![];
for i in 0..shares_num {
- let mut dkg = setup_dkg(i as usize, shares_num, security_threshold);
- let message = dkg.share(rng).expect("Test failed");
- let sender = dkg.get_validator(&dkg.me.public_key).unwrap();
- transcripts.push((sender.clone(), message.clone()));
- }
-
- let mut dkg = setup_dkg(0, shares_num, security_threshold);
- for (sender, pvss) in transcripts.into_iter() {
- dkg.apply_message(&sender, &pvss).expect("Setup failed");
+ let dkg = setup_dkg(i as usize, shares_num, security_threshold);
+ let transcript = dkg.generate_transcript(rng).expect("Test failed");
+ transcripts.push(transcript.clone());
}
- dkg
+ let dkg = setup_dkg(0, shares_num, security_threshold);
+ (dkg, transcripts)
}
fn main() {
@@ -128,9 +126,8 @@ fn main() {
for (shares_num, threshold) in configs {
println!("shares_num: {shares_num}, threshold: {threshold}");
- let dkg = setup(*shares_num as u32, threshold, rng);
- let transcript = &dkg.vss.values().next().unwrap();
- let transcript_bytes = bincode::serialize(&transcript).unwrap();
+ let (_, transcripts) = setup(*shares_num as u32, threshold, rng);
+ let transcript_bytes = bincode::serialize(&transcripts[0]).unwrap();
save_data(
*shares_num as usize,
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index dd8e40bd..77372255 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -11,13 +11,12 @@ pub use ferveo_tdec::api::{
DecryptionSharePrecomputed, Fr, G1Affine, G1Prepared, G2Affine, SecretBox,
E,
};
-use ferveo_tdec::PublicKeyShare;
use generic_array::{
typenum::{Unsigned, U48},
GenericArray,
};
use rand::{thread_rng, RngCore};
-use serde::{Deserialize, Serialize};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_with::serde_as;
#[cfg(feature = "bindings-python")]
@@ -26,7 +25,7 @@ use crate::bindings_python;
use crate::bindings_wasm;
pub use crate::EthereumAddress;
use crate::{
- do_verify_aggregation, DomainPoint, Error, Message, PVSSMap,
+ do_verify_aggregation, DomainPoint, Error, PVSSMap,
PubliclyVerifiableParams, PubliclyVerifiableSS, Result,
};
@@ -54,17 +53,11 @@ pub fn from_bytes(bytes: &[u8]) -> Result {
pub fn encrypt(
message: SecretBox>,
aad: &[u8],
- pubkey: &DkgPublicKey,
+ public_key: &DkgPublicKey,
) -> Result {
- let mut rng = rand::thread_rng();
- let ciphertext = ferveo_tdec::api::encrypt(
- message,
- aad,
- &PublicKeyShare {
- public_key_share: pubkey.0,
- },
- &mut rng,
- )?;
+ let mut rng = thread_rng();
+ let ciphertext =
+ ferveo_tdec::api::encrypt(message, aad, &public_key.0, &mut rng)?;
Ok(Ciphertext(ciphertext))
}
@@ -147,16 +140,19 @@ 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,
+ #[serde(bound(
+ serialize = "ferveo_tdec::PublicKeyShare: Serialize",
+ deserialize = "ferveo_tdec::PublicKeyShare: DeserializeOwned"
+ ))]
+ pub(crate) ferveo_tdec::PublicKeyShare,
);
+// TODO: Consider moving these implementation details to ferveo_tdec::PublicKeyShare
impl DkgPublicKey {
pub fn to_bytes(&self) -> Result> {
- let as_bytes = to_bytes(&self.0)?;
+ let as_bytes = to_bytes(&self.0 .0)?;
Ok(GenericArray::::from_slice(&as_bytes).to_owned())
}
@@ -169,7 +165,8 @@ impl DkgPublicKey {
bytes.len(),
)
})?;
- from_bytes(&bytes).map(DkgPublicKey)
+ let pk: G1Affine = from_bytes(&bytes)?;
+ Ok(DkgPublicKey(ferveo_tdec::PublicKeyShare(pk)))
}
pub fn serialized_size() -> usize {
@@ -179,9 +176,9 @@ impl DkgPublicKey {
/// Generate a random DKG public key.
/// Use this for testing only.
pub fn random() -> Self {
- let mut rng = rand::thread_rng();
+ let mut rng = thread_rng();
let g1 = G1Affine::rand(&mut rng);
- Self(g1)
+ Self(ferveo_tdec::PublicKeyShare(g1))
}
}
@@ -222,43 +219,23 @@ impl Dkg {
Ok(Self(dkg))
}
- pub fn public_key(&self) -> DkgPublicKey {
- DkgPublicKey(self.0.public_key().public_key_share)
- }
-
pub fn generate_transcript(
&mut self,
rng: &mut R,
) -> Result {
- match self.0.share(rng) {
- Ok(Message::Deal(transcript)) => Ok(transcript),
- Err(e) => Err(e),
- _ => Err(Error::InvalidDkgStateToDeal),
- }
+ self.0.generate_transcript(rng)
}
pub fn aggregate_transcripts(
- &mut self,
+ &self,
messages: &[ValidatorMessage],
) -> 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
- // internal state of the DKG.
- // If we didn't do that, that would cause the DKG to produce incorrect decryption shares
- // in the future.
- // TODO: Remove this dependency on DKG state
- // TODO: Avoid mutating current state here
- for (validator, transcript) in messages {
- self.0.deal(validator, transcript)?;
- }
- let pvss = messages
- .iter()
- .map(|(_, t)| t)
- .cloned()
- .collect::>>();
- Ok(AggregatedTranscript(crate::pvss::aggregate(&pvss)?))
+ self.0
+ .aggregate_transcripts(messages)
+ .map(AggregatedTranscript)
}
+ // TODO: Unused?
pub fn public_params(&self) -> DkgPublicParameters {
DkgPublicParameters {
g1_inv: self.0.pvss_params.g_inv(),
@@ -283,18 +260,17 @@ fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap {
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct AggregatedTranscript(
- pub(crate) PubliclyVerifiableSS,
-);
+pub struct AggregatedTranscript(crate::AggregatedTranscript);
impl AggregatedTranscript {
pub fn new(messages: &[ValidatorMessage]) -> Result {
- let pvss_list = messages
+ let transcripts: Vec<_> = messages
.iter()
- .map(|(_, t)| t)
- .cloned()
- .collect::>>();
- Ok(AggregatedTranscript(crate::pvss::aggregate(&pvss_list)?))
+ .map(|(_, transcript)| transcript.clone())
+ .collect();
+ let aggregated_transcript =
+ crate::AggregatedTranscript::::from_transcripts(&transcripts)?;
+ Ok(AggregatedTranscript(aggregated_transcript))
}
pub fn verify(
@@ -314,7 +290,7 @@ impl AggregatedTranscript {
GeneralEvaluationDomain::::new(validators_num as usize)
.expect("Unable to construct an evaluation domain");
- let is_valid_optimistic = self.0.verify_optimistic();
+ let is_valid_optimistic = self.0.aggregate.verify_optimistic();
if !is_valid_optimistic {
return Err(Error::InvalidTranscriptAggregate);
}
@@ -328,8 +304,8 @@ impl AggregatedTranscript {
// This check also includes `verify_full`. See impl. for details.
let is_valid = do_verify_aggregation(
- &self.0.coeffs,
- &self.0.shares,
+ &self.0.aggregate.coeffs,
+ &self.0.aggregate.shares,
&pvss_params,
&validators,
&domain,
@@ -355,7 +331,7 @@ impl AggregatedTranscript {
dkg.0.dkg_params.security_threshold(),
));
}
- self.0.create_decryption_share_simple_precomputed(
+ self.0.aggregate.create_decryption_share_simple_precomputed(
&ciphertext_header.0,
aad,
validator_keypair,
@@ -373,7 +349,7 @@ impl AggregatedTranscript {
aad: &[u8],
validator_keypair: &Keypair,
) -> Result {
- let share = self.0.create_decryption_share_simple(
+ let share = self.0.aggregate.create_decryption_share_simple(
&ciphertext_header.0,
aad,
validator_keypair,
@@ -394,11 +370,16 @@ impl AggregatedTranscript {
) -> Result {
Ok(PrivateKeyShare(
self.0
+ .aggregate
.decrypt_private_key_share(validator_keypair, share_index)?
.0
.clone(),
))
}
+
+ pub fn public_key(&self) -> DkgPublicKey {
+ DkgPublicKey(self.0.public_key)
+ }
}
#[serde_as]
@@ -406,7 +387,7 @@ impl AggregatedTranscript {
pub struct DecryptionShareSimple {
share: ferveo_tdec::api::DecryptionShareSimple,
#[serde_as(as = "serialization::SerdeAs")]
- domain_point: Fr,
+ domain_point: DomainPoint,
}
// TODO: Deprecate?
@@ -450,8 +431,6 @@ pub fn combine_shares_simple(shares: &[DecryptionShareSimple]) -> SharedSecret {
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 {
@@ -678,7 +657,7 @@ mod test_ferveo_api {
let messages: Vec<_> = validators
.iter()
.map(|sender| {
- let mut dkg = Dkg::new(
+ let dkg = Dkg::new(
tau,
shares_num,
security_threshold,
@@ -686,7 +665,7 @@ mod test_ferveo_api {
sender,
)
.unwrap();
- (sender.clone(), dkg.generate_transcript(rng).unwrap())
+ (sender.clone(), dkg.0.generate_transcript(rng).unwrap())
})
.collect();
@@ -719,18 +698,16 @@ mod test_ferveo_api {
validators_num,
);
- // Now that every validator holds a dkg instance and a transcript for every other validator,
- // every validator can aggregate the transcripts
+ // Every validator can aggregate the transcripts
let me = validators[0].clone();
- let mut dkg =
+ let dkg =
Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
-
let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
assert!(pvss_aggregated.verify(validators_num, &messages).unwrap());
// At this point, any given validator should be able to provide a DKG public key
- let dkg_public_key = dkg.public_key();
+ let dkg_public_key = pvss_aggregated.public_key();
// In the meantime, the client creates a ciphertext and decryption request
let ciphertext =
@@ -742,7 +719,7 @@ mod test_ferveo_api {
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(
+ let dkg = Dkg::new(
TAU,
shares_num,
security_threshold,
@@ -812,7 +789,7 @@ mod test_ferveo_api {
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
- let mut dkg = Dkg::new(
+ let dkg = Dkg::new(
TAU,
shares_num,
security_threshold,
@@ -820,12 +797,11 @@ mod test_ferveo_api {
&validators[0],
)
.unwrap();
-
let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
assert!(pvss_aggregated.verify(validators_num, &messages).unwrap());
// At this point, any given validator should be able to provide a DKG public key
- let public_key = dkg.public_key();
+ let public_key = pvss_aggregated.public_key();
// In the meantime, the client creates a ciphertext and decryption request
let ciphertext =
@@ -836,7 +812,7 @@ mod test_ferveo_api {
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(
+ let dkg = Dkg::new(
TAU,
shares_num,
security_threshold,
@@ -907,10 +883,9 @@ mod test_ferveo_api {
// Now that every validator holds a dkg instance and a transcript for every other validator,
// every validator can aggregate the transcripts
let me = validators[0].clone();
- let mut dkg =
+ let dkg =
Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
-
let good_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
assert!(good_aggregate.verify(validators_num, &messages).is_ok());
@@ -926,7 +901,7 @@ mod test_ferveo_api {
));
// Should fail if no transcripts are provided
- let mut dkg =
+ let dkg =
Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
assert!(matches!(
@@ -935,7 +910,7 @@ mod test_ferveo_api {
));
// Not enough transcripts
- let mut dkg =
+ let dkg =
Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
let not_enough_messages = &messages[..security_threshold as usize - 1];
@@ -947,19 +922,51 @@ mod test_ferveo_api {
Err(Error::InvalidTranscriptAggregate)
));
+ // Duplicated transcripts
+ let message_with_duplicated_validator = (
+ // Duplicating the validator but with a different, valid transcript
+ messages[0].0.clone(),
+ messages[security_threshold as usize].1.clone(),
+ );
+ let mut messages_with_duplicates = messages.clone();
+ messages_with_duplicates.push(message_with_duplicated_validator);
+ // assert_eq!(messages_with_duplicates.len(), security_threshold as usize);
+ assert!(dkg
+ .aggregate_transcripts(&messages_with_duplicates)
+ .is_err());
+
+ // TODO: Transcripts are not hashable?
+ // let message_with_duplicated_transcript = (
+ // // Duplicating the transcript but with a different, valid validator
+ // validators[security_threshold as usize - 1].clone(),
+ // messages[security_threshold as usize - 1].1.clone(),
+ // );
+ // let messages_with_duplicates = [
+ // &messages[..(security_threshold - 1) as usize],
+ // &[message_with_duplicated_transcript],
+ // ]
+ // .concat();
+ // assert_eq!(messages_with_duplicates.len(), security_threshold as usize);
+ // assert!(dkg
+ // .aggregate_transcripts(&messages_with_duplicates)
+ // .is_err());
+
// Unexpected transcripts in the aggregate or transcripts from a different ritual
// Using same DKG parameters, but different DKG instances and validators
let mut dkg =
Dkg::new(TAU, shares_num, security_threshold, &validators, &me)
.unwrap();
- let (bad_messages, _, _) = make_test_inputs(
- rng,
- TAU,
- security_threshold,
- shares_num,
- validators_num,
+ let bad_message = (
+ // Reusing a good validator, but giving them a bad transcript
+ messages[security_threshold as usize - 1].0.clone(),
+ dkg.generate_transcript(rng).unwrap(),
);
- let mixed_messages = [&messages[..2], &bad_messages[..1]].concat();
+ let mixed_messages = [
+ &messages[..(security_threshold - 1) as usize],
+ &[bad_message],
+ ]
+ .concat();
+ assert_eq!(mixed_messages.len(), security_threshold as usize);
let bad_aggregate = dkg.aggregate_transcripts(&mixed_messages).unwrap();
assert!(matches!(
bad_aggregate.verify(validators_num, &messages),
@@ -1058,7 +1065,7 @@ mod test_ferveo_api {
shares_num,
validators_num,
);
- let mut dkgs = validators
+ let dkgs = validators
.iter()
.map(|validator| {
Dkg::new(
@@ -1071,17 +1078,25 @@ mod test_ferveo_api {
.unwrap()
})
.collect::>();
- let pvss_aggregated = dkgs[0].aggregate_transcripts(&messages).unwrap();
+
+ // Creating a copy to avoiding accidentally changing DKG state
+ let mut dkg = dkgs[0].clone();
+ let pvss_aggregated = dkg.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 public_key = pvss_aggregated.public_key();
let ciphertext =
- encrypt(SecretBox::new(MSG.to_vec()), AAD, public_key).unwrap();
+ encrypt(SecretBox::new(MSG.to_vec()), AAD, &public_key).unwrap();
let ciphertext_header = ciphertext.header().unwrap();
+ for (validator, transcript) in messages.iter() {
+ dkg.0
+ .vss
+ .insert(validator.address.clone(), transcript.clone());
+ }
let (_, _, old_shared_secret) =
crate::test_dkg_full::create_shared_secret_simple_tdec(
- &dkgs[0].0,
+ &dkg.0,
AAD,
&ciphertext_header.0,
validator_keypairs.as_slice(),
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index f6fce72c..a229568c 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -58,8 +58,8 @@ impl From for PyErr {
Error::DuplicateDealer(dealer) => {
DuplicateDealer::new_err(dealer.to_string())
}
- Error::InvalidPvssTranscript => {
- InvalidPvssTranscript::new_err("")
+ Error::InvalidPvssTranscript(validator_addr) => {
+ InvalidPvssTranscript::new_err(validator_addr.to_string())
}
Error::InsufficientTranscriptsForAggregate(
expected,
@@ -124,6 +124,11 @@ impl From for PyErr {
Error::UnknownValidator(validator) => {
UnknownValidator::new_err(validator.to_string())
},
+ Error::TooManyTranscripts(expected, received) => {
+ TooManyTranscripts::new_err(format!(
+ "expected: {expected}, received: {received}"
+ ))
+ }
// 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.
},
@@ -174,6 +179,7 @@ create_exception!(
PyValueError
);
create_exception!(exceptions, UnknownValidator, PyValueError);
+create_exception!(exceptions, TooManyTranscripts, PyValueError);
fn from_py_bytes(bytes: &[u8]) -> PyResult {
T::from_bytes(bytes)
@@ -516,11 +522,6 @@ impl Dkg {
Ok(Self(dkg))
}
- #[getter]
- pub fn public_key(&self) -> DkgPublicKey {
- DkgPublicKey(self.0.public_key())
- }
-
pub fn generate_transcript(&mut self) -> PyResult {
let rng = &mut thread_rng();
let transcript = self
@@ -669,6 +670,11 @@ impl AggregatedTranscript {
.map_err(FerveoPythonError::FerveoError)?;
Ok(DecryptionShareSimple(decryption_share))
}
+
+ #[getter]
+ pub fn public_key(&self) -> DkgPublicKey {
+ DkgPublicKey(self.0.public_key())
+ }
}
// Since adding functions in pyo3 requires a two-step process
@@ -789,6 +795,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> {
py.get_type::(),
)?;
m.add("UnknownValidator", py.get_type::())?;
+ m.add("TooManyTranscripts", py.get_type::())?;
Ok(())
}
@@ -883,7 +890,7 @@ mod test_ferveo_python {
.unwrap());
// At this point, any given validator should be able to provide a DKG public key
- let dkg_public_key = dkg.public_key();
+ let dkg_public_key = pvss_aggregated.public_key();
// In the meantime, the client creates a ciphertext and decryption request
let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap();
@@ -961,7 +968,7 @@ mod test_ferveo_python {
.unwrap());
// At this point, any given validator should be able to provide a DKG public key
- let dkg_public_key = dkg.public_key();
+ let dkg_public_key = pvss_aggregated.public_key();
// In the meantime, the client creates a ciphertext and decryption request
let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap();
diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs
index 3c885269..56325092 100644
--- a/ferveo/src/bindings_wasm.rs
+++ b/ferveo/src/bindings_wasm.rs
@@ -360,11 +360,6 @@ impl Dkg {
Ok(Self(dkg))
}
- #[wasm_bindgen(js_name = "publicKey")]
- pub fn public_key(&self) -> DkgPublicKey {
- DkgPublicKey(self.0.public_key())
- }
-
#[wasm_bindgen(js_name = "generateTranscript")]
pub fn generate_transcript(&mut self) -> JsResult {
let rng = &mut thread_rng();
@@ -496,6 +491,14 @@ impl ValidatorMessage {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AggregatedTranscript(api::AggregatedTranscript);
+#[wasm_bindgen]
+impl AggregatedTranscript {
+ #[wasm_bindgen(getter, js_name = "publicKey")]
+ pub fn public_key(&self) -> DkgPublicKey {
+ DkgPublicKey(self.0.public_key())
+ }
+}
+
generate_common_methods!(AggregatedTranscript);
#[wasm_bindgen]
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index 1ee6ec6e..fe1e18c2 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -1,21 +1,20 @@
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashSet};
-use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup};
+use ark_ec::pairing::Pairing;
use ark_poly::EvaluationDomain;
use ark_std::UniformRand;
use ferveo_common::PublicKey;
use measure_time::print_time;
use rand::RngCore;
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
-use serde_with::serde_as;
+use serde::{Deserialize, Serialize};
use crate::{
- aggregate, assert_no_share_duplicates, AggregatedPvss, Error,
- EthereumAddress, PubliclyVerifiableParams, PubliclyVerifiableSS, Result,
- Validator,
+ assert_no_share_duplicates, AggregatedTranscript, Error, EthereumAddress,
+ PubliclyVerifiableParams, PubliclyVerifiableSS, Result, Validator,
};
pub type DomainPoint = ::ScalarField;
+pub type ValidatorMessage = (Validator, PubliclyVerifiableSS);
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct DkgParams {
@@ -68,29 +67,6 @@ impl DkgParams {
pub type ValidatorsMap = BTreeMap>;
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,
- },
- Dealt,
- Success {
- public_key: ferveo_tdec::PublicKeyShare,
- },
- Invalid,
-}
-
-impl DkgState {
- fn new() -> Self {
- DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- }
- }
-}
-
/// 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.
@@ -100,10 +76,10 @@ pub struct PubliclyVerifiableDkg {
pub dkg_params: DkgParams,
pub pvss_params: PubliclyVerifiableParams,
pub validators: ValidatorsMap,
+ // TODO: Remove vss?
pub vss: PVSSMap,
pub domain: ark_poly::GeneralEvaluationDomain,
pub me: Validator,
- state: DkgState,
}
impl PubliclyVerifiableDkg {
@@ -145,10 +121,10 @@ impl PubliclyVerifiableDkg {
domain,
me: me.clone(),
validators,
- state: DkgState::new(),
})
}
+ /// Get the validator with for the given public key
pub fn get_validator(
&self,
public_key: &PublicKey,
@@ -159,48 +135,25 @@ impl PubliclyVerifiableDkg {
}
/// Create a new PVSS instance within this DKG session, contributing to the final key
- /// `rng` is a cryptographic random number generator
- /// Returns a PVSS dealing message to post on-chain
- pub fn share(&mut self, rng: &mut R) -> Result> {
+ pub fn generate_transcript(
+ &self,
+ rng: &mut R,
+ ) -> Result> {
print_time!("PVSS Sharing");
- match self.state {
- DkgState::Sharing { .. } | DkgState::Dealt => {
- let vss = PubliclyVerifiableSS::::new(
- &DomainPoint::::rand(rng),
- self,
- rng,
- )?;
- Ok(Message::Deal(vss))
- }
- _ => Err(Error::InvalidDkgStateToDeal),
- }
+ PubliclyVerifiableSS::::new(&DomainPoint::::rand(rng), self, rng)
}
/// Aggregate all received PVSS messages into a single message, prepared to post on-chain
- pub fn aggregate(&self) -> Result> {
- match self.state {
- DkgState::Dealt => {
- let public_key = self.public_key();
- let pvss_list = self.vss.values().cloned().collect::>();
- Ok(Message::Aggregate(Aggregation {
- vss: aggregate(&pvss_list)?,
- public_key: public_key.public_key_share,
- }))
- }
- _ => Err(Error::InvalidDkgStateToAggregate),
- }
- }
-
- /// Returns the public key generated by the DKG
- 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(),
- }
+ pub fn aggregate_transcripts(
+ &self,
+ messages: &[ValidatorMessage],
+ ) -> Result> {
+ self.verify_transcripts(messages)?;
+ let transcripts: Vec> = messages
+ .iter()
+ .map(|(_sender, transcript)| transcript.clone())
+ .collect();
+ AggregatedTranscript::::from_transcripts(&transcripts)
}
/// Return a domain point for the share_index
@@ -212,10 +165,12 @@ impl PubliclyVerifiableDkg {
}
/// Return an appropriate amount of domain points for the DKG
+ /// The number of domain points should be equal to the number of validators
pub fn domain_points(&self) -> Vec> {
self.domain.elements().take(self.validators.len()).collect()
}
+ /// Remove a validator from the DKG
pub fn offboard_validator(
&mut self,
address: &EthereumAddress,
@@ -228,142 +183,48 @@ impl PubliclyVerifiableDkg {
}
}
- pub fn verify_message(
+ /// Verify PVSS transcripts against the set of validators in the DKG
+ // TODO: Make private?
+ pub fn verify_transcripts(
&self,
- sender: &Validator,
- message: &Message,
+ messages: &[ValidatorMessage],
) -> Result<()> {
- match message {
- Message::Deal(pvss)
- if matches!(
- self.state,
- DkgState::Sharing { .. } | DkgState::Dealt
- ) =>
- {
- if !self.validators.contains_key(&sender.address) {
- Err(Error::UnknownDealer(sender.clone().address))
- } else if self.vss.contains_key(&sender.address) {
- Err(Error::DuplicateDealer(sender.clone().address))
- } else if !pvss.verify_optimistic() {
- Err(Error::InvalidPvssTranscript)
- } else {
- Ok(())
- }
- }
- Message::Aggregate(Aggregation { vss, public_key })
- if matches!(self.state, DkgState::Dealt) =>
- {
- let minimum_shares = self.dkg_params.shares_num
- - self.dkg_params.security_threshold;
- let actual_shares = vss.shares.len() as u32;
- // We reject aggregations that fail to meet the security threshold
- if actual_shares < minimum_shares {
- Err(Error::InsufficientTranscriptsForAggregate(
- minimum_shares,
- actual_shares,
- ))
- } else if vss.verify_aggregation(self).is_err() {
- Err(Error::InvalidTranscriptAggregate)
- } else if &self.public_key().public_key_share == public_key {
- Ok(())
- } else {
- Err(Error::InvalidDkgPublicKey)
- }
+ let mut validator_set = HashSet::::new();
+ // TODO: Transcripts are not hashable?
+ // let mut transcript_set = HashSet::>::new();
+ for (sender, transcript) in messages.iter() {
+ let sender = &sender.address;
+ if !self.validators.contains_key(sender) {
+ return Err(Error::UnknownDealer(sender.clone()));
+ } else if validator_set.contains(sender) {
+ return Err(Error::DuplicateDealer(sender.clone()));
+ // } else if !transcript_set.contains(transcript) {
+ // return Err(Error::DuplicateTranscript(sender.clone()));
+ } else if !transcript.verify_optimistic() {
+ return Err(Error::InvalidPvssTranscript(sender.clone()));
+ } else {
+ validator_set.insert(sender.clone());
+ // transcript_set.insert(sender.clone());
}
- _ => Err(Error::InvalidDkgStateToVerify),
}
- }
- /// 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,
- payload: &Message,
- ) -> Result<()> {
- match payload {
- Message::Deal(pvss)
- if matches!(
- self.state,
- DkgState::Sharing { .. } | DkgState::Dealt
- ) =>
- {
- if !self.validators.contains_key(&sender.address) {
- return Err(Error::UnknownDealer(sender.clone().address));
- }
-
- // TODO: Throw error instead of silently accepting excess shares?
- // if self.vss.len() < self.dkg_params.shares_num as usize {
- // self.vss.insert(sender.address.clone(), pvss.clone());
- // }
- self.vss.insert(sender.address.clone(), pvss.clone());
-
- // we keep track of the amount of shares seen until the security
- // threshold is met. Then we may change the state of the DKG
- if let DkgState::Sharing {
- ref mut accumulated_shares,
- ..
- } = &mut self.state
- {
- *accumulated_shares += 1;
- if *accumulated_shares >= self.dkg_params.security_threshold
- {
- self.state = DkgState::Dealt;
- }
- }
- Ok(())
- }
- Message::Aggregate(_) if matches!(self.state, DkgState::Dealt) => {
- // change state and cache the final key
- self.state = DkgState::Success {
- public_key: self.public_key(),
- };
- Ok(())
- }
- _ => Err(Error::InvalidDkgStateToIngest),
+ if validator_set.len() > self.validators.len() {
+ return Err(Error::TooManyTranscripts(
+ self.validators.len() as u32,
+ validator_set.len() as u32,
+ ));
}
- }
+ // if transcript_set.len() > self.validators.len() {
+ // return Err(Error::TooManyTranscripts(
+ // self.validators.len() as u32,
+ // transcript_set.len() as u32,
+ // ));
+ // }
- pub fn deal(
- &mut self,
- sender: &Validator,
- pvss: &PubliclyVerifiableSS,
- ) -> Result<()> {
- // Add the ephemeral public key and pvss transcript
- let (sender_address, _) = self
- .validators
- .iter()
- .find(|(probe_address, _)| sender.address == **probe_address)
- .ok_or_else(|| Error::UnknownDealer(sender.address.clone()))?;
- self.vss.insert(sender_address.clone(), pvss.clone());
Ok(())
}
}
-#[serde_as]
-#[derive(Serialize, Deserialize, Clone, Debug)]
-#[serde(bound(
- serialize = "AggregatedPvss: Serialize",
- deserialize = "AggregatedPvss: DeserializeOwned"
-))]
-pub struct Aggregation {
- vss: AggregatedPvss,
- #[serde_as(as = "ferveo_common::serialization::SerdeAs")]
- public_key: E::G1Affine,
-}
-
-// TODO: Remove these?
-// TODO: These messages are not actually used anywhere, we use our own ValidatorMessage for Deal, and Aggregate for Message.Aggregate
-#[derive(Serialize, Deserialize, Clone, Debug)]
-#[serde(bound(
- serialize = "AggregatedPvss: Serialize, PubliclyVerifiableSS: Serialize",
- deserialize = "AggregatedPvss: DeserializeOwned, PubliclyVerifiableSS: DeserializeOwned"
-))]
-pub enum Message {
- Deal(PubliclyVerifiableSS),
- Aggregate(Aggregation),
-}
-
/// Test initializing DKG
#[cfg(test)]
mod test_dkg_init {
@@ -399,12 +260,8 @@ mod test_dkg_init {
/// Test the dealing phase of the DKG
#[cfg(test)]
mod test_dealing {
- use ark_ec::AffineRepr;
- use ferveo_tdec::PublicKeyShare;
-
use crate::{
- test_common::*, DkgParams, DkgState, DkgState::Dealt, Error,
- PubliclyVerifiableDkg, Validator,
+ test_common::*, DkgParams, Error, PubliclyVerifiableDkg, Validator,
};
/// Check that the canonical share indices of validators are expected and enforced
@@ -434,67 +291,25 @@ mod test_dealing {
);
}
- /// Test that dealing correct PVSS transcripts
- /// pass verification an application and that
- /// state is updated correctly
+ /// Test that dealing correct PVSS transcripts passes validation
#[test]
fn test_pvss_dealing() {
let rng = &mut ark_std::test_rng();
+ let (dkg, _) = setup_dkg(0);
+ let messages = make_messages(rng, &dkg);
- // Create a test DKG instance
- let (mut dkg, _) = setup_dkg(0);
-
- // Gather everyone's transcripts
- let mut messages = vec![];
- for i in 0..dkg.dkg_params.shares_num() {
- let (mut dkg, _) = setup_dkg(i as usize);
- let message = dkg.share(rng).unwrap();
- let sender = dkg.me.clone();
- messages.push((sender, message));
- }
-
- let mut expected = 0u32;
- for (sender, pvss) in messages.iter() {
- // Check the verification passes
- assert!(dkg.verify_message(sender, pvss).is_ok());
-
- // Check that application passes
- assert!(dkg.apply_message(sender, pvss).is_ok());
-
- expected += 1;
- if expected < dkg.dkg_params.security_threshold {
- // check that shares accumulates correctly
- match dkg.state {
- DkgState::Sharing {
- accumulated_shares, ..
- } => {
- assert_eq!(accumulated_shares, expected)
- }
- _ => panic!("Test failed"),
- }
- } else {
- // Check that when enough shares is accumulated, we transition state
- assert!(matches!(dkg.state, DkgState::Dealt));
- }
- }
+ assert!(dkg.verify_transcripts(&messages).is_ok());
}
- /// Test the verification and application of
- /// pvss transcripts from unknown validators
- /// are rejected
+ /// Test the verification and application of pvss transcripts from
+ /// unknown validators are rejected
#[test]
fn test_pvss_from_unknown_dealer_rejected() {
let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 0,
- block: 0
- }
- ));
- let pvss = dkg.share(rng).unwrap();
- // Need to make sure this falls outside of the validator set:
+ let (dkg, _) = setup_dkg(0);
+ let mut messages = make_messages(rng, &dkg);
+
+ // Need to make sure this falls outside the validator set:
let unknown_validator_index =
dkg.dkg_params.shares_num + VALIDATORS_NUM + 1;
let sender = Validator:: {
@@ -502,255 +317,75 @@ mod test_dealing {
public_key: ferveo_common::Keypair::::new(rng).public_key(),
share_index: unknown_validator_index,
};
- // check that verification fails
- assert!(dkg.verify_message(&sender, &pvss).is_err());
- // check that application fails
- assert!(dkg.apply_message(&sender, &pvss).is_err());
- // check that state has not changed
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- }
- ));
+ let transcript = dkg.generate_transcript(rng).unwrap();
+ messages.push((sender, transcript));
+
+ assert!(dkg.verify_transcripts(&messages).is_err());
}
- /// Test that if a validator sends two pvss transcripts,
- /// the second fails to verify
+ /// Test that if a validator sends two pvss transcripts, the second fails to verify
#[test]
fn test_pvss_sent_twice_rejected() {
let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
- // We start with an empty state
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- }
- ));
+ let (dkg, _) = setup_dkg(0);
+ let mut messages = make_messages(rng, &dkg);
- let pvss = dkg.share(rng).unwrap();
-
- // This validator has already sent a PVSS
- let sender = dkg.me.clone();
-
- // First PVSS is accepted
- assert!(dkg.verify_message(&sender, &pvss).is_ok());
- assert!(dkg.apply_message(&sender, &pvss).is_ok());
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 1,
- block: 0,
- }
- ));
+ messages.push(messages[0].clone());
- // Second PVSS is rejected
- assert!(dkg.verify_message(&sender, &pvss).is_err());
+ assert!(dkg.verify_transcripts(&messages).is_err());
}
- /// Test that if a validators tries to verify it's own
- /// share message, it passes
+ /// Test that if a validators tries to verify its own share message, it passes
#[test]
fn test_own_pvss() {
let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
- // We start with an empty state
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- }
- ));
-
- // Sender creates a PVSS transcript
- let pvss = dkg.share(rng).unwrap();
- // Note that state of DKG has not changed
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- }
- ));
-
- let sender = dkg.me.clone();
-
- // Sender verifies it's own PVSS transcript
- assert!(dkg.verify_message(&sender, &pvss).is_ok());
- assert!(dkg.apply_message(&sender, &pvss).is_ok());
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 1,
- block: 0,
- }
- ));
- }
-
- /// Test that the [`PubliclyVerifiableDkg::share`] method
- /// errors if its state is not [`DkgState::Shared{..} | Dkg::Dealt`]
- #[test]
- fn test_pvss_cannot_share_from_wrong_state() {
- let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- }
- ));
-
- dkg.state = DkgState::Success {
- public_key: PublicKeyShare {
- public_key_share: G1::zero(),
- },
- };
- assert!(dkg.share(rng).is_err());
-
- // check that even if security threshold is met, we can still share
- dkg.state = Dealt;
- assert!(dkg.share(rng).is_ok());
- }
-
- /// Check that share messages can only be
- /// verified or applied if the dkg is in
- /// state [`DkgState::Share{..} | DkgState::Dealt`]
- #[test]
- fn test_share_message_state_guards() {
- let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
- let pvss = dkg.share(rng).unwrap();
- assert!(matches!(
- dkg.state,
- DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- }
- ));
+ let (dkg, _) = setup_dkg(0);
+ let messages = make_messages(rng, &dkg)
+ .iter()
+ .take(1)
+ .cloned()
+ .collect::>();
- let sender = dkg.me.clone();
- dkg.state = DkgState::Success {
- public_key: PublicKeyShare {
- public_key_share: G1::zero(),
- },
- };
- assert!(dkg.verify_message(&sender, &pvss).is_err());
- assert!(dkg.apply_message(&sender, &pvss).is_err());
-
- // check that we can still accept pvss transcripts after meeting threshold
- dkg.state = Dealt;
- assert!(dkg.verify_message(&sender, &pvss).is_ok());
- assert!(dkg.apply_message(&sender, &pvss).is_ok());
- assert!(matches!(dkg.state, DkgState::Dealt))
+ assert!(dkg.verify_transcripts(&messages).is_ok());
}
}
/// Test aggregating transcripts into final key
#[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};
+ use crate::test_common::*;
/// 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")]
- fn test_aggregate(shares_num: u32, validators_num: u32) {
- let security_threshold = shares_num - 1;
- let (mut dkg, _) = setup_dealt_dkg_with_n_validators(
- security_threshold,
- shares_num,
- validators_num,
- );
- let aggregate_msg = dkg.aggregate().unwrap();
- if let Message::Aggregate(Aggregation { public_key, .. }) =
- &aggregate_msg
- {
- assert_eq!(public_key, &dkg.public_key().public_key_share);
- } else {
- panic!("Expected aggregate message")
- }
- let sender = dkg.me.clone();
- assert!(dkg.verify_message(&sender, &aggregate_msg).is_ok());
- assert!(dkg.apply_message(&sender, &aggregate_msg).is_ok());
- assert!(matches!(dkg.state, DkgState::Success { .. }));
- }
-
- /// Test that aggregate only succeeds if we are in the state [`DkgState::Dealt]
- #[test]
- fn test_aggregate_state_guards() {
- let (mut dkg, _) = setup_dealt_dkg();
- dkg.state = DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- };
- assert!(dkg.aggregate().is_err());
- dkg.state = DkgState::Success {
- public_key: PublicKeyShare {
- public_key_share: G1::zero(),
- },
- };
- assert!(dkg.aggregate().is_err());
- }
-
- /// Test that aggregate message fail to be verified or applied unless
- /// dkg.state is [`DkgState::Dealt`]
- #[test]
- fn test_aggregate_message_state_guards() {
- let (mut dkg, _) = setup_dealt_dkg();
- let aggregate = dkg.aggregate().unwrap();
- let sender = dkg.me.clone();
-
- dkg.state = DkgState::Sharing {
- accumulated_shares: 0,
- block: 0,
- };
- assert!(dkg.verify_message(&sender, &aggregate).is_err());
- assert!(dkg.apply_message(&sender, &aggregate).is_err());
-
- dkg.state = DkgState::Success {
- public_key: PublicKeyShare {
- public_key_share: G1::zero(),
- },
- };
- assert!(dkg.verify_message(&sender, &aggregate).is_err());
- assert!(dkg.apply_message(&sender, &aggregate).is_err())
- }
+ fn test_aggregate(shares_num: u32, _validators_num: u32) {
+ let _security_threshold = shares_num - 1;
+ let rng = &mut ark_std::test_rng();
+ let (dkg, _) = setup_dkg(0);
+ let all_messages = make_messages(rng, &dkg);
- /// Test that an aggregate message will fail to verify if the
- /// security threshold is not met
- #[test]
- fn test_aggregate_wont_verify_if_under_threshold() {
- let (mut dkg, _) = setup_dealt_dkg();
- dkg.dkg_params.shares_num = 10;
- let aggregate = dkg.aggregate().unwrap();
- let sender = dkg.me.clone();
- assert!(dkg.verify_message(&sender, &aggregate).is_err());
- }
+ let not_enough_messages = all_messages
+ .iter()
+ .take((dkg.dkg_params.security_threshold - 1) as usize)
+ .cloned()
+ .collect::>();
+ let bad_aggregate =
+ dkg.aggregate_transcripts(¬_enough_messages).unwrap();
- /// If the aggregated pvss passes, check that the announced
- /// key is correct. Verification should fail if it is not
- #[test]
- fn test_aggregate_wont_verify_if_wrong_key() {
- let (dkg, _) = setup_dealt_dkg();
- let mut aggregate = dkg.aggregate().unwrap();
- while dkg.public_key().public_key_share == G1::zero() {
- let (_dkg, _) = setup_dealt_dkg();
- }
- if let Message::Aggregate(Aggregation { public_key, .. }) =
- &mut aggregate
- {
- *public_key = G1::zero();
- }
- let sender = dkg.me.clone();
- assert!(dkg.verify_message(&sender, &aggregate).is_err());
+ let enough_messages = all_messages
+ .iter()
+ .take((dkg.dkg_params.security_threshold) as usize)
+ .cloned()
+ .collect::>();
+ let good_aggregate_1 =
+ dkg.aggregate_transcripts(&enough_messages).unwrap();
+ assert_ne!(bad_aggregate, good_aggregate_1);
+
+ let good_aggregate_2 =
+ dkg.aggregate_transcripts(&all_messages).unwrap();
+ assert_ne!(good_aggregate_1, good_aggregate_2);
}
}
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index ed39fc4b..94128a01 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -59,8 +59,8 @@ pub enum Error {
DuplicateDealer(EthereumAddress),
/// DKG received an invalid transcript for which optimistic verification failed
- #[error("DKG received an invalid transcript")]
- InvalidPvssTranscript,
+ #[error("DKG received an invalid transcript from validator: {0}")]
+ InvalidPvssTranscript(EthereumAddress),
/// Aggregation failed because the DKG did not receive enough PVSS transcripts
#[error(
@@ -125,6 +125,14 @@ pub enum Error {
/// Validator not found in the DKG set of validators
#[error("Validator not found: {0}")]
UnknownValidator(EthereumAddress),
+
+ /// Too many transcripts received by the DKG
+ #[error("Too many transcripts. Expected: {0}, got: {1}")]
+ TooManyTranscripts(u32, u32),
+ // TODO: Transcripts are not hashable?
+ // /// Received a duplicated transcript from a validator
+ // #[error("Received a duplicated transcript from validator: {0}")]
+ // DuplicateTranscript(EthereumAddress),
}
pub type Result = std::result::Result;
@@ -156,13 +164,14 @@ mod test_dkg_full {
ciphertext_header: &ferveo_tdec::CiphertextHeader,
validator_keypairs: &[Keypair],
) -> (
- PubliclyVerifiableSS,
+ AggregatedTranscript,
Vec>,
SharedSecret,
) {
- let pvss_list = dkg.vss.values().cloned().collect::>();
- let pvss_aggregated = aggregate(&pvss_list).unwrap();
- assert!(pvss_aggregated.verify_aggregation(dkg).is_ok());
+ let transcripts = dkg.vss.values().cloned().collect::>();
+ let pvss_aggregated =
+ AggregatedTranscript::from_transcripts(&transcripts).unwrap();
+ assert!(pvss_aggregated.aggregate.verify_aggregation(dkg).is_ok());
let decryption_shares: Vec> =
validator_keypairs
@@ -172,6 +181,7 @@ mod test_dkg_full {
.get_validator(&validator_keypair.public_key())
.unwrap();
pvss_aggregated
+ .aggregate
.create_decryption_share_simple(
ciphertext_header,
aad,
@@ -213,7 +223,10 @@ mod test_dkg_full {
validators_num,
);
- let public_key = dkg.public_key();
+ let transcripts = dkg.vss.values().cloned().collect::>();
+ let public_key = AggregatedTranscript::from_transcripts(&transcripts)
+ .unwrap()
+ .public_key;
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
@@ -252,7 +265,11 @@ mod test_dkg_full {
shares_num,
validators_num,
);
- let public_key = dkg.public_key();
+ let transcripts = dkg.vss.values().cloned().collect::>();
+ let pvss_aggregated =
+ AggregatedTranscript::from_transcripts(&transcripts).unwrap();
+ pvss_aggregated.aggregate.verify_aggregation(&dkg).unwrap();
+ let public_key = pvss_aggregated.public_key;
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
@@ -261,9 +278,6 @@ mod test_dkg_full {
)
.unwrap();
- let pvss_list = dkg.vss.values().cloned().collect::>();
- let pvss_aggregated = aggregate(&pvss_list).unwrap();
- pvss_aggregated.verify_aggregation(&dkg).unwrap();
let domain_points = dkg
.domain
.elements()
@@ -278,6 +292,7 @@ mod test_dkg_full {
.get_validator(&validator_keypair.public_key())
.unwrap();
pvss_aggregated
+ .aggregate
.create_decryption_share_simple_precomputed(
&ciphertext.header().unwrap(),
AAD,
@@ -321,7 +336,10 @@ mod test_dkg_full {
shares_num,
validators_num,
);
- let public_key = dkg.public_key();
+ let transcripts = dkg.vss.values().cloned().collect::>();
+ let public_key = AggregatedTranscript::from_transcripts(&transcripts)
+ .unwrap()
+ .public_key;
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
@@ -339,7 +357,7 @@ mod test_dkg_full {
);
izip!(
- &pvss_aggregated.shares,
+ &pvss_aggregated.aggregate.shares,
&validator_keypairs,
&decryption_shares,
)
@@ -361,7 +379,7 @@ mod test_dkg_full {
let mut with_bad_decryption_share = decryption_share.clone();
with_bad_decryption_share.decryption_share = TargetField::zero();
assert!(!with_bad_decryption_share.verify(
- &pvss_aggregated.shares[0],
+ &pvss_aggregated.aggregate.shares[0],
&validator_keypairs[0].public_key().encryption_key,
&dkg.pvss_params.h,
&ciphertext,
@@ -371,7 +389,7 @@ mod test_dkg_full {
let mut with_bad_checksum = decryption_share;
with_bad_checksum.validator_checksum.checksum = G1Affine::zero();
assert!(!with_bad_checksum.verify(
- &pvss_aggregated.shares[0],
+ &pvss_aggregated.aggregate.shares[0],
&validator_keypairs[0].public_key().encryption_key,
&dkg.pvss_params.h,
&ciphertext,
@@ -393,11 +411,14 @@ mod test_dkg_full {
shares_num,
validators_num,
);
- let public_key = &dkg.public_key();
+ let transcripts = dkg.vss.values().cloned().collect::>();
+ let public_key = AggregatedTranscript::from_transcripts(&transcripts)
+ .unwrap()
+ .public_key;
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
- public_key,
+ &public_key,
rng,
)
.unwrap();
@@ -469,10 +490,9 @@ mod test_dkg_full {
.unwrap();
// Creates updated private key shares
- // 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
+ AggregatedTranscript::from_transcripts(&transcripts)
+ .unwrap()
+ .aggregate
.create_updated_private_key_share(
validator_keypair,
validator.share_index,
@@ -496,11 +516,9 @@ mod test_dkg_full {
.iter()
.enumerate()
.map(|(share_index, validator_keypair)| {
- // 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
+ AggregatedTranscript::from_transcripts(&transcripts)
+ .unwrap()
+ .aggregate
.create_decryption_share_simple(
&ciphertext.header().unwrap(),
AAD,
@@ -563,11 +581,14 @@ mod test_dkg_full {
shares_num,
validators_num,
);
- let public_key = &dkg.public_key();
+ let transcripts = dkg.vss.values().cloned().collect::>();
+ let public_key = AggregatedTranscript::from_transcripts(&transcripts)
+ .unwrap()
+ .public_key;
let ciphertext = ferveo_tdec::encrypt::(
SecretBox::new(MSG.to_vec()),
AAD,
- public_key,
+ &public_key,
rng,
)
.unwrap();
@@ -619,10 +640,9 @@ mod test_dkg_full {
.unwrap();
// Creates updated private key shares
- // 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
+ AggregatedTranscript::from_transcripts(&transcripts)
+ .unwrap()
+ .aggregate
.create_updated_private_key_share(
validator_keypair,
validator.share_index,
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 44ef5dc6..4409fb72 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -1,4 +1,4 @@
-use std::{marker::PhantomData, ops::Mul};
+use std::{hash::Hash, marker::PhantomData, ops::Mul};
use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group};
use ark_ff::{Field, Zero};
@@ -12,7 +12,7 @@ use ferveo_tdec::{
};
use itertools::Itertools;
use rand::RngCore;
-use serde::{Deserialize, Serialize};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_with::serde_as;
use subproductdomain::fast_multiexp;
use zeroize::{self, Zeroize, ZeroizeOnDrop};
@@ -28,7 +28,7 @@ use crate::{
pub type ShareEncryptions = ::G2Affine;
/// Marker struct for unaggregated PVSS transcripts
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Unaggregated;
/// Marker struct for aggregated PVSS transcripts
@@ -100,9 +100,8 @@ impl Drop for SecretPolynomial {
impl ZeroizeOnDrop for SecretPolynomial {}
-/// Each validator posts a transcript to the chain. Once enough
-/// validators have done this (their total voting power exceeds
-/// 2/3 the total), this will be aggregated into a final key
+/// Each validator posts a transcript to the chain. Once enough (threshold) validators have done,
+/// these will be aggregated into a final key
#[serde_as]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PubliclyVerifiableSS {
@@ -206,7 +205,7 @@ impl PubliclyVerifiableSS {
/// If aggregation fails, a validator needs to know that their pvss
/// transcript was at fault so that the can issue a new one. This
/// function may also be used for that purpose.
- pub fn verify_full(&self, dkg: &PubliclyVerifiableDkg) -> bool {
+ pub fn verify_full(&self, dkg: &PubliclyVerifiableDkg) -> Result {
let validators = dkg.validators.values().cloned().collect::>();
do_verify_full(
&self.coeffs,
@@ -225,14 +224,14 @@ pub fn do_verify_full(
pvss_params: &PubliclyVerifiableParams,
validators: &[Validator],
domain: &ark_poly::GeneralEvaluationDomain,
-) -> bool {
+) -> Result {
+ assert_no_share_duplicates(validators)?;
+
let mut commitment = batch_to_projective_g1::(pvss_coefficients);
domain.fft_in_place(&mut commitment);
- assert_no_share_duplicates(validators).expect("Validators must be unique");
-
// Each validator checks that their share is correct
- validators
+ Ok(validators
.iter()
.zip(pvss_encrypted_shares.iter())
.enumerate()
@@ -247,7 +246,7 @@ pub fn do_verify_full(
// See #4 in 4.2.3 section of https://eprint.iacr.org/2022/898.pdf
// e(G,Y) = e(A, ek)
E::pairing(pvss_params.g, *y_i) == E::pairing(a_i, ek_i)
- })
+ }))
}
pub fn do_verify_aggregation(
@@ -264,7 +263,7 @@ pub fn do_verify_aggregation(
pvss_params,
validators,
domain,
- );
+ )?;
if !is_valid {
return Err(Error::InvalidTranscriptAggregate);
}
@@ -318,9 +317,9 @@ impl PubliclyVerifiableSS {
),
)
.into_affine();
- Ok(PrivateKeyShare(ferveo_tdec::PrivateKeyShare {
+ Ok(PrivateKeyShare(ferveo_tdec::PrivateKeyShare(
private_key_share,
- }))
+ )))
}
/// Make a decryption share (simple variant) for a given ciphertext
@@ -380,13 +379,45 @@ impl PubliclyVerifiableSS {
}
}
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+pub struct AggregatedTranscript {
+ #[serde(bound(
+ serialize = "PubliclyVerifiableSS: Serialize",
+ deserialize = "PubliclyVerifiableSS: DeserializeOwned"
+ ))]
+ pub aggregate: PubliclyVerifiableSS,
+ #[serde(bound(
+ serialize = "ferveo_tdec::PublicKeyShare: Serialize",
+ deserialize = "ferveo_tdec::PublicKeyShare: DeserializeOwned"
+ ))]
+ pub public_key: ferveo_tdec::PublicKeyShare,
+}
+
+impl AggregatedTranscript {
+ pub fn from_transcripts(
+ transcripts: &[PubliclyVerifiableSS],
+ ) -> Result {
+ let aggregate = aggregate(transcripts)?;
+ let public_key = transcripts
+ .iter()
+ .map(|pvss| pvss.coeffs[0].into_group())
+ .sum::()
+ .into_affine();
+ let public_key = ferveo_tdec::PublicKeyShare::(public_key);
+ Ok(AggregatedTranscript {
+ aggregate,
+ public_key,
+ })
+ }
+}
+
/// Aggregate the PVSS instances in `pvss` from DKG session `dkg`
/// into a new PVSS instance
/// See: https://nikkolasg.github.io/ferveo/pvss.html?highlight=aggregate#aggregation
-pub(crate) fn aggregate(
- pvss_list: &[PubliclyVerifiableSS],
+fn aggregate(
+ transcripts: &[PubliclyVerifiableSS],
) -> Result> {
- let mut pvss_iter = pvss_list.iter();
+ let mut pvss_iter = transcripts.iter();
let first_pvss = pvss_iter
.next()
.ok_or_else(|| Error::NoTranscriptsToAggregate)?;
@@ -429,6 +460,22 @@ mod test_pvss {
use super::*;
use crate::test_common::*;
+ /// Test that an aggregate message will fail to verify if the
+ /// security threshold is not met
+ #[test]
+ fn test_aggregate_wont_verify_if_under_threshold() {
+ let (_dkg, _) = setup_dealt_dkg_with_n_transcript_dealt(
+ SECURITY_THRESHOLD,
+ SHARES_NUM,
+ VALIDATORS_NUM,
+ SECURITY_THRESHOLD - 1,
+ );
+ // TODO: Fix after rewriting dkg.vss
+ // let messages = dkg.vss.iter().map(|(v, t)| (v.clone(), t.clone())).collect::>();
+ // let aggregate = dkg.aggregate_transcripts(&messages).unwrap();
+ // assert!(aggregate.aggregate.verify_aggregation(&dkg).unwrap());
+ }
+
/// 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")]
@@ -454,12 +501,12 @@ mod test_pvss {
);
// Check that the correct number of shares were created
assert_eq!(pvss.shares.len(), dkg.validators.len());
- // Check that the prove of knowledge is correct
+ // Check that the proof of knowledge is correct
assert_eq!(pvss.sigma, G2::generator().mul(s));
// Check that the optimistic verify returns true
assert!(pvss.verify_optimistic());
// Check that the full verify returns true
- assert!(pvss.verify_full(&dkg));
+ assert!(pvss.verify_full(&dkg).unwrap());
}
/// Check that if the proof of knowledge is wrong,
@@ -492,7 +539,7 @@ mod test_pvss {
// So far, everything works
assert!(pvss.verify_optimistic());
- assert!(pvss.verify_full(&dkg));
+ assert!(pvss.verify_full(&dkg).unwrap());
// Now, we're going to tamper with the PVSS shares
let mut bad_pvss = pvss;
@@ -501,7 +548,7 @@ mod test_pvss {
// Optimistic verification should not catch this issue
assert!(bad_pvss.verify_optimistic());
// Full verification should catch this issue
- assert!(!bad_pvss.verify_full(&dkg));
+ assert!(!bad_pvss.verify_full(&dkg).unwrap());
}
/// Check that happy flow of aggregating PVSS transcripts
@@ -527,9 +574,9 @@ mod test_pvss {
// Check that the optimistic verify returns true
assert!(aggregate.verify_optimistic());
// Check that the full verify returns true
- assert!(aggregate.verify_full(&dkg));
+ assert!(aggregate.verify_full(&dkg).unwrap());
// Check that the verification of aggregation passes
- assert!(aggregate.verify_aggregation(&dkg).expect("Test failed"),);
+ assert!(aggregate.verify_aggregation(&dkg).expect("Test failed"));
}
/// Check that if the aggregated PVSS transcript has an
diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs
index 87797a9b..619eff1c 100644
--- a/ferveo/src/refresh.rs
+++ b/ferveo/src/refresh.rs
@@ -37,12 +37,8 @@ impl PrivateKeyShare {
) -> 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,
- };
+ .fold(self.0 .0, |acc, delta| (acc + delta.inner().0).into());
+ let updated_key_share = ferveo_tdec::PrivateKeyShare(updated_key_share);
UpdatedPrivateKeyShare(updated_key_share)
}
@@ -56,11 +52,9 @@ impl 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));
+ .map(|(y_j, l)| y_j.0 .0.mul(l));
let y_r = prods.fold(E::G2::zero(), |acc, y_j| acc + y_j);
- PrivateKeyShare(ferveo_tdec::PrivateKeyShare {
- private_key_share: y_r.into_affine(),
- })
+ PrivateKeyShare(ferveo_tdec::PrivateKeyShare(y_r.into_affine()))
}
pub fn create_decryption_share_simple(
@@ -226,9 +220,7 @@ fn prepare_share_updates_with_root(
let eval = d_i.evaluate(x_i);
h.mul(eval).into_affine()
})
- .map(|p| InnerPrivateKeyShare {
- private_key_share: p,
- })
+ .map(ferveo_tdec::PrivateKeyShare)
.collect()
}
diff --git a/ferveo/src/test_common.rs b/ferveo/src/test_common.rs
index dce10e5d..d947317c 100644
--- a/ferveo/src/test_common.rs
+++ b/ferveo/src/test_common.rs
@@ -1,12 +1,16 @@
/// Factory functions and variables for testing
use std::str::FromStr;
+use ark_bls12_381::Bls12_381;
pub use ark_bls12_381::Bls12_381 as E;
use ark_ec::pairing::Pairing;
use ferveo_common::Keypair;
-use rand::seq::SliceRandom;
+use rand::{seq::SliceRandom, Rng};
-use crate::{DkgParams, EthereumAddress, PubliclyVerifiableDkg, Validator};
+use crate::{
+ DkgParams, EthereumAddress, PubliclyVerifiableDkg, PubliclyVerifiableSS,
+ Validator,
+};
pub type ScalarField = ::ScalarField;
pub type G1 = ::G1Affine;
@@ -80,6 +84,8 @@ pub fn setup_dealt_dkg() -> TestSetup {
setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM)
}
+// TODO: Rewrite setup_utils to return messages separately
+
pub fn setup_dealt_dkg_with(
security_threshold: u32,
shares_num: u32,
@@ -95,21 +101,49 @@ pub fn setup_dealt_dkg_with_n_validators(
security_threshold: u32,
shares_num: u32,
validators_num: u32,
+) -> TestSetup {
+ setup_dealt_dkg_with_n_transcript_dealt(
+ security_threshold,
+ shares_num,
+ validators_num,
+ security_threshold,
+ )
+}
+
+pub fn make_messages(
+ rng: &mut (impl Rng + Sized),
+ dkg: &PubliclyVerifiableDkg,
+) -> Vec<(Validator, PubliclyVerifiableSS)> {
+ let mut messages = vec![];
+ for i in 0..dkg.dkg_params.shares_num() {
+ let (dkg, _) = setup_dkg(i as usize);
+ let transcript = dkg.generate_transcript(rng).unwrap();
+ let sender = dkg.me.clone();
+ messages.push((sender, transcript));
+ }
+ messages
+}
+
+pub fn setup_dealt_dkg_with_n_transcript_dealt(
+ security_threshold: u32,
+ shares_num: u32,
+ validators_num: u32,
+ transcripts_to_use: u32,
) -> TestSetup {
let rng = &mut ark_std::test_rng();
// Gather everyone's transcripts
- let mut messages: Vec<_> = (0..validators_num)
+ let mut transcripts: Vec<_> = (0..validators_num)
.map(|my_index| {
- let (mut dkg, _) = setup_dkg_for_n_validators(
+ let (dkg, _) = setup_dkg_for_n_validators(
security_threshold,
shares_num,
my_index as usize,
validators_num,
);
let me = dkg.me.clone();
- let message = dkg.share(rng).unwrap();
- (me, message)
+ let transcript = dkg.generate_transcript(rng).unwrap();
+ (me, transcript)
})
.collect();
@@ -122,9 +156,15 @@ pub fn setup_dealt_dkg_with_n_validators(
);
// The ordering of messages should not matter
- messages.shuffle(rng);
- messages.iter().for_each(|(sender, message)| {
- dkg.apply_message(sender, message).expect("Setup failed");
- });
+ transcripts.shuffle(rng);
+ // Use only the first `transcripts_to_use` transcripts
+ transcripts
+ .iter()
+ .take(transcripts_to_use as usize)
+ .for_each(|(sender, message)| {
+ // TODO: How to do this in user-facing API?
+ // TODO: just return transcripts after getting rid of the dkg.vss
+ dkg.vss.insert(sender.address.clone(), message.clone());
+ });
(dkg, keypairs)
}
From c9f1adc19198464d99d5759391e6b967ab505a70 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Mon, 19 Feb 2024 19:55:53 +0100
Subject: [PATCH 2/6] feature!: remove state from dkg, part 2
---
ferveo-python/ferveo/__init__.py | 1 +
ferveo-python/ferveo/__init__.pyi | 3 +
ferveo/src/api.rs | 139 ++++++++++--------------------
ferveo/src/bindings_python.rs | 5 ++
ferveo/src/dkg.rs | 46 +++-------
ferveo/src/lib.rs | 99 ++++++++++++---------
ferveo/src/pvss.rs | 58 +++++++------
ferveo/src/refresh.rs | 6 +-
ferveo/src/test_common.rs | 33 ++++---
9 files changed, 178 insertions(+), 212 deletions(-)
diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py
index 5088675d..209741b2 100644
--- a/ferveo-python/ferveo/__init__.py
+++ b/ferveo-python/ferveo/__init__.py
@@ -41,4 +41,5 @@
InvalidAggregateVerificationParameters,
UnknownValidator,
TooManyTranscripts,
+ DuplicateTranscript,
)
diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi
index f4b5fd9f..e738f2ab 100644
--- a/ferveo-python/ferveo/__init__.pyi
+++ b/ferveo-python/ferveo/__init__.pyi
@@ -226,3 +226,6 @@ class UnknownValidator(Exception):
class TooManyTranscripts(Exception):
pass
+
+class DuplicateTranscript(Exception):
+ pass
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index 77372255..80c350ce 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -25,8 +25,8 @@ use crate::bindings_python;
use crate::bindings_wasm;
pub use crate::EthereumAddress;
use crate::{
- do_verify_aggregation, DomainPoint, Error, PVSSMap,
- PubliclyVerifiableParams, PubliclyVerifiableSS, Result,
+ do_verify_aggregation, DomainPoint, Error, PubliclyVerifiableParams,
+ PubliclyVerifiableSS, Result,
};
pub type PublicKey = ferveo_common::PublicKey;
@@ -66,12 +66,12 @@ pub fn decrypt_with_shared_secret(
aad: &[u8],
shared_secret: &SharedSecret,
) -> Result> {
- let dkg_public_params = DkgPublicParameters::default();
+ let g_inv = PubliclyVerifiableParams::::default().g_inv();
ferveo_tdec::api::decrypt_with_shared_secret(
&ciphertext.0,
aad,
&shared_secret.0,
- &dkg_public_params.g1_inv,
+ &g_inv,
)
.map_err(Error::from)
}
@@ -235,13 +235,6 @@ impl Dkg {
.map(AggregatedTranscript)
}
- // TODO: Unused?
- pub fn public_params(&self) -> DkgPublicParameters {
- DkgPublicParameters {
- g1_inv: self.0.pvss_params.g_inv(),
- }
- }
-
pub fn me(&self) -> &Validator {
&self.0.me
}
@@ -251,14 +244,6 @@ impl Dkg {
}
}
-fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap {
- let mut pvss_map: PVSSMap = PVSSMap::new();
- messages.iter().for_each(|(validator, transcript)| {
- pvss_map.insert(validator.address.clone(), transcript.clone());
- });
- pvss_map
-}
-
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AggregatedTranscript(crate::AggregatedTranscript