From 30a49313ad55f51c3f0548dcb51de27eccaf3162 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Tue, 16 Jan 2024 18:02:43 +0100
Subject: [PATCH] feat(dkg): relax dkg ceremony constraints
---
Cargo.lock | 34 +++
ferveo-python/test/test_ferveo.py | 73 +++++
ferveo-wasm/tests/node.rs | 27 +-
ferveo/Cargo.toml | 1 +
ferveo/benches/benchmarks/validity_checks.rs | 6 +-
ferveo/examples/bench_primitives_size.rs | 6 +-
ferveo/src/api.rs | 131 ++++++++-
ferveo/src/bindings_python.rs | 14 +-
ferveo/src/bindings_wasm.rs | 8 +-
ferveo/src/dkg.rs | 176 +++++++++---
ferveo/src/lib.rs | 271 +++++++++++--------
ferveo/src/pvss.rs | 262 +++++++++++-------
12 files changed, 711 insertions(+), 298 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 7aa7e272..685f931f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -786,6 +786,7 @@ dependencies = [
"serde",
"serde_with",
"subproductdomain-pre-release",
+ "test-case",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-derive",
@@ -1881,6 +1882,39 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "test-case"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
+dependencies = [
+ "test-case-macros",
+]
+
+[[package]]
+name = "test-case-core"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
+dependencies = [
+ "cfg-if",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "test-case-macros"
+version = "3.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+ "test-case-core",
+]
+
[[package]]
name = "textwrap"
version = "0.11.0"
diff --git a/ferveo-python/test/test_ferveo.py b/ferveo-python/test/test_ferveo.py
index 6f00b6df..c3874d40 100644
--- a/ferveo-python/test/test_ferveo.py
+++ b/ferveo-python/test/test_ferveo.py
@@ -135,6 +135,79 @@ def test_precomputed_tdec_doesnt_have_enough_messages():
FerveoVariant.Precomputed, shares_num=4, threshold=4, shares_to_use=3
)
+def test_dkg_has_min_shares():
+ total_shares_num = 5
+ min_shares_num = 3
+ threshold = 3
+
+ tau = 1
+ validator_keypairs = [Keypair.random() for _ in range(0, total_shares_num)]
+ validators = [
+ Validator(gen_eth_addr(i), keypair.public_key())
+ for i, keypair in enumerate(validator_keypairs)
+ ]
+ validators.sort(key=lambda v: v.address)
+
+ messages = []
+ for sender in validators:
+ dkg = Dkg(
+ tau=tau,
+ shares_num=min_shares_num,
+ security_threshold=threshold,
+ validators=validators,
+ me=sender,
+ )
+ messages.append(ValidatorMessage(sender, dkg.generate_transcript()))
+
+ dkg = Dkg(
+ tau=tau,
+ shares_num=min_shares_num,
+ security_threshold=threshold,
+ validators=validators,
+ me=validators[0],
+ )
+ pvss_aggregated = dkg.aggregate_transcripts(messages)
+ assert pvss_aggregated.verify(min_shares_num, messages)
+
+ dkg_pk_bytes = bytes(dkg.public_key)
+ dkg_pk = DkgPublicKey.from_bytes(dkg_pk_bytes)
+
+ msg = "abc".encode()
+ aad = "my-aad".encode()
+ ciphertext = encrypt(msg, aad, dkg_pk)
+
+ decryption_shares = []
+ for validator, validator_keypair in zip(validators, validator_keypairs):
+ dkg = Dkg(
+ tau=tau,
+ shares_num=total_shares_num,
+ security_threshold=threshold,
+ validators=validators,
+ me=validator,
+ )
+ pvss_aggregated = dkg.aggregate_transcripts(messages)
+ assert pvss_aggregated.verify(total_shares_num, messages)
+
+ decryption_share = decryption_share_for_variant(variant, pvss_aggregated)(
+ dkg, ciphertext.header, aad, validator_keypair
+ )
+ decryption_shares.append(decryption_share)
+
+ shared_secret = combine_shares_for_variant(variant, decryption_shares)
+
+ if variant == FerveoVariant.Simple and len(decryption_shares) < threshold:
+ with pytest.raises(ThresholdEncryptionError):
+ decrypt_with_shared_secret(ciphertext, aad, shared_secret)
+ return
+
+ if variant == FerveoVariant.Precomputed and len(decryption_shares) < total_shares_num:
+ with pytest.raises(ThresholdEncryptionError):
+ decrypt_with_shared_secret(ciphertext, aad, shared_secret)
+ return
+
+ plaintext = decrypt_with_shared_secret(ciphertext, aad, shared_secret)
+ assert bytes(plaintext) == msg
+
PARAMS = [
(1, FerveoVariant.Simple),
diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs
index b4234d07..4ac71429 100644
--- a/ferveo-wasm/tests/node.rs
+++ b/ferveo-wasm/tests/node.rs
@@ -8,8 +8,8 @@ use wasm_bindgen_test::*;
type TestSetup = (
u32,
- usize,
- usize,
+ u32,
+ u32,
Vec,
Vec,
ValidatorArray,
@@ -21,11 +21,12 @@ type TestSetup = (
fn setup_dkg() -> TestSetup {
let tau = 1;
- let shares_num = 16;
+ let shares_num: u32 = 16;
let security_threshold = shares_num * 2 / 3;
- let validator_keypairs =
- (0..shares_num).map(gen_keypair).collect::>();
+ let validator_keypairs = (0..shares_num as usize)
+ .map(gen_keypair)
+ .collect::>();
let validators = validator_keypairs
.iter()
.enumerate()
@@ -38,8 +39,8 @@ fn setup_dkg() -> TestSetup {
let messages = validators.iter().map(|sender| {
let dkg = Dkg::new(
tau,
- shares_num as u32,
- security_threshold as u32,
+ shares_num,
+ security_threshold,
&validators_js,
sender,
)
@@ -54,8 +55,8 @@ fn setup_dkg() -> TestSetup {
let mut dkg = Dkg::new(
tau,
- shares_num as u32,
- security_threshold as u32,
+ shares_num,
+ security_threshold,
&validators_js,
&validators[0],
)
@@ -112,8 +113,8 @@ fn tdec_simple() {
.map(|(validator, keypair)| {
let mut dkg = Dkg::new(
tau,
- shares_num as u32,
- security_threshold as u32,
+ shares_num,
+ security_threshold,
&validators_js,
&validator,
)
@@ -166,8 +167,8 @@ fn tdec_precomputed() {
.map(|(validator, keypair)| {
let mut dkg = Dkg::new(
tau,
- shares_num as u32,
- security_threshold as u32,
+ shares_num,
+ security_threshold,
&validators_js,
&validator,
)
diff --git a/ferveo/Cargo.toml b/ferveo/Cargo.toml
index c063c502..d2a0d0eb 100644
--- a/ferveo/Cargo.toml
+++ b/ferveo/Cargo.toml
@@ -51,6 +51,7 @@ wasm-bindgen-derive = { version = "0.2.1", optional = true }
criterion = "0.3" # supports pprof, # TODO: Figure out if/how we can update to 0.4
digest = { version = "0.10.0", features = ["alloc"] }
pprof = { version = "0.6", features = ["flamegraph", "criterion"] }
+test-case = "3.3.1"
# WASM bindings
console_error_panic_hook = "0.1.7"
diff --git a/ferveo/benches/benchmarks/validity_checks.rs b/ferveo/benches/benchmarks/validity_checks.rs
index a6dd9f48..cc7266f7 100644
--- a/ferveo/benches/benchmarks/validity_checks.rs
+++ b/ferveo/benches/benchmarks/validity_checks.rs
@@ -45,11 +45,7 @@ fn setup_dkg(
let me = validators[validator].clone();
PubliclyVerifiableDkg::new(
&validators,
- &DkgParams {
- tau: 0,
- security_threshold: shares_num / 3,
- shares_num,
- },
+ &DkgParams::new(0, shares_num / 3, shares_num).unwrap(),
&me,
)
.expect("Setup failed")
diff --git a/ferveo/examples/bench_primitives_size.rs b/ferveo/examples/bench_primitives_size.rs
index 18adf673..79afb8a4 100644
--- a/ferveo/examples/bench_primitives_size.rs
+++ b/ferveo/examples/bench_primitives_size.rs
@@ -80,11 +80,7 @@ fn setup_dkg(
let me = validators[validator].clone();
PubliclyVerifiableDkg::new(
&validators,
- &DkgParams {
- tau: 0,
- security_threshold,
- shares_num,
- },
+ &DkgParams::new(0, security_threshold, shares_num).unwrap(),
&me,
)
.expect("Setup failed")
diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs
index af3edcd4..c625eb53 100644
--- a/ferveo/src/api.rs
+++ b/ferveo/src/api.rs
@@ -26,8 +26,8 @@ use crate::bindings_python;
use crate::bindings_wasm;
pub use crate::EthereumAddress;
use crate::{
- do_verify_aggregation, Error, PVSSMap, PubliclyVerifiableParams,
- PubliclyVerifiableSS, Result,
+ do_verify_aggregation, DkgValidator, Error, PVSSMap,
+ PubliclyVerifiableParams, PubliclyVerifiableSS, Result, ValidatorsMap,
};
pub type DecryptionSharePrecomputed =
@@ -203,11 +203,8 @@ impl Dkg {
validators: &[Validator],
me: &Validator,
) -> Result {
- let dkg_params = crate::DkgParams {
- tau,
- security_threshold,
- shares_num,
- };
+ let dkg_params =
+ crate::DkgParams::new(tau, security_threshold, shares_num)?;
let dkg = crate::PubliclyVerifiableDkg::::new(
validators,
&dkg_params,
@@ -259,6 +256,24 @@ fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap {
pvss_map
}
+fn make_validator_map(messages: &[ValidatorMessage]) -> ValidatorsMap {
+ let mut validator_map = ValidatorsMap::new();
+ // TODO: Don't use enumerate here. Instead, add `share_index` to `ValidatorMessage`
+ messages.iter().enumerate().for_each(
+ |(share_index, (validator, _transcript))| {
+ let dkg_validator = DkgValidator {
+ share_index,
+ validator: Validator {
+ address: validator.address.clone(),
+ public_key: validator.public_key,
+ },
+ };
+ validator_map.insert(validator.address.clone(), dkg_validator);
+ },
+ );
+ validator_map
+}
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AggregatedTranscript(PubliclyVerifiableSS);
@@ -282,12 +297,10 @@ impl AggregatedTranscript {
return Err(Error::InvalidTranscriptAggregate);
}
+ // TODO: Can we simplify this? Do we need another map?
+ // TODO: See if do_verify_aggregation can be simplified in terms of parameters
let pvss_map = make_pvss_map(messages);
- let validators: Vec<_> = messages
- .iter()
- .map(|(validator, _)| validator)
- .cloned()
- .collect();
+ let validators = make_validator_map(messages);
// This check also includes `verify_full`. See impl. for details.
let is_valid = do_verify_aggregation(
@@ -312,7 +325,7 @@ impl AggregatedTranscript {
.0
.domain
.elements()
- .take(dkg.0.dkg_params.shares_num as usize)
+ .take(dkg.0.dkg_params.shares_num() as usize)
.collect();
self.0.make_decryption_share_simple_precomputed(
&ciphertext_header.0,
@@ -539,6 +552,7 @@ mod test_ferveo_api {
}
#[test]
+
fn test_server_api_tdec_simple() {
let rng = &mut StdRng::seed_from_u64(0);
@@ -627,6 +641,95 @@ mod test_ferveo_api {
}
}
+ #[test]
+ fn test_server_api_tdec_simple_with_min_shares() {
+ let rng = &mut StdRng::seed_from_u64(0);
+
+ // Works for both power of 2 and non-power of 2
+ for shares_num in [4, 7] {
+ let tau = 1;
+ let security_threshold = shares_num - 1;
+
+ let (messages, validators, validator_keypairs) =
+ make_test_inputs(rng, tau, security_threshold, shares_num);
+
+ // 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(
+ tau,
+ shares_num,
+ security_threshold,
+ &validators,
+ &validators[0],
+ )
+ .unwrap();
+
+ let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap();
+ assert!(pvss_aggregated.verify(shares_num, &messages).unwrap());
+
+ // At this point, any given validator should be able to provide a DKG public key
+ let public_key = dkg.public_key();
+
+ // In the meantime, the client creates a ciphertext and decryption request
+ let msg = "my-msg".as_bytes().to_vec();
+ let aad: &[u8] = "my-aad".as_bytes();
+ let ciphertext =
+ encrypt(SecretBox::new(msg.clone()), 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(shares_num, &messages)
+ .unwrap());
+ aggregate
+ .create_decryption_share_simple(
+ &dkg,
+ &ciphertext.header().unwrap(),
+ aad,
+ validator_keypair,
+ )
+ .unwrap()
+ })
+ .collect();
+
+ // Now, the decryption share can be used to decrypt the ciphertext
+ // This part is part of the client API
+
+ // In simple variant, we only need `security_threshold` shares to be able to decrypt
+ let decryption_shares =
+ decryption_shares[..security_threshold as usize].to_vec();
+
+ let shared_secret = combine_shares_simple(&decryption_shares);
+ let plaintext =
+ decrypt_with_shared_secret(&ciphertext, aad, &shared_secret)
+ .unwrap();
+ assert_eq!(plaintext, msg);
+
+ // Let's say that we've only received `security_threshold - 1` shares
+ // In this case, we should not be able to decrypt
+ let decryption_shares =
+ decryption_shares[..security_threshold as usize - 1].to_vec();
+
+ let shared_secret = combine_shares_simple(&decryption_shares);
+ let result =
+ decrypt_with_shared_secret(&ciphertext, aad, &shared_secret);
+ assert!(result.is_err());
+ }
+ }
+
#[test]
fn server_side_local_verification() {
let rng = &mut StdRng::seed_from_u64(0);
@@ -647,7 +750,7 @@ mod test_ferveo_api {
let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap();
assert!(local_aggregate
- .verify(dkg.0.dkg_params.shares_num, &messages)
+ .verify(dkg.0.dkg_params.shares_num(), &messages)
.is_ok());
}
diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs
index ed965f3e..00c455a3 100644
--- a/ferveo/src/bindings_python.rs
+++ b/ferveo/src/bindings_python.rs
@@ -93,7 +93,17 @@ impl From for PyErr {
}
Error::InvalidVariant(variant) => {
InvalidVariant::new_err(variant.to_string())
- }
+ },
+ Error::InvalidDkgParameters(num_shares, security_threshold) => {
+ InvalidDkgParameters::new_err(format!(
+ "num_shares: {num_shares}, security_threshold: {security_threshold}"
+ ))
+ },
+ Error::InvalidShareIndex(index) => {
+ InvalidShareIndex::new_err(format!(
+ "{index}"
+ ))
+ },
},
_ => default(),
}
@@ -128,6 +138,8 @@ create_exception!(exceptions, ValidatorPublicKeyMismatch, PyValueError);
create_exception!(exceptions, SerializationError, PyValueError);
create_exception!(exceptions, InvalidByteLength, PyValueError);
create_exception!(exceptions, InvalidVariant, PyValueError);
+create_exception!(exceptions, InvalidDkgParameters, PyValueError);
+create_exception!(exceptions, InvalidShareIndex, PyValueError);
fn from_py_bytes(bytes: &[u8]) -> PyResult {
T::from_bytes(bytes)
diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs
index e412b6e0..5b03f188 100644
--- a/ferveo/src/bindings_wasm.rs
+++ b/ferveo/src/bindings_wasm.rs
@@ -510,15 +510,13 @@ impl AggregatedTranscript {
#[wasm_bindgen]
pub fn verify(
&self,
- shares_num: usize,
+ shares_num: u32,
messages: &ValidatorMessageArray,
) -> JsResult {
set_panic_hook();
let messages = unwrap_messages_js(messages)?;
- let is_valid = self
- .0
- .verify(shares_num as u32, &messages)
- .map_err(map_js_err)?;
+ let is_valid =
+ self.0.verify(shares_num, &messages).map_err(map_js_err)?;
Ok(is_valid)
}
diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs
index 3f7fc09d..760a6c57 100644
--- a/ferveo/src/dkg.rs
+++ b/ferveo/src/dkg.rs
@@ -15,9 +15,47 @@ use crate::{
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct DkgParams {
- pub tau: u32,
- pub security_threshold: u32,
- pub shares_num: u32,
+ tau: u32,
+ security_threshold: u32,
+ shares_num: u32,
+}
+
+impl DkgParams {
+ /// Create new DKG parameters
+ /// `tau` is a unique identifier for the DKG (ritual id)
+ /// `security_threshold` is the minimum number of shares required to reconstruct the key
+ /// `shares_num` is the total number of shares to be generated
+ /// Returns an error if the parameters are invalid
+ /// Parameters must hold: `shares_num` >= `security_threshold`
+ pub fn new(
+ tau: u32,
+ security_threshold: u32,
+ shares_num: u32,
+ ) -> Result {
+ if shares_num < security_threshold {
+ return Err(Error::InvalidDkgParameters(
+ shares_num,
+ security_threshold,
+ ));
+ }
+ Ok(Self {
+ tau,
+ security_threshold,
+ shares_num,
+ })
+ }
+
+ pub fn tau(&self) -> u32 {
+ self.tau
+ }
+
+ pub fn security_threshold(&self) -> u32 {
+ self.security_threshold
+ }
+
+ pub fn shares_num(&self) -> u32 {
+ self.shares_num
+ }
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
@@ -77,14 +115,15 @@ impl PubliclyVerifiableDkg {
me: &Validator,
) -> Result {
let domain = ark_poly::GeneralEvaluationDomain::::new(
- dkg_params.shares_num as usize,
+ validators.len(),
)
.expect("unable to construct domain");
- // Sort the validators to verify a global ordering
+ // Verify the global ordering of validators
if !is_sorted(validators) {
return Err(Error::ValidatorsNotSorted);
}
+ // TODO: Should we take it from the coordinator instead of using validators: &[Validator] as input?
let validators: ValidatorsMap = validators
.iter()
.enumerate()
@@ -123,6 +162,7 @@ impl PubliclyVerifiableDkg {
validators,
state: DkgState::Sharing {
accumulated_shares: 0,
+ // TODO: Do we need to keep track of the block number?
block: 0,
},
})
@@ -151,6 +191,7 @@ impl PubliclyVerifiableDkg {
}
}
+ // TODO: Make private, use `share` instead. Currently used only in bindings
pub fn create_share(
&self,
rng: &mut R,
@@ -248,6 +289,10 @@ impl PubliclyVerifiableDkg {
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
@@ -321,6 +366,7 @@ pub(crate) mod test_common {
pub use ark_bls12_381::Bls12_381 as E;
use ferveo_common::Keypair;
+ use rand::seq::SliceRandom;
pub use super::*;
@@ -348,22 +394,32 @@ pub(crate) mod test_common {
pub type TestSetup = (PubliclyVerifiableDkg, Vec>);
- pub fn setup_dkg_for_n_validators(
+ pub fn setup_dkg_for_me(
+ security_threshold: u32,
+ shares_num: u32,
+ my_index: usize,
+ ) -> TestSetup {
+ setup_dkg_for_me_with_n_validators(
+ security_threshold,
+ shares_num,
+ my_index,
+ shares_num,
+ )
+ }
+
+ pub fn setup_dkg_for_me_with_n_validators(
security_threshold: u32,
shares_num: u32,
my_index: usize,
+ n_validators: u32,
) -> TestSetup {
- let keypairs = gen_keypairs(shares_num);
+ let keypairs = gen_keypairs(n_validators);
let mut validators = gen_validators(keypairs.as_slice());
validators.sort();
let me = validators[my_index].clone();
let dkg = PubliclyVerifiableDkg::new(
&validators,
- &DkgParams {
- tau: 0,
- security_threshold,
- shares_num,
- },
+ &DkgParams::new(0, security_threshold, shares_num).unwrap(),
&me,
)
.expect("Setup failed");
@@ -373,44 +429,56 @@ pub(crate) mod test_common {
/// Create a test dkg
///
/// The [`test_dkg_init`] module checks correctness of this setup
- pub fn setup_dkg(validator: usize) -> TestSetup {
- setup_dkg_for_n_validators(2, 4, validator)
+ pub fn setup_dkg() -> TestSetup {
+ setup_dkg_for_me(2, 4, 0)
}
/// Set up a dkg with enough pvss transcripts to meet the threshold
///
/// The correctness of this function is tested in the module [`test_dealing`]
pub fn setup_dealt_dkg() -> TestSetup {
- setup_dealt_dkg_with_n_validators(2, 4)
+ setup_dealt_dkg_with_n_validators(2, 4, 4)
}
pub fn setup_dealt_dkg_with_n_validators(
security_threshold: u32,
shares_num: u32,
+ validators_num: u32,
) -> TestSetup {
let rng = &mut ark_std::test_rng();
// Gather everyone's transcripts
- let messages: Vec<_> = (0..shares_num)
+ let mut dkgs = Vec::new();
+ let mut messages: Vec<_> = (0..validators_num)
.map(|my_index| {
- let (mut dkg, _) = setup_dkg_for_n_validators(
+ let (mut dkg, _) = setup_dkg_for_me_with_n_validators(
security_threshold,
shares_num,
my_index as usize,
+ validators_num,
);
+ dkgs.push(dkg.clone());
+
let me = dkg.me.validator.clone();
let message = dkg.share(rng).unwrap();
+ println!("{my_index}");
(me, message)
})
.collect();
// Create a test DKG instance
- let (mut dkg, keypairs) =
- setup_dkg_for_n_validators(security_threshold, shares_num, 0);
- messages.iter().for_each(|(sender, message)| {
- dkg.apply_message(sender, message).expect("Setup failed");
- });
- (dkg, keypairs)
+ let mut dkg = dkgs.pop().unwrap();
+
+ // `shares_num` is either equal or lower than `validators_num`, so we always take `shares_num` messages
+ assert!(shares_num <= validators_num);
+ // Make sure messages are out of order - The ordering should not matter
+ messages.shuffle(rng);
+ messages.iter().take(shares_num as usize).for_each(
+ |(sender, message)| {
+ dkg.apply_message(sender, message).expect("Setup failed");
+ },
+ );
+ (dkg, gen_keypairs(validators_num))
}
}
@@ -450,29 +518,41 @@ mod test_dkg_init {
#[cfg(test)]
mod test_dealing {
use ark_ec::AffineRepr;
+ use test_case::test_case;
use super::test_common::*;
use crate::DkgState::Dealt;
- /// Test that dealing correct PVSS transcripts
- /// pass verification an application and that
+ /// Test that dealing correct PVSS transcripts pass verification an application and that
/// state is updated correctly
- #[test]
- fn test_pvss_dealing() {
- let rng = &mut ark_std::test_rng();
+ #[test_case(4, 4 ; "number of shares is equal to number of validators")]
+ #[test_case(4, 6 ; "number of shares is smaller than the number of validators")]
+ fn test_pvss_dealing(shares_num: u32, validator_num: u32) {
+ let threshold = 3;
+ // Create a test DKG instance
+ let (mut dkg, _) = setup_dkg_for_me_with_n_validators(
+ threshold,
+ shares_num,
+ 0,
+ validator_num,
+ );
// Gather everyone's transcripts
let mut messages = vec![];
- for i in 0..4 {
- let (mut dkg, _) = setup_dkg(i);
+ let rng = &mut ark_std::test_rng();
+ // Notice how we use `shares_num` instead of `validator_num` - Not everyone will send a message
+ for i in 0..shares_num {
+ let (mut dkg, _) = setup_dkg_for_me_with_n_validators(
+ threshold,
+ shares_num,
+ i as usize,
+ validator_num,
+ );
let message = dkg.share(rng).unwrap();
let sender = dkg.me.validator.clone();
messages.push((sender, message));
}
- // Create a test DKG instance
- let (mut dkg, _) = setup_dkg(0);
-
let mut expected = 0u32;
for (sender, pvss) in messages.iter() {
// Check the verification passes
@@ -483,7 +563,7 @@ mod test_dealing {
expected += 1;
if expected < dkg.dkg_params.security_threshold {
- // check that shares accumulates correctly
+ // Check that shares accumulates correctly
match dkg.state {
DkgState::Sharing {
accumulated_shares, ..
@@ -505,7 +585,7 @@ mod test_dealing {
#[test]
fn test_pvss_from_unknown_dealer_rejected() {
let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
+ let (mut dkg, _) = setup_dkg();
assert!(matches!(
dkg.state,
DkgState::Sharing {
@@ -538,7 +618,7 @@ mod test_dealing {
#[test]
fn test_pvss_sent_twice_rejected() {
let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
+ let (mut dkg, _) = setup_dkg();
// We start with an empty state
assert!(matches!(
dkg.state,
@@ -573,7 +653,7 @@ mod test_dealing {
#[test]
fn test_own_pvss() {
let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
+ let (mut dkg, _) = setup_dkg();
// We start with an empty state
assert!(matches!(
dkg.state,
@@ -613,7 +693,7 @@ mod test_dealing {
#[test]
fn test_pvss_cannot_share_from_wrong_state() {
let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
+ let (mut dkg, _) = setup_dkg();
assert!(matches!(
dkg.state,
DkgState::Sharing {
@@ -638,7 +718,7 @@ mod test_dealing {
#[test]
fn test_share_message_state_guards() {
let rng = &mut ark_std::test_rng();
- let (mut dkg, _) = setup_dkg(0);
+ let (mut dkg, _) = setup_dkg();
let pvss = dkg.share(rng).unwrap();
assert!(matches!(
dkg.state,
@@ -751,3 +831,21 @@ mod test_aggregation {
assert!(dkg.verify_message(&sender, &aggregate).is_err());
}
}
+
+/// Test DKG parameters
+#[cfg(test)]
+mod test_dkg_params {
+ const TAU: u32 = 0;
+
+ #[test]
+ fn test_shares_num_less_than_security_threshold() {
+ let dkg_params = super::DkgParams::new(TAU, 4, 3);
+ assert!(dkg_params.is_err());
+ }
+
+ #[test]
+ fn test_valid_dkg_params() {
+ let dkg_params = super::DkgParams::new(TAU, 2, 3);
+ assert!(dkg_params.is_ok());
+ }
+}
diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs
index 59a44024..72a9a183 100644
--- a/ferveo/src/lib.rs
+++ b/ferveo/src/lib.rs
@@ -101,6 +101,12 @@ pub enum Error {
#[error("Invalid variant: {0}")]
InvalidVariant(String),
+
+ #[error("Invalid DKG parameters: number of shares {0}, threshold {1}")]
+ InvalidDkgParameters(u32, u32),
+
+ #[error("Invalid share index: {0}")]
+ InvalidShareIndex(u32),
}
pub type Result = std::result::Result;
@@ -131,6 +137,7 @@ mod test_dkg_full {
SharedSecret,
};
use itertools::izip;
+ use test_case::test_case;
use super::*;
use crate::dkg::test_common::*;
@@ -189,114 +196,125 @@ mod test_dkg_full {
(pvss_aggregated, decryption_shares, shared_secret)
}
- #[test]
- fn test_dkg_simple_tdec() {
+ #[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, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_dkg_simple_tdec(shares_num: u32, validator_num: u32) {
let rng = &mut test_rng();
- // Works for both power of 2 and non-power of 2
- for shares_num in [4, 7] {
- let threshold = shares_num / 2 + 1;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(threshold, shares_num);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
- let public_key = dkg.public_key();
- let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg.clone()),
- aad,
- &public_key,
- rng,
- )
- .unwrap();
+ let threshold = shares_num / 2 + 1;
+ let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(
+ threshold,
+ shares_num,
+ validator_num,
+ );
+ let msg = "my-msg".as_bytes().to_vec();
+ let aad: &[u8] = "my-aad".as_bytes();
+ let public_key = dkg.public_key();
+ let ciphertext = ferveo_tdec::encrypt::(
+ SecretBox::new(msg.clone()),
+ aad,
+ &public_key,
+ rng,
+ )
+ .unwrap();
- let (_, _, shared_secret) = make_shared_secret_simple_tdec(
- &dkg,
- aad,
- &ciphertext.header().unwrap(),
- validator_keypairs.as_slice(),
- );
+ let (_, _, shared_secret) = make_shared_secret_simple_tdec(
+ &dkg,
+ aad,
+ &ciphertext.header().unwrap(),
+ validator_keypairs.as_slice(),
+ );
- let plaintext = ferveo_tdec::decrypt_with_shared_secret(
- &ciphertext,
- aad,
- &shared_secret,
- &dkg.pvss_params.g_inv(),
- )
- .unwrap();
- assert_eq!(plaintext, msg);
- }
+ let plaintext = ferveo_tdec::decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &shared_secret,
+ &dkg.pvss_params.g_inv(),
+ )
+ .unwrap();
+ assert_eq!(plaintext, msg);
}
- #[test]
- fn test_dkg_simple_tdec_precomputed() {
+ #[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, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_dkg_simple_tdec_precomputed(shares_num: u32, validators_num: u32) {
let rng = &mut test_rng();
- // Works for both power of 2 and non-power of 2
- for shares_num in [4, 7] {
- // In precomputed variant, threshold must be equal to shares_num
- let threshold = shares_num;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(threshold, shares_num);
- let msg = "my-msg".as_bytes().to_vec();
- let aad: &[u8] = "my-aad".as_bytes();
- let public_key = dkg.public_key();
- let ciphertext = ferveo_tdec::encrypt::(
- SecretBox::new(msg.clone()),
- aad,
- &public_key,
- rng,
- )
- .unwrap();
+ // In precomputed variant, threshold must be equal to shares_num
+ let threshold = shares_num;
+ let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(
+ threshold,
+ shares_num,
+ validators_num,
+ );
+ let msg = "my-msg".as_bytes().to_vec();
+ let aad: &[u8] = "my-aad".as_bytes();
+ let public_key = dkg.public_key();
+ let ciphertext = ferveo_tdec::encrypt::(
+ SecretBox::new(msg.clone()),
+ aad,
+ &public_key,
+ rng,
+ )
+ .unwrap();
- let pvss_aggregated = aggregate(&dkg.vss);
- pvss_aggregated.verify_aggregation(&dkg).unwrap();
- let domain_points = dkg
- .domain
- .elements()
- .take(validator_keypairs.len())
- .collect::>();
-
- let decryption_shares: Vec> =
- validator_keypairs
- .iter()
- .map(|validator_keypair| {
- let validator = dkg
- .get_validator(&validator_keypair.public_key())
- .unwrap();
- pvss_aggregated
- .make_decryption_share_simple_precomputed(
- &ciphertext.header().unwrap(),
- aad,
- &validator_keypair.decryption_key,
- validator.share_index,
- &domain_points,
- &dkg.pvss_params.g_inv(),
- )
- .unwrap()
- })
- .collect();
- assert_eq!(domain_points.len(), decryption_shares.len());
+ let pvss_aggregated = aggregate(&dkg.vss);
+ pvss_aggregated.verify_aggregation(&dkg).unwrap();
+ let domain_points = dkg
+ .domain
+ .elements()
+ .take(validator_keypairs.len())
+ .collect::>();
+
+ let decryption_shares: Vec> =
+ validator_keypairs
+ .iter()
+ .map(|validator_keypair| {
+ let validator = dkg
+ .get_validator(&validator_keypair.public_key())
+ .unwrap();
+ pvss_aggregated
+ .make_decryption_share_simple_precomputed(
+ &ciphertext.header().unwrap(),
+ aad,
+ &validator_keypair.decryption_key,
+ validator.share_index,
+ &domain_points,
+ &dkg.pvss_params.g_inv(),
+ )
+ .unwrap()
+ })
+ .collect();
+ assert_eq!(domain_points.len(), decryption_shares.len());
- let shared_secret =
- ferveo_tdec::share_combine_precomputed::(&decryption_shares);
+ let shared_secret =
+ ferveo_tdec::share_combine_precomputed::(&decryption_shares);
- // Combination works, let's decrypt
- let plaintext = ferveo_tdec::decrypt_with_shared_secret(
- &ciphertext,
- aad,
- &shared_secret,
- &dkg.pvss_params.g_inv(),
- )
- .unwrap();
- assert_eq!(plaintext, msg);
- }
+ // Combination works, let's decrypt
+ let plaintext = ferveo_tdec::decrypt_with_shared_secret(
+ &ciphertext,
+ aad,
+ &shared_secret,
+ &dkg.pvss_params.g_inv(),
+ )
+ .unwrap();
+ assert_eq!(plaintext, msg);
}
- #[test]
- fn test_dkg_simple_tdec_share_verification() {
+ #[test_case(4, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_dkg_simple_tdec_share_verification(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
let rng = &mut test_rng();
- let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4);
+ let (dkg, validator_keypairs) =
+ setup_dealt_dkg_with_n_validators(2, shares_num, validators_num);
let msg = "my-msg".as_bytes().to_vec();
let aad: &[u8] = "my-aad".as_bytes();
let public_key = dkg.public_key();
@@ -322,7 +340,7 @@ mod test_dkg_full {
&decryption_shares,
)
.for_each(
- |(aggregated_share, validator_keypair, decryption_share)| {
+ |((_, aggregated_share), validator_keypair, decryption_share)| {
assert!(decryption_share.verify(
aggregated_share,
&validator_keypair.public_key().encryption_key,
@@ -339,7 +357,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.shares[&0],
&validator_keypairs[0].public_key().encryption_key,
&dkg.pvss_params.h,
&ciphertext,
@@ -349,21 +367,28 @@ 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.shares[&0],
&validator_keypairs[0].public_key().encryption_key,
&dkg.pvss_params.h,
&ciphertext,
));
}
- #[test]
- fn test_dkg_simple_tdec_share_recovery() {
+ #[test_case(4, 4; "number of shares is equal to number of validators")]
+ // TODO: Doesn't work - Should it work? Or is a case that we don't care about?
+ // #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_dkg_simple_tdec_share_recovery(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
let rng = &mut test_rng();
let security_threshold = 3;
- let shares_num = 4;
- let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(security_threshold, shares_num);
+ let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(
+ security_threshold,
+ shares_num,
+ validators_num,
+ );
let msg = "my-msg".as_bytes().to_vec();
let aad: &[u8] = "my-aad".as_bytes();
let public_key = &dkg.public_key();
@@ -410,7 +435,7 @@ mod test_dkg_full {
&domain_points,
&dkg.pvss_params.h.into_affine(),
&x_r,
- dkg.dkg_params.security_threshold as usize,
+ dkg.dkg_params.security_threshold() as usize,
rng,
);
(v_addr.clone(), deltas_i)
@@ -439,11 +464,13 @@ mod test_dkg_full {
// Creates updated private key shares
// TODO: Why not using dkg.aggregate()?
let pvss_aggregated = aggregate(&dkg.vss);
- pvss_aggregated.update_private_key_share_for_recovery(
- &decryption_key,
- validator.share_index,
- updates_for_participant.as_slice(),
- )
+ pvss_aggregated
+ .update_private_key_share_for_recovery(
+ &decryption_key,
+ validator.share_index,
+ updates_for_participant.as_slice(),
+ )
+ .unwrap()
})
.collect();
@@ -515,14 +542,16 @@ mod test_dkg_full {
);
}
- #[test]
- fn test_dkg_simple_tdec_share_refreshing() {
+ #[test_case(4, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_dkg_simple_tdec_share_refreshing(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
let rng = &mut test_rng();
- let security_threshold = 3;
- let shares_num = 4;
let (dkg, validator_keypairs) =
- setup_dealt_dkg_with_n_validators(security_threshold, shares_num);
+ setup_dealt_dkg_with_n_validators(2, shares_num, validators_num);
let msg = "my-msg".as_bytes().to_vec();
let aad: &[u8] = "my-aad".as_bytes();
let public_key = &dkg.public_key();
@@ -552,7 +581,7 @@ mod test_dkg_full {
let deltas_i = prepare_share_updates_for_refresh::(
&domain_points,
&dkg.pvss_params.h.into_affine(),
- dkg.dkg_params.security_threshold as usize,
+ dkg.dkg_params.security_threshold() as usize,
rng,
);
(v_addr.clone(), deltas_i)
@@ -582,11 +611,13 @@ mod test_dkg_full {
// Creates updated private key shares
// TODO: Why not using dkg.aggregate()?
let pvss_aggregated = aggregate(&dkg.vss);
- pvss_aggregated.update_private_key_share_for_recovery(
- &decryption_key,
- validator.share_index,
- updates_for_participant.as_slice(),
- )
+ pvss_aggregated
+ .update_private_key_share_for_recovery(
+ &decryption_key,
+ validator.share_index,
+ updates_for_participant.as_slice(),
+ )
+ .unwrap()
})
.collect();
@@ -608,10 +639,10 @@ mod test_dkg_full {
.collect();
let lagrange = ferveo_tdec::prepare_combine_simple::(
- &domain_points[..security_threshold as usize],
+ &domain_points[..dkg.dkg_params.security_threshold() as usize],
);
let new_shared_secret = ferveo_tdec::share_combine_simple::(
- &decryption_shares[..security_threshold as usize],
+ &decryption_shares[..dkg.dkg_params.security_threshold() as usize],
&lagrange,
);
diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs
index 4f63da82..c5b6f2de 100644
--- a/ferveo/src/pvss.rs
+++ b/ferveo/src/pvss.rs
@@ -19,12 +19,15 @@ use zeroize::{self, Zeroize, ZeroizeOnDrop};
use crate::{
apply_updates_to_private_share, batch_to_projective_g1,
- batch_to_projective_g2, utils::is_sorted, Error, PVSSMap,
- PubliclyVerifiableDkg, Result, Validator,
+ batch_to_projective_g2, Error, PVSSMap, PubliclyVerifiableDkg, Result,
+ ValidatorsMap,
};
/// These are the blinded evaluations of shares of a single random polynomial
-pub type ShareEncryptions = ::G2Affine;
+pub type ShareEncryption = ::G2Affine;
+
+pub type ShareEncryptionMap =
+ std::collections::BTreeMap>;
/// Marker struct for unaggregated PVSS transcripts
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
@@ -115,7 +118,7 @@ pub struct PubliclyVerifiableSS {
/// 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
- pub shares: Vec,
+ pub shares: ShareEncryptionMap,
/// Proof of Knowledge
#[serde_as(as = "ferveo_common::serialization::SerdeAs")]
@@ -138,7 +141,7 @@ impl PubliclyVerifiableSS {
) -> Result {
let phi = SecretPolynomial::::new(
s,
- (dkg.dkg_params.security_threshold - 1) as usize,
+ (dkg.dkg_params.security_threshold() - 1) as usize,
rng,
);
@@ -151,17 +154,23 @@ impl PubliclyVerifiableSS {
.values()
.map(|validator| {
// ek_{i}^{eval_i}, i = validator index
- fast_multiexp(
+ let share_encryption = fast_multiexp(
// &evals.evals[i..i] = &evals.evals[i]
&[evals.evals[validator.share_index]], // one share per validator
validator.validator.public_key.encryption_key.into_group(),
- )[0]
+ )[0];
+ println!(
+ "share_index: {}, share_encryption: {}",
+ validator.share_index, share_encryption
+ );
+ (validator.share_index, share_encryption)
})
- .collect::>>();
- if shares.len() != dkg.validators.len() {
+ .collect::>();
+
+ if shares.len() < dkg.dkg_params.shares_num() as usize {
return Err(Error::InsufficientValidators(
shares.len() as u32,
- dkg.validators.len() as u32,
+ dkg.dkg_params.shares_num(),
));
}
@@ -201,17 +210,11 @@ impl PubliclyVerifiableSS {
/// 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 {
- let validators = dkg
- .validators
- .values()
- .map(|v| v.validator.clone())
- .collect::>();
- let validators = validators.as_slice();
do_verify_full(
&self.coeffs,
&self.shares,
&dkg.pvss_params,
- validators,
+ &dkg.validators,
&dkg.domain,
)
}
@@ -220,58 +223,67 @@ impl PubliclyVerifiableSS {
// TODO: Return validator that failed the check
pub fn do_verify_full(
pvss_coefficients: &[E::G1Affine],
- pvss_encrypted_shares: &[E::G2Affine],
+ pvss_share_map: &ShareEncryptionMap,
pvss_params: &PubliclyVerifiableParams,
- validators: &[Validator],
+ validator_map: &ValidatorsMap,
domain: &ark_poly::GeneralEvaluationDomain,
) -> bool {
let mut commitment = batch_to_projective_g1::(pvss_coefficients);
domain.fft_in_place(&mut commitment);
- // At this point, validators must be sorted
- assert!(is_sorted(validators));
+ for validator in validator_map.values() {
+ println!("validator.share_index: {}", validator.share_index);
+ }
+
+ println!("pvss_encrypted_shares.len(): {}", pvss_share_map.len());
+ println!("validator_map.len(): {}", validator_map.len());
// Each validator checks that their share is correct
- validators
- .iter()
- .zip(pvss_encrypted_shares.iter())
- .enumerate()
- .all(|(share_index, (validator, y_i))| {
- // TODO: Check #3 is missing
- // See #3 in 4.2.3 section of https://eprint.iacr.org/2022/898.pdf
-
- // Validator checks aggregated shares against commitment
- let ek_i = validator.public_key.encryption_key.into_group();
- let a_i = &commitment[share_index];
- // We verify that e(G, Y_i) = e(A_i, ek_i) for validator i
- // 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)
- })
+ validator_map.values().all(|validator| {
+ // TODO: Check #3 is missing
+ // See #3 in 4.2.3 section of https://eprint.iacr.org/2022/898.pdf
+
+ // Validator checks aggregated shares against commitment
+ let ek_i = validator.validator.public_key.encryption_key.into_group();
+ let a_i = &commitment[validator.share_index];
+ let y_i = &pvss_share_map[&validator.share_index];
+ println!("share index: {}", validator.share_index);
+ println!("ek_i: {ek_i}");
+ println!("a_i: {a_i}");
+ println!("y_i: {y_i}");
+
+ // We verify that e(G, Y_i) = e(A_i, ek_i) for validator i
+ // See #4 in 4.2.3 section of https://eprint.iacr.org/2022/898.pdf
+ // e(G,Y) = e(A, ek)
+ let result = E::pairing(pvss_params.g, *y_i) == E::pairing(a_i, ek_i);
+ println!("result: {result}");
+ result
+ })
}
pub fn do_verify_aggregation(
pvss_agg_coefficients: &[E::G1Affine],
- pvss_agg_encrypted_shares: &[E::G2Affine],
+ share_encryption_map: &ShareEncryptionMap,
pvss_params: &PubliclyVerifiableParams,
- validators: &[Validator],
+ validator_map: &ValidatorsMap,
domain: &ark_poly::GeneralEvaluationDomain,
- vss: &PVSSMap,
+ pvss_map: &PVSSMap,
) -> Result {
let is_valid = do_verify_full(
pvss_agg_coefficients,
- pvss_agg_encrypted_shares,
+ share_encryption_map,
pvss_params,
- validators,
+ validator_map,
domain,
);
if !is_valid {
+ // TODO: Throws here
return Err(Error::InvalidTranscriptAggregate);
}
// Now, we verify that the aggregated PVSS transcript is a valid aggregation
let mut y = E::G1::zero();
- for (_, pvss) in vss.iter() {
+ for (_, pvss) in pvss_map.iter() {
y += pvss.coeffs[0].into_group();
}
if y.into_affine() == pvss_agg_coefficients[0] {
@@ -291,17 +303,11 @@ impl PubliclyVerifiableSS {
&self,
dkg: &PubliclyVerifiableDkg,
) -> Result {
- let validators = dkg
- .validators
- .values()
- .map(|v| v.validator.clone())
- .collect::>();
- let validators = validators.as_slice();
do_verify_aggregation(
&self.coeffs,
&self.shares,
&dkg.pvss_params,
- validators,
+ &dkg.validators,
&dkg.domain,
&dkg.vss,
)
@@ -311,19 +317,19 @@ impl PubliclyVerifiableSS {
&self,
validator_decryption_key: &E::ScalarField,
share_index: usize,
- ) -> PrivateKeyShare {
+ ) -> Result> {
// Decrypt private key shares https://nikkolasg.github.io/ferveo/pvss.html#validator-decryption-of-private-key-shares
let private_key_share = self
.shares
- .get(share_index)
- .unwrap()
+ .get(&share_index)
+ .ok_or(Error::InvalidShareIndex(share_index as u32))?
.mul(
validator_decryption_key
.inverse()
.expect("Validator decryption key must have an inverse"),
)
.into_affine();
- PrivateKeyShare { private_key_share }
+ Ok(PrivateKeyShare { private_key_share })
}
pub fn make_decryption_share_simple(
@@ -335,7 +341,7 @@ impl PubliclyVerifiableSS {
g_inv: &E::G1Prepared,
) -> Result> {
let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index);
+ .decrypt_private_key_share(validator_decryption_key, share_index)?;
DecryptionShareSimple::create(
validator_decryption_key,
&private_key_share,
@@ -356,7 +362,7 @@ impl PubliclyVerifiableSS {
g_inv: &E::G1Prepared,
) -> Result> {
let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index);
+ .decrypt_private_key_share(validator_decryption_key, share_index)?;
// We use the `prepare_combine_simple` function to precompute the lagrange coefficients
let lagrange_coeffs = prepare_combine_simple::(domain_points);
@@ -379,13 +385,16 @@ impl PubliclyVerifiableSS {
validator_decryption_key: &E::ScalarField,
share_index: usize,
share_updates: &[E::G2],
- ) -> PrivateKeyShare {
+ ) -> Result> {
// Retrieves their private key share
let private_key_share = self
- .decrypt_private_key_share(validator_decryption_key, share_index);
+ .decrypt_private_key_share(validator_decryption_key, share_index)?;
// And updates their share
- apply_updates_to_private_share::(&private_key_share, share_updates)
+ Ok(apply_updates_to_private_share::(
+ &private_key_share,
+ share_updates,
+ ))
}
}
@@ -402,7 +411,13 @@ pub fn aggregate(
let mut coeffs = batch_to_projective_g1::(&first_pvss.coeffs);
let mut sigma = first_pvss.sigma;
- let mut shares = batch_to_projective_g2::(&first_pvss.shares);
+ // Ordering of shares doesn't matter here, so we can just collect them from the map
+ let shares = first_pvss
+ .shares
+ .values()
+ .cloned()
+ .collect::>>();
+ let mut shares = batch_to_projective_g2::(&shares);
// So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their sigma
// sigma is the sum of all the sigma_i, which is the proof of knowledge of the secret polynomial
@@ -416,9 +431,16 @@ pub fn aggregate(
shares
.iter_mut()
.zip_eq(next.shares.iter())
- .for_each(|(a, b)| *a += b);
+ .for_each(|(a, (_, b))| *a += b);
}
let shares = E::G2::normalize_batch(&shares);
+ // TODO: There is just one share, but we need to convert it back to a map
+ // TODO: Find a better way to do this
+ let shares = shares
+ .into_iter()
+ .enumerate()
+ .map(|(i, share)| (i, share))
+ .collect::>();
PubliclyVerifiableSS {
coeffs: E::G1::normalize_batch(&coeffs),
@@ -430,18 +452,30 @@ pub fn aggregate(
pub fn aggregate_for_decryption(
dkg: &PubliclyVerifiableDkg,
-) -> Vec> {
+) -> Vec> {
// From docs: https://nikkolasg.github.io/ferveo/pvss.html?highlight=aggregate#aggregation
// "Two PVSS instances may be aggregated into a single PVSS instance by adding elementwise each of the corresponding group elements."
- let shares = dkg
+ let share_maps = dkg
.vss
.values()
.map(|pvss| pvss.shares.clone())
- .collect::>();
- let first_share = shares
- .first()
- .expect("Need one or more decryption shares to aggregate")
- .to_vec();
+ .collect::>>();
+
+ // Now, get shares from each map. Sort them by index, and then collect them into a vector
+ let shares = share_maps
+ .into_iter()
+ .map(|share_map| {
+ share_map
+ .into_iter()
+ .sorted_by_key(|(index, _)| *index)
+ .map(|(_, share)| share)
+ .collect::>>()
+ })
+ .collect::>>>();
+
+ // Now, we're going to add the shares together
+ let first_share = shares[0].clone();
+
shares
.into_iter()
.skip(1)
@@ -456,10 +490,11 @@ pub fn aggregate_for_decryption(
#[cfg(test)]
mod test_pvss {
- use ark_bls12_381::Bls12_381 as EllipticCurve;
+ use ark_bls12_381::{Bls12_381 as EllipticCurve, Bls12_381};
use ark_ec::AffineRepr;
use ark_ff::UniformRand;
use rand::seq::SliceRandom;
+ use test_case::test_case;
use super::*;
use crate::{dkg::test_common::*, utils::is_sorted};
@@ -468,12 +503,35 @@ mod test_pvss {
type G1 = ::G1Affine;
type G2 = ::G2Affine;
+ fn _setup_dkg(
+ shares_num: u32,
+ validators_num: u32,
+ ) -> PubliclyVerifiableDkg {
+ let (dkg, _) = setup_dkg_for_me_with_n_validators(
+ 2,
+ shares_num,
+ 0,
+ validators_num,
+ );
+ dkg
+ }
+
+ fn _setup_dealt_dkg(
+ shares_num: u32,
+ validators_num: u32,
+ ) -> PubliclyVerifiableDkg {
+ let (dkg, _) =
+ setup_dealt_dkg_with_n_validators(2, shares_num, validators_num);
+ dkg
+ }
+
/// Test the happy flow that a pvss with the correct form is created
/// and that appropriate validations pass
- #[test]
- fn test_new_pvss() {
+ #[test_case(4, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_new_pvss(shares_num: u32, validators_num: u32) {
let rng = &mut ark_std::test_rng();
- let (dkg, _) = setup_dkg(0);
+ let dkg = _setup_dkg(shares_num, validators_num);
let s = ScalarField::rand(rng);
let pvss = PubliclyVerifiableSS::::new(&s, &dkg, rng)
.expect("Test failed");
@@ -482,7 +540,7 @@ mod test_pvss {
// Check that a polynomial of the correct degree was created
assert_eq!(
pvss.coeffs.len(),
- dkg.dkg_params.security_threshold as usize
+ dkg.dkg_params.security_threshold() as usize
);
// Check that the correct number of shares were created
assert_eq!(pvss.shares.len(), dkg.validators.len());
@@ -496,10 +554,15 @@ mod test_pvss {
/// Check that if the proof of knowledge is wrong,
/// the optimistic verification of PVSS fails
- #[test]
- fn test_verify_pvss_wrong_proof_of_knowledge() {
+ #[test_case(4, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_verify_pvss_wrong_proof_of_knowledge(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
let rng = &mut ark_std::test_rng();
- let (dkg, _) = setup_dkg(0);
+ // Make sure it works for relaxed DKG ceremony constraints
+ let dkg = _setup_dkg(shares_num, validators_num);
let mut s = ScalarField::rand(rng);
// Ensure that the proof of knowledge is not zero
while s == ScalarField::zero() {
@@ -514,13 +577,20 @@ mod test_pvss {
}
/// Check that if PVSS shares are tampered with, the full verification fails
- #[test]
- fn test_verify_pvss_bad_shares() {
+ #[test_case(4, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_verify_pvss_bad_shares(shares_num: u32, validators_num: u32) {
let rng = &mut ark_std::test_rng();
- let (dkg, _) = setup_dkg(0);
- let s = ScalarField::rand(rng);
+ let keypairs = gen_keypairs(validators_num);
+ let mut validators = gen_validators(&keypairs);
+ validators.sort();
+ let _me = validators[0].clone();
+
+ let dkg = _setup_dkg(shares_num, validators_num);
+ let secret = ScalarField::rand(rng);
let pvss =
- PubliclyVerifiableSS::::new(&s, &dkg, rng).unwrap();
+ PubliclyVerifiableSS::::new(&secret, &dkg, rng)
+ .unwrap();
// So far, everything works
assert!(pvss.verify_optimistic());
@@ -528,7 +598,7 @@ mod test_pvss {
// Now, we're going to tamper with the PVSS shares
let mut bad_pvss = pvss;
- bad_pvss.shares[0] = G2::zero();
+ bad_pvss.shares.insert(0_usize, G2::zero());
// Optimistic verification should not catch this issue
assert!(bad_pvss.verify_optimistic());
@@ -543,7 +613,6 @@ mod test_pvss {
let rng = &mut ark_std::test_rng();
let shares_num = 4;
- let security_threshold = shares_num - 1;
let keypairs = gen_keypairs(shares_num);
let mut validators = gen_validators(&keypairs);
let me = validators[0].clone();
@@ -555,11 +624,7 @@ mod test_pvss {
// And because of that the DKG should fail
let result = PubliclyVerifiableDkg::new(
&validators,
- &DkgParams {
- tau: 0,
- security_threshold,
- shares_num,
- },
+ &DkgParams::new(0, 2, shares_num).unwrap(),
&me,
);
assert!(result.is_err());
@@ -571,14 +636,15 @@ mod test_pvss {
/// Check that happy flow of aggregating PVSS transcripts
/// Should have the correct form and validations pass
- #[test]
- fn test_aggregate_pvss() {
- let (dkg, _) = setup_dealt_dkg();
+ #[test_case(4, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_aggregate_pvss(shares_num: u32, validators_num: u32) {
+ let dkg = _setup_dealt_dkg(shares_num, validators_num);
let aggregate = aggregate(&dkg.vss);
// Check that a polynomial of the correct degree was created
assert_eq!(
aggregate.coeffs.len(),
- dkg.dkg_params.security_threshold as usize
+ dkg.dkg_params.security_threshold() as usize
);
// Check that the correct number of shares were created
assert_eq!(aggregate.shares.len(), dkg.validators.len());
@@ -592,12 +658,16 @@ mod test_pvss {
/// Check that if the aggregated pvss transcript has an
/// incorrect constant term, the verification fails
- #[test]
- fn test_verify_aggregation_fails_if_constant_term_wrong() {
- let (dkg, _) = setup_dealt_dkg();
+ #[test_case(4, 4; "number of shares is equal to number of validators")]
+ #[test_case(4, 6; "number of shares is smaller than the number of validators")]
+ fn test_verify_aggregation_fails_if_constant_term_wrong(
+ shares_num: u32,
+ validators_num: u32,
+ ) {
+ let dkg = _setup_dealt_dkg(shares_num, validators_num);
let mut aggregated = aggregate(&dkg.vss);
while aggregated.coeffs[0] == G1::zero() {
- let (dkg, _) = setup_dkg(0);
+ let (dkg, _) = setup_dkg();
aggregated = aggregate(&dkg.vss);
}
aggregated.coeffs[0] = G1::zero();