Skip to content

Commit bb075db

Browse files
committed
introduce VSS::aggregate() and ResultIter to avoid copy during from_dkg()
1 parent 9a7dfbc commit bb075db

File tree

6 files changed

+136
-49
lines changed

6 files changed

+136
-49
lines changed

timeboost-crypto/src/feldman.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,14 @@ impl<C: CurveGroup> FeldmanCommitment<C> {
211211
}
212212
}
213213

214-
// Implementation of Add trait for FeldmanCommitment + &FeldmanCommitment
214+
impl<C: CurveGroup> Add<FeldmanCommitment<C>> for FeldmanCommitment<C> {
215+
type Output = FeldmanCommitment<C>;
216+
217+
fn add(self, other: FeldmanCommitment<C>) -> Self::Output {
218+
&self + &other
219+
}
220+
}
221+
215222
impl<C: CurveGroup> Add<&FeldmanCommitment<C>> for FeldmanCommitment<C> {
216223
type Output = FeldmanCommitment<C>;
217224

@@ -220,7 +227,6 @@ impl<C: CurveGroup> Add<&FeldmanCommitment<C>> for FeldmanCommitment<C> {
220227
}
221228
}
222229

223-
// Implementation of Add trait for &FeldmanCommitment + &FeldmanCommitment
224230
impl<C: CurveGroup> Add<&FeldmanCommitment<C>> for &FeldmanCommitment<C> {
225231
type Output = FeldmanCommitment<C>;
226232

timeboost-crypto/src/traits/dkg.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Traits related to Distributed Key Generation (DKG) and Key Resharing
22
33
use ark_std::rand::Rng;
4+
use std::ops::Add;
45
use thiserror::Error;
56

67
/// A trait for (t, n)-Verifiable Secret Sharing (VSS) schemes.
@@ -53,6 +54,22 @@ pub trait VerifiableSecretSharing {
5354
pp: &Self::PublicParam,
5455
shares: impl ExactSizeIterator<Item = (usize, Self::SecretShare)> + Clone,
5556
) -> Result<Self::Secret, VssError>;
57+
58+
/// Aggregates multiple commitments and secret shares into a single commitment and secret share.
59+
///
60+
/// This is commonly used in DKG protocols to combine multiple dealings/contributions.
61+
///
62+
/// Returns `Ok((secret_share, commitment))` if aggregation succeeds
63+
fn aggregate<I>(dealings: I) -> Result<(Self::SecretShare, Self::Commitment), VssError>
64+
where
65+
I: Iterator<Item = (Self::SecretShare, Self::Commitment)>,
66+
Self::Commitment: Add<Self::Commitment, Output = Self::Commitment>,
67+
Self::SecretShare: Add<Self::SecretShare, Output = Self::SecretShare>,
68+
{
69+
dealings
70+
.reduce(|(acc_share, acc_comm), (share, comm)| (acc_share + share, acc_comm + comm))
71+
.ok_or(VssError::EmptyAggInput)
72+
}
5673
}
5774

5875
/// Publicly verifiable key resharing scheme for a VSS where existing share holders of a Shamir
@@ -116,6 +133,8 @@ pub enum VssError {
116133
FailedReconstruction(String),
117134
#[error("internal err: {0}")]
118135
InternalError(String),
136+
#[error("aggregation input is empty")]
137+
EmptyAggInput,
119138

120139
#[error("reshare data is empty")]
121140
EmptyReshare,

timeboost-sequencer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ serde = { workspace = true }
2424
thiserror = { workspace = true }
2525
timeboost-crypto = { path = "../timeboost-crypto" }
2626
timeboost-types = { path = "../timeboost-types" }
27+
timeboost-utils = { path = "../timeboost-utils" }
2728
tokio = { workspace = true }
2829
tracing = { workspace = true }
2930

3031
[dev-dependencies]
3132
ark-std = { workspace = true }
3233
bs58 = { workspace = true }
3334
portpicker = { workspace = true }
34-
timeboost-utils = { path = "../timeboost-utils" }

timeboost-sequencer/src/decrypt.rs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use timeboost_crypto::{DecryptionScheme, Plaintext};
2121
use timeboost_types::{
2222
DecryptionKey, DkgAccumulator, DkgBundle, DkgKeyStore, InclusionList, Subset,
2323
};
24+
use timeboost_utils::ResultIter;
2425
use tokio::spawn;
2526
use tokio::sync::mpsc::{Receiver, Sender, channel};
2627
use tokio::task::JoinHandle;
@@ -663,24 +664,23 @@ impl Worker {
663664
// TODO: centralize these constant, redeclared in DkgAccumulator.try_add()
664665
let aad: &[u8; 3] = b"dkg";
665666
let vess = ShoupVess::new_fast_from(committee);
666-
let (shares, commitments) = subset
667-
.bundles()
668-
.iter()
669-
.map(|b| {
670-
vess.decrypt_share(&self.dkg_sk, b.vess_ct(), aad)
671-
.map(|s| (s, b.comm().clone()))
672-
.map_err(|e| DecrypterError::Dkg(e.to_string()))
673-
})
674-
.collect::<Result<(Vec<_>, Vec<_>)>>()?;
667+
let mut dealings_iter = ResultIter::new(subset.bundles().iter().map(|b| {
668+
vess.decrypt_share(&self.dkg_sk, b.vess_ct(), aad)
669+
.map(|s| (s, b.comm().clone()))
670+
}));
675671

676672
let dec_sk = DecryptionKey::from_dkg(
677673
committee.size().into(),
678674
self.dkg_sk.node_idx(),
679-
&commitments,
680-
&shares,
675+
&mut dealings_iter,
681676
)
682677
.map_err(|e| DecrypterError::Dkg(e.to_string()))?;
683678

679+
// in case of early-return of ResultIter
680+
dealings_iter
681+
.result()
682+
.map_err(|e| DecrypterError::Dkg(e.to_string()))?;
683+
684684
self.enc_key.set(dec_sk.pubkey().clone());
685685
self.dkg_state = DkgState::Completed(dec_sk);
686686
info!(committee_id = %committee.id(), node = %self.label, "DKG finished (node successfully recovered)");
@@ -771,24 +771,23 @@ impl Worker {
771771
// TODO:(alex) centralize these constant, redeclared in DkgAccumulator.try_add()
772772
let aad: &[u8; 3] = b"dkg";
773773
let vess = ShoupVess::new_fast_from(committee);
774-
let (shares, commitments) = subset
775-
.bundles()
776-
.iter()
777-
.map(|b| {
778-
vess.decrypt_share(&self.dkg_sk, b.vess_ct(), aad)
779-
.map(|s| (s, b.comm().clone()))
780-
.map_err(|e| DecrypterError::Dkg(e.to_string()))
781-
})
782-
.collect::<Result<(Vec<_>, Vec<_>)>>()?;
774+
let mut dealings_iter = ResultIter::new(subset.bundles().iter().map(|b| {
775+
vess.decrypt_share(&self.dkg_sk, b.vess_ct(), aad)
776+
.map(|s| (s, b.comm().clone()))
777+
}));
783778

784779
let dec_sk = DecryptionKey::from_dkg(
785780
committee.size().into(),
786781
self.dkg_sk.node_idx(),
787-
&commitments,
788-
&shares,
782+
&mut dealings_iter,
789783
)
790784
.map_err(|e| DecrypterError::Dkg(e.to_string()))?;
791785

786+
// in case of early-return of ResultIter
787+
dealings_iter
788+
.result()
789+
.map_err(|e| DecrypterError::Dkg(e.to_string()))?;
790+
792791
self.enc_key.set(dec_sk.pubkey().clone());
793792
self.dkg_state = DkgState::Completed(dec_sk);
794793
info!(committee_id = %committee.id(), node = %self.label, "DKG finished");
@@ -1419,8 +1418,12 @@ mod tests {
14191418
.iter()
14201419
.enumerate()
14211420
.map(|(node_idx, shares)| {
1422-
super::DecryptionKey::from_dkg(COMMITTEE_SIZE, node_idx, &commitments, shares)
1423-
.expect("threshold key derivation should succeed")
1421+
super::DecryptionKey::from_dkg(
1422+
COMMITTEE_SIZE,
1423+
node_idx,
1424+
shares.iter().cloned().zip(commitments.iter().cloned()),
1425+
)
1426+
.expect("threshold key derivation should succeed")
14241427
})
14251428
.collect();
14261429
tracing::info!(

timeboost-types/src/decryption.rs

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use std::collections::{BTreeMap, btree_map};
2-
31
use anyhow::anyhow;
42
use ark_ec::AffineRepr;
53
use multisig::{Committee, CommitteeId, KeyId};
64
use rayon::prelude::*;
75
use serde::{Deserialize, Serialize};
6+
use std::collections::{BTreeMap, btree_map};
87
use timeboost_crypto::{
98
DecryptionScheme,
109
prelude::{DkgEncKey, Vess, Vss},
@@ -43,38 +42,34 @@ impl DecryptionKey {
4342
/// # Parameters
4443
/// - `committee_size`: size of the threshold committee
4544
/// - `node_idx`: in 0..committee_size, currently same as KeyId
46-
/// - `commitments`: the Feldman Commitments: multiple output of `ShoupVess::encrypted_shares()`
47-
/// - `key_shares`: multiple decrypted secret shares from `ShoupVess::decrypt_share()`
48-
pub fn from_dkg(
45+
/// - `dealings`: ResultIter containing decrypted shares and commitments
46+
pub fn from_dkg<I>(
4947
committee_size: usize,
5048
node_idx: usize,
51-
commitments: &[<Vss as VerifiableSecretSharing>::Commitment],
52-
key_shares: &[<Vss as VerifiableSecretSharing>::SecretShare],
53-
) -> anyhow::Result<Self> {
54-
anyhow::ensure!(
55-
commitments.len() == key_shares.len(),
56-
"mismatched input length"
57-
);
58-
59-
// aggregate selected dealings/contributions
60-
let agg_comm = commitments
61-
.par_iter()
62-
.cloned()
63-
.reduce_with(|a, b| a + &b)
64-
.ok_or_else(|| anyhow!("no commitments provided"))?;
65-
let agg_key_share = key_shares.iter().sum();
49+
mut dealings: I,
50+
) -> anyhow::Result<Self>
51+
where
52+
I: Iterator<
53+
Item = (
54+
<Vss as VerifiableSecretSharing>::SecretShare,
55+
<Vss as VerifiableSecretSharing>::Commitment,
56+
),
57+
>,
58+
{
59+
// aggregate selected dealings
60+
let (agg_key_share, agg_comm) = Vss::aggregate(&mut dealings)?;
6661

6762
// derive key material
68-
Self::from_single_dkg(committee_size, node_idx, &agg_comm, agg_key_share)
63+
Self::from_single_dkg(committee_size, node_idx, agg_key_share, &agg_comm)
6964
}
7065

7166
/// inner routine to construct from a single (aggregated or interpolated) DKG output,
7267
/// shared in both DKG and resharing logic.
7368
fn from_single_dkg(
7469
committee_size: usize,
7570
node_idx: usize,
76-
commitment: &<Vss as VerifiableSecretSharing>::Commitment,
7771
key_share: <Vss as VerifiableSecretSharing>::SecretShare,
72+
commitment: &<Vss as VerifiableSecretSharing>::Commitment,
7873
) -> anyhow::Result<Self> {
7974
// note: all .into() are made available via derive_more::From on those structs
8075
let pk: PublicKey = commitment

timeboost-utils/src/lib.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,67 @@ pub fn select_peer_hosts(
6161
Box::new(keyset.iter().take(keyset.len())) as Box<dyn Iterator<Item = _>>
6262
}
6363
}
64+
65+
/// Wrapper iterator that bridges type conversion
66+
/// from Iterator<Item = Result<T, E>> to Iterator<Item = T>
67+
/// while early-returning an Err(E) if any item is an Err, without collecting or allocating memory.
68+
///
69+
/// # Usage
70+
/// ```no_run
71+
/// fn use_result_iter<I, T, E>(iter: I) -> Result<(), E>
72+
/// where
73+
/// I: Iterator<Item = Result<T, E>>,
74+
/// {
75+
/// let mut result_iter = ResultIter::new(iter);
76+
/// for _ in &mut result_iter {
77+
/// // use item
78+
/// }
79+
/// result_iter.result()
80+
/// }
81+
/// ```
82+
pub struct ResultIter<I, T, E>
83+
where
84+
I: Iterator<Item = Result<T, E>>,
85+
{
86+
iter: I,
87+
error: Option<E>,
88+
}
89+
90+
impl<I, T, E> ResultIter<I, T, E>
91+
where
92+
I: Iterator<Item = Result<T, E>>,
93+
{
94+
/// construct a new ResultIter
95+
pub fn new(iter: I) -> Self {
96+
Self { iter, error: None }
97+
}
98+
99+
/// Get the early-return result
100+
pub fn result(self) -> Result<(), E> {
101+
match self.error {
102+
Some(e) => Err(e),
103+
None => Ok(()),
104+
}
105+
}
106+
}
107+
108+
impl<I, T, E> Iterator for ResultIter<I, T, E>
109+
where
110+
I: Iterator<Item = Result<T, E>>,
111+
{
112+
type Item = T;
113+
114+
fn next(&mut self) -> Option<Self::Item> {
115+
if self.error.is_some() {
116+
return None;
117+
}
118+
match self.iter.next() {
119+
Some(Ok(v)) => Some(v),
120+
Some(Err(e)) => {
121+
self.error = Some(e);
122+
None
123+
}
124+
None => None,
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)