Skip to content

Commit 77a0b61

Browse files
authored
Merge pull request #516 from EspressoSystems/ak/revisit
Revisit dynamic committee design
2 parents ff17e9a + 5b48be9 commit 77a0b61

File tree

6 files changed

+117
-83
lines changed

6 files changed

+117
-83
lines changed

timeboost-crypto/src/feldman.rs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -302,51 +302,52 @@ impl<C: CurveGroup> KeyResharing<Self> for FeldmanVss<C> {
302302
old_pp: &FeldmanVssPublicParam<C>,
303303
new_pp: &FeldmanVssPublicParam<C>,
304304
recv_node_idx: usize,
305-
reshares: impl ExactSizeIterator<Item = (usize, C::ScalarField, FeldmanCommitment<C>)> + Clone,
305+
reshares: impl Iterator<Item = (usize, C::ScalarField, FeldmanCommitment<C>)>,
306306
) -> Result<(C::ScalarField, FeldmanCommitment<C>), VssError> {
307-
// input validation
308307
let n = old_pp.n.get();
309-
if reshares.len() == 0 {
310-
return Err(VssError::EmptyReshare);
311-
}
312-
for (idx, _, _) in reshares.clone() {
308+
309+
let mut eval_points = Vec::new();
310+
let mut recv_shares = Vec::new();
311+
let mut row_commitments = Vec::new();
312+
313+
for (idx, share, commitment) in reshares {
313314
if idx >= n {
314315
return Err(VssError::IndexOutOfBound(n - 1, idx));
315316
}
317+
eval_points.push(C::ScalarField::from(idx as u64 + 1));
318+
recv_shares.push(share);
319+
row_commitments.push(commitment);
320+
}
321+
322+
if eval_points.is_empty() {
323+
return Err(VssError::EmptyReshare);
316324
}
317325

318326
let new_n = new_pp.n.get();
319327
let new_t = new_pp.t.get();
320328
if recv_node_idx >= new_n {
321329
return Err(VssError::IndexOutOfBound(new_n - 1, recv_node_idx));
322330
}
323-
for (_, _, row_commitment) in reshares.clone() {
324-
if row_commitment.len() != new_t {
325-
return Err(VssError::InvalidCommitment);
326-
}
331+
332+
if row_commitments.iter().any(|c| c.len() != new_t) {
333+
return Err(VssError::InvalidCommitment);
327334
}
328335

329336
// interpolate reshares to get new secret share
330-
let eval_points: Vec<_> = reshares
331-
.clone()
332-
.map(|(idx, _, _)| C::ScalarField::from(idx as u64 + 1))
333-
.collect();
334-
let recv_reshares: Vec<_> = reshares.clone().map(|(_, share, _)| share).collect();
335-
let new_secret = interpolate::<C>(&eval_points, &recv_reshares)
337+
let new_secret = interpolate::<C>(&eval_points, &recv_shares)
336338
.map_err(|e| VssError::FailedCombine(e.to_string()))?;
337339

338340
// interpolate in the exponent to get new Feldman commitment
339-
let row_commitments: Vec<_> = reshares.map(|(_, _, commitment)| commitment).collect();
340341
let new_commitment = (0..new_t)
341342
.into_par_iter()
342343
.map(|j| {
343344
let j_th_coeffs: Vec<C::Affine> =
344345
row_commitments.iter().map(|row| row[j]).collect();
345346
interpolate_in_exponent::<C>(&eval_points, &j_th_coeffs)
346347
.map_err(|e| VssError::FailedCombine(e.to_string()))
348+
.map(|p| p.into_affine())
347349
})
348350
.collect::<Result<Vec<_>, VssError>>()?;
349-
let new_commitment = C::normalize_batch(&new_commitment);
350351

351352
Ok((new_secret, new_commitment.into()))
352353
}

timeboost-crypto/src/traits/dkg.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ pub trait KeyResharing<VSS: VerifiableSecretSharing> {
112112
old_pp: &VSS::PublicParam,
113113
new_pp: &VSS::PublicParam,
114114
recv_node_idx: usize,
115-
reshares: impl ExactSizeIterator<Item = (usize, VSS::SecretShare, VSS::Commitment)> + Clone,
115+
reshares: impl Iterator<Item = (usize, VSS::SecretShare, VSS::Commitment)>,
116116
) -> Result<(VSS::Secret, VSS::Commitment), VssError>;
117117
}
118118

timeboost-sequencer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ tracing = { workspace = true }
3333
ark-std = { workspace = true }
3434
bs58 = { workspace = true }
3535
futures = { workspace = true }
36-
test-utils = { path = "../test-utils", features = ["ports"]}
36+
test-utils = { path = "../test-utils", features = ["ports"] }
3737
timeboost-utils = { path = "../timeboost-utils" }

timeboost-sequencer/src/decrypt.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1+
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
2+
use std::result::Result as StdResult;
3+
use std::sync::Arc;
4+
15
use ark_std::{UniformRand, rand::thread_rng};
26
use bon::Builder;
37
use bytes::{BufMut, Bytes, BytesMut};
48
use cliquenet::overlay::{Data, DataError, NetworkDown, Overlay};
59
use cliquenet::{
610
AddressableCommittee, MAX_MESSAGE_SIZE, Network, NetworkError, NetworkMetrics, Role,
711
};
8-
use rayon::iter::ParallelIterator;
9-
use rayon::prelude::*;
10-
1112
use multisig::{Committee, CommitteeId, PublicKey};
1213
use parking_lot::RwLock;
14+
use rayon::iter::ParallelIterator;
15+
use rayon::prelude::*;
1316
use sailfish::types::{Evidence, Round, RoundNumber};
1417
use serde::{Deserialize, Serialize};
15-
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
16-
use std::result::Result as StdResult;
17-
use std::sync::Arc;
1818
use timeboost_config::DECRYPTER_PORT_OFFSET;
1919
use timeboost_crypto::prelude::{
2020
DkgDecKey, LabeledDkgDecKey, Plaintext, ThresholdCiphertext, ThresholdDecShare,
2121
ThresholdEncError, ThresholdEncScheme, ThresholdScheme, Vess, VssSecret,
2222
};
2323
use timeboost_types::{
24-
AccumulatorMode, DkgAccumulator, DkgBundle, DkgSubset, InclusionList, KeyStore, KeyStoreVec,
25-
ThresholdKey, ThresholdKeyCell,
24+
AccumulatorMode, DkgAccumulator, DkgBundle, DkgSubset, DkgSubsetRef, InclusionList, KeyStore,
25+
KeyStoreVec, ThresholdKey, ThresholdKeyCell,
2626
};
2727
use tokio::spawn;
2828
use tokio::sync::mpsc::{Receiver, Sender, channel};
@@ -728,21 +728,31 @@ impl Worker {
728728
.ok_or(DecrypterError::NoCommittee(self.current))?;
729729

730730
let prev = (guard.len() == 2).then(|| guard.last().clone());
731-
732-
subsets.insert(src, res.subset.to_owned());
733731
let committee = current.committee();
734732
let threshold: usize = committee.one_honest_threshold().into();
735733

736-
let mut counts = HashMap::new();
737-
for subset in subsets.values() {
738-
*counts.entry(subset).or_insert(0) += 1;
734+
subsets.insert(src, res.subset);
735+
736+
let mut subset_groups: HashMap<DkgSubsetRef, Vec<&PublicKey>> = HashMap::new();
737+
let mut threshold_subset = None;
738+
739+
for (pk, subset) in subsets.iter() {
740+
let pks = subset_groups.entry(subset.as_ref()).or_default();
741+
pks.push(pk);
742+
743+
if pks.len() >= threshold {
744+
threshold_subset = Some((subset.as_ref(), pks[0]));
745+
break;
746+
}
739747
}
740748

741-
if let Some((&subset, _)) = counts.iter().find(|(_, count)| **count >= threshold) {
742-
let acc = DkgAccumulator::from_subset(current.clone(), subset.to_owned());
749+
if let Some((subset_ref, pk)) = threshold_subset {
750+
let subset = subsets.to_owned().remove(pk).expect("subset exists in map");
751+
let acc = DkgAccumulator::from_subset(current.clone(), subset);
743752
let mode = acc.mode().clone();
744753
self.tracker.insert(committee.id(), acc);
745-
let dec_key = subset
754+
755+
let dec_key = subset_ref
746756
.extract_key(current, &self.dkg_sk, prev.as_ref())
747757
.map_err(|e| DecrypterError::Dkg(e.to_string()))?;
748758

@@ -830,7 +840,7 @@ impl Worker {
830840
if matches!(mode, AccumulatorMode::Dkg) {
831841
if let Some(subset) = acc.try_finalize() {
832842
let dec_key = subset
833-
.extract_key(&key_store.clone(), &self.dkg_sk, None)
843+
.extract_key(&key_store, &self.dkg_sk, None)
834844
.map_err(|e| DecrypterError::Dkg(e.to_string()))?;
835845
self.dec_key.set(dec_key);
836846
self.state = WorkerState::Running;

timeboost-types/src/decryption.rs

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ impl ThresholdKey {
8181
dealings: I,
8282
) -> anyhow::Result<Self>
8383
where
84-
I: ExactSizeIterator<Item = (usize, VssShare, VssCommitment)> + Clone,
84+
I: Iterator<Item = (usize, VssShare, VssCommitment)>,
8585
{
8686
let old_pp = FeldmanVssPublicParam::from(old_committee);
8787
let new_pp = FeldmanVssPublicParam::from(new_committee);
@@ -131,10 +131,15 @@ impl ThresholdKey {
131131

132132
/// `DecryptionKeyCell` is a thread-safe container for an optional `DecryptionKey`
133133
/// that allows asynchronous notification when the key is set.
134-
#[derive(Debug, Clone, Default)]
134+
#[derive(Clone, Debug, Default)]
135135
pub struct ThresholdKeyCell {
136-
key: Arc<RwLock<Option<ThresholdKey>>>,
137-
notify: Arc<Notify>,
136+
inner: Arc<ThresholdKeyCellInner>,
137+
}
138+
139+
#[derive(Debug, Default)]
140+
struct ThresholdKeyCellInner {
141+
key: RwLock<Option<ThresholdKey>>,
142+
notify: Notify,
138143
}
139144

140145
impl ThresholdKeyCell {
@@ -143,25 +148,25 @@ impl ThresholdKeyCell {
143148
}
144149

145150
pub fn set(&self, key: ThresholdKey) {
146-
*self.key.write() = Some(key);
147-
self.notify.notify_waiters();
151+
*self.inner.key.write() = Some(key);
152+
self.inner.notify.notify_waiters();
148153
}
149154

150155
pub fn get(&self) -> Option<ThresholdKey> {
151-
(*self.key.read()).clone()
156+
(*self.inner.key.read()).clone()
152157
}
153158

154159
pub fn enc_key(&self) -> Option<ThresholdEncKey> {
155160
self.get().map(|sk| sk.pubkey)
156161
}
157162

158163
pub fn get_ref(&self) -> impl Deref<Target = Option<ThresholdKey>> {
159-
self.key.read()
164+
self.inner.key.read()
160165
}
161166

162167
pub async fn read(&self) -> ThresholdKey {
163168
loop {
164-
let fut = self.notify.notified();
169+
let fut = self.inner.notify.notified();
165170
if let Some(k) = self.get() {
166171
return k;
167172
}
@@ -280,10 +285,10 @@ impl<const N: usize> From<KeyStore> for KeyStoreVec<N> {
280285

281286
/// A `KeyStore` with committee information and public keys used in the DKG or key resharing
282287
#[derive(Debug, Clone)]
283-
pub struct KeyStore(Arc<Inner>);
288+
pub struct KeyStore(Arc<KeyStoreInner>);
284289

285290
#[derive(Debug)]
286-
struct Inner {
291+
struct KeyStoreInner {
287292
committee: Committee,
288293
keys: BTreeMap<KeyId, DkgEncKey>,
289294
}
@@ -294,7 +299,7 @@ impl KeyStore {
294299
I: IntoIterator<Item = (T, DkgEncKey)>,
295300
T: Into<KeyId>,
296301
{
297-
let this = Self(Arc::new(Inner {
302+
let this = Self(Arc::new(KeyStoreInner {
298303
committee: c,
299304
keys: keys.into_iter().map(|(i, k)| (i.into(), k)).collect(),
300305
}));
@@ -446,20 +451,19 @@ impl DkgAccumulator {
446451
}
447452

448453
/// Try to finalize the accumulator into a subset if enough bundles are collected.
449-
pub fn try_finalize(&mut self) -> Option<DkgSubset> {
454+
/// Returns a reference to the internal data to avoid cloning the bundles.
455+
pub fn try_finalize(&self) -> Option<DkgSubsetRef<'_>> {
450456
if self.complete {
451457
let combkey = match &self.mode {
452458
AccumulatorMode::Dkg => None,
453459
AccumulatorMode::Resharing(combkey) => Some(combkey.clone()),
454460
};
455461

456-
let subset = DkgSubset {
462+
Some(DkgSubsetRef {
457463
committee_id: self.committee().id(),
458-
bundles: self.bundles.clone(),
464+
bundles: &self.bundles,
459465
combkey,
460-
};
461-
462-
Some(subset)
466+
})
463467
} else {
464468
None
465469
}
@@ -488,6 +492,14 @@ pub struct DkgSubset {
488492
combkey: Option<ThresholdCombKey>,
489493
}
490494

495+
/// A reference-based version of DkgSubset to avoid cloning bundles.
496+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
497+
pub struct DkgSubsetRef<'a> {
498+
committee_id: CommitteeId,
499+
bundles: &'a [DkgBundle],
500+
combkey: Option<ThresholdCombKey>,
501+
}
502+
491503
impl DkgSubset {
492504
/// Create a new subset with DKG bundles.
493505
pub fn new_dkg(committee_id: CommitteeId, bundles: Vec<DkgBundle>) -> Self {
@@ -536,6 +548,27 @@ impl DkgSubset {
536548
self.combkey.is_some()
537549
}
538550

551+
/// Convert this DkgSubset to a DkgSubsetRef.
552+
pub fn as_ref(&self) -> DkgSubsetRef<'_> {
553+
DkgSubsetRef {
554+
committee_id: self.committee_id,
555+
bundles: &self.bundles,
556+
combkey: self.combkey.as_ref().cloned(),
557+
}
558+
}
559+
560+
/// Extract the new threshold decryption key from the subset.
561+
pub fn extract_key(
562+
&self,
563+
curr: &KeyStore,
564+
dkg_sk: &LabeledDkgDecKey,
565+
prev: Option<&KeyStore>,
566+
) -> anyhow::Result<ThresholdKey> {
567+
self.as_ref().extract_key(curr, dkg_sk, prev)
568+
}
569+
}
570+
571+
impl<'a> DkgSubsetRef<'a> {
539572
/// Extract the new threshold decryption key from the subset.
540573
pub fn extract_key(
541574
&self,
@@ -547,7 +580,7 @@ impl DkgSubset {
547580

548581
match &self.combkey {
549582
None => {
550-
let mut dealings_iter = ResultIter::new(self.bundles().iter().map(|b| {
583+
let mut dealings_iter = ResultIter::new(self.bundles.iter().map(|b| {
551584
vess.decrypt_share(curr.committee(), dkg_sk, b.vess_ct(), DKG_AAD)
552585
.map(|s| (s, b.comm().clone()))
553586
}));
@@ -559,38 +592,28 @@ impl DkgSubset {
559592
)?;
560593

561594
dealings_iter.result()?;
562-
563595
Ok(dec_key)
564596
}
565597
Some(combkey) => {
566-
let Some(prev) = prev else {
567-
return Err(anyhow!("previous key store missing"));
568-
};
598+
let prev = prev.ok_or_else(|| anyhow!("previous key store missing"))?;
599+
let mut dealings_iter = ResultIter::new(self.bundles.iter().map(|b| {
600+
let node_idx = b.origin().0.into();
601+
let pub_share = combkey
602+
.get_pub_share(node_idx)
603+
.ok_or(VessError::FailedVerification)?;
604+
vess.decrypt_reshare(curr.committee(), dkg_sk, b.vess_ct(), DKG_AAD, *pub_share)
605+
.map(|s| (node_idx, s, b.comm().clone()))
606+
}));
569607

570-
let dealings: Vec<_> = self
571-
.bundles()
572-
.iter()
573-
.map(|b| {
574-
let node_idx = b.origin().0.into();
575-
let pub_share = combkey
576-
.get_pub_share(node_idx)
577-
.ok_or(VessError::FailedVerification)?;
578-
let s = vess.decrypt_reshare(
579-
curr.committee(),
580-
dkg_sk,
581-
b.vess_ct(),
582-
DKG_AAD,
583-
*pub_share,
584-
)?;
585-
Ok((node_idx, s, b.comm().clone()))
586-
})
587-
.collect::<Result<Vec<_>, VessError>>()?;
588-
ThresholdKey::from_resharing(
608+
let dec_key = ThresholdKey::from_resharing(
589609
prev.committee(),
590610
curr.committee(),
591611
dkg_sk.node_idx(),
592-
dealings.into_iter(),
593-
)
612+
&mut dealings_iter,
613+
)?;
614+
615+
dealings_iter.result()?;
616+
Ok(dec_key)
594617
}
595618
}
596619
}

timeboost-types/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ pub use bundle::{
2222
pub use bytes::Bytes;
2323
pub use candidate_list::{CandidateList, CandidateListBytes};
2424
pub use decryption::{
25-
AccumulatorMode, DkgAccumulator, DkgSubset, KeyStore, KeyStoreVec, ResultIter, ThresholdKey,
26-
ThresholdKeyCell,
25+
AccumulatorMode, DkgAccumulator, DkgSubset, DkgSubsetRef, KeyStore, KeyStoreVec, ResultIter,
26+
ThresholdKey, ThresholdKeyCell,
2727
};
2828
pub use delayed_inbox::DelayedInboxIndex;
2929
pub use inclusion_list::InclusionList;

0 commit comments

Comments
 (0)