Skip to content

Commit 2987525

Browse files
committed
Merge branch 'ax/reshare-core' into ax/reshare-vess
2 parents 2c08f97 + a8dddf2 commit 2987525

File tree

6 files changed

+190
-97
lines changed

6 files changed

+190
-97
lines changed

timeboost-crypto/src/feldman.rs

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use multisig::Committee;
1010
use rayon::prelude::*;
1111
use serde::{Deserialize, Serialize};
1212
use serde_with::serde_as;
13-
use std::{iter::successors, num::NonZeroUsize};
13+
use std::{iter::successors, num::NonZeroUsize, ops::Add};
1414

1515
use crate::{
1616
interpolation::{interpolate, interpolate_in_exponent},
@@ -158,27 +158,26 @@ impl<C: CurveGroup> VerifiableSecretSharing for FeldmanVss<C> {
158158

159159
fn reconstruct(
160160
pp: &Self::PublicParam,
161-
shares: impl Iterator<Item = (usize, Self::SecretShare)>,
161+
shares: impl ExactSizeIterator<Item = (usize, Self::SecretShare)> + Clone,
162162
) -> Result<Self::Secret, VssError> {
163-
let shares = shares.collect::<Vec<_>>();
164163
let n = pp.n.get();
165164
let t = pp.t.get();
166165
// input validation
167166
if shares.len() != t {
168167
return Err(VssError::MismatchedSharesCount(t, shares.len()));
169168
}
170-
for (idx, _) in shares.iter() {
171-
if *idx >= n {
172-
return Err(VssError::IndexOutOfBound(n - 1, *idx));
169+
for (idx, _) in shares.clone() {
170+
if idx >= n {
171+
return Err(VssError::IndexOutOfBound(n - 1, idx));
173172
}
174173
}
175174

176175
// Lagrange interpolate to get back the secret
177176
let eval_points: Vec<_> = shares
178-
.iter()
179-
.map(|&(idx, _)| C::ScalarField::from(idx as u64 + 1))
177+
.clone()
178+
.map(|(idx, _)| C::ScalarField::from(idx as u64 + 1))
180179
.collect();
181-
let evals: Vec<_> = shares.iter().map(|&(_, share)| share).collect();
180+
let evals: Vec<_> = shares.map(|(_, share)| share).collect();
182181
interpolate::<C>(&eval_points, &evals)
183182
.map_err(|e| VssError::FailedReconstruction(e.to_string()))
184183
}
@@ -217,6 +216,36 @@ impl<C: CurveGroup> FeldmanCommitment<C> {
217216
}
218217
}
219218

219+
impl<C: CurveGroup> Add<FeldmanCommitment<C>> for FeldmanCommitment<C> {
220+
type Output = FeldmanCommitment<C>;
221+
222+
fn add(self, other: FeldmanCommitment<C>) -> Self::Output {
223+
&self + &other
224+
}
225+
}
226+
227+
impl<C: CurveGroup> Add<&FeldmanCommitment<C>> for FeldmanCommitment<C> {
228+
type Output = FeldmanCommitment<C>;
229+
230+
fn add(self, other: &FeldmanCommitment<C>) -> Self::Output {
231+
&self + other
232+
}
233+
}
234+
235+
impl<C: CurveGroup> Add<&FeldmanCommitment<C>> for &FeldmanCommitment<C> {
236+
type Output = FeldmanCommitment<C>;
237+
238+
fn add(self, other: &FeldmanCommitment<C>) -> Self::Output {
239+
let combined: Vec<C> = self
240+
.comm
241+
.iter()
242+
.zip(other.comm.iter())
243+
.map(|(x, y)| *x + y)
244+
.collect();
245+
C::normalize_batch(&combined).into()
246+
}
247+
}
248+
220249
impl<C: CurveGroup> KeyResharing<Self> for FeldmanVss<C> {
221250
fn reshare<R: Rng>(
222251
new_pp: &FeldmanVssPublicParam,
@@ -252,19 +281,17 @@ impl<C: CurveGroup> KeyResharing<Self> for FeldmanVss<C> {
252281
fn combine(
253282
old_pp: &FeldmanVssPublicParam,
254283
new_pp: &FeldmanVssPublicParam,
255-
send_node_indices: &[usize],
256-
row_commitments: &[FeldmanCommitment<C>],
257284
recv_node_idx: usize,
258-
recv_reshares: &[C::ScalarField],
285+
reshares: impl ExactSizeIterator<Item = (usize, C::ScalarField, FeldmanCommitment<C>)> + Clone,
259286
) -> Result<(C::ScalarField, FeldmanCommitment<C>), VssError> {
260287
// input validation
261288
let n = old_pp.n.get();
262-
if send_node_indices.is_empty() || row_commitments.is_empty() || recv_reshares.is_empty() {
289+
if reshares.len() == 0 {
263290
return Err(VssError::EmptyReshare);
264291
}
265-
for idx in send_node_indices.iter() {
266-
if *idx >= n {
267-
return Err(VssError::IndexOutOfBound(n - 1, *idx));
292+
for (idx, _, _) in reshares.clone() {
293+
if idx >= n {
294+
return Err(VssError::IndexOutOfBound(n - 1, idx));
268295
}
269296
}
270297

@@ -273,24 +300,23 @@ impl<C: CurveGroup> KeyResharing<Self> for FeldmanVss<C> {
273300
if recv_node_idx >= new_n {
274301
return Err(VssError::IndexOutOfBound(new_n - 1, recv_node_idx));
275302
}
276-
if row_commitments.iter().any(|cm| cm.len() != new_t) {
277-
return Err(VssError::InvalidCommitment);
278-
}
279-
280-
let subset_size = recv_reshares.len();
281-
if send_node_indices.len() != subset_size || row_commitments.len() != subset_size {
282-
return Err(VssError::MismatchedInputLength);
303+
for (_, _, row_commitment) in reshares.clone() {
304+
if row_commitment.len() != new_t {
305+
return Err(VssError::InvalidCommitment);
306+
}
283307
}
284308

285309
// interpolate reshares to get new secret share
286-
let eval_points: Vec<_> = send_node_indices
287-
.iter()
288-
.map(|&idx| C::ScalarField::from(idx as u64 + 1))
310+
let eval_points: Vec<_> = reshares
311+
.clone()
312+
.map(|(idx, _, _)| C::ScalarField::from(idx as u64 + 1))
289313
.collect();
290-
let new_secret = interpolate::<C>(&eval_points, recv_reshares)
314+
let recv_reshares: Vec<_> = reshares.clone().map(|(_, share, _)| share).collect();
315+
let new_secret = interpolate::<C>(&eval_points, &recv_reshares)
291316
.map_err(|e| VssError::FailedCombine(e.to_string()))?;
292317

293318
// interpolate in the exponent to get new Feldman commitment
319+
let row_commitments: Vec<_> = reshares.map(|(_, _, commitment)| commitment).collect();
294320
let new_commitment = (0..new_t)
295321
.into_par_iter()
296322
.map(|j| {
@@ -461,15 +487,10 @@ mod tests {
461487
let selected_row_commitments: Vec<FeldmanCommitment<_>> =
462488
(0..old_t).map(|i| row_commitments[i].clone()).collect();
463489

464-
let (new_secret_share, new_commitment) = FeldmanVss::<G1Projective>::combine(
465-
&old_pp,
466-
&new_pp,
467-
&(0..old_t).collect::<Vec<_>>(),
468-
&selected_row_commitments,
469-
j,
470-
&recv_reshares,
471-
)
472-
.unwrap();
490+
let reshares_iter =
491+
(0..old_t).map(|i| (i, recv_reshares[i], selected_row_commitments[i].clone()));
492+
let (new_secret_share, new_commitment) =
493+
FeldmanVss::<G1Projective>::combine(&old_pp, &new_pp, j, reshares_iter).unwrap();
473494

474495
new_shares.push(new_secret_share);
475496
new_commitments.push(new_commitment);

timeboost-crypto/src/traits/dkg.rs

Lines changed: 21 additions & 4 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.
@@ -51,8 +52,24 @@ pub trait VerifiableSecretSharing {
5152
/// Returns `Ok(secret)` if reconstruction succeeds, or an appropriate `VssError` otherwise.
5253
fn reconstruct(
5354
pp: &Self::PublicParam,
54-
shares: impl Iterator<Item = (usize, Self::SecretShare)>,
55+
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
@@ -94,10 +111,8 @@ pub trait KeyResharing<VSS: VerifiableSecretSharing> {
94111
fn combine(
95112
old_pp: &VSS::PublicParam,
96113
new_pp: &VSS::PublicParam,
97-
send_node_indices: &[usize],
98-
row_commitments: &[VSS::Commitment],
99114
recv_node_idx: usize,
100-
recv_reshares: &[VSS::SecretShare],
115+
reshares: impl ExactSizeIterator<Item = (usize, VSS::SecretShare, VSS::Commitment)> + Clone,
101116
) -> Result<(VSS::Secret, VSS::Commitment), VssError>;
102117
}
103118

@@ -118,6 +133,8 @@ pub enum VssError {
118133
FailedReconstruction(String),
119134
#[error("internal err: {0}")]
120135
InternalError(String),
136+
#[error("aggregation input is empty")]
137+
EmptyAggInput,
121138

122139
#[error("reshare data is empty")]
123140
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
@@ -20,6 +20,7 @@ use timeboost_crypto::{DecryptionScheme, Plaintext};
2020
use timeboost_types::{
2121
DecryptionKey, DkgAccumulator, DkgBundle, DkgKeyStore, InclusionList, Subset,
2222
};
23+
use timeboost_utils::ResultIter;
2324
use tokio::spawn;
2425
use tokio::sync::mpsc::{Receiver, Sender, channel};
2526
use tokio::task::JoinHandle;
@@ -662,24 +663,23 @@ impl Worker {
662663
// TODO: centralize these constant, redeclared in DkgAccumulator.try_add()
663664
let aad: &[u8; 3] = b"dkg";
664665
let vess = Vess::new_fast();
665-
let (shares, commitments) = subset
666-
.bundles()
667-
.iter()
668-
.map(|b| {
669-
vess.decrypt_share(committee, &self.dkg_sk, b.vess_ct(), aad)
670-
.map(|s| (s, b.comm().clone()))
671-
.map_err(|e| DecrypterError::Dkg(e.to_string()))
672-
})
673-
.collect::<Result<(Vec<_>, Vec<_>)>>()?;
666+
let mut dealings_iter = ResultIter::new(subset.bundles().iter().map(|b| {
667+
vess.decrypt_share(committee, &self.dkg_sk, b.vess_ct(), aad)
668+
.map(|s| (s, b.comm().clone()))
669+
}));
674670

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

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

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

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

timeboost-types/src/decryption.rs

Lines changed: 18 additions & 32 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;
4-
use ark_ec::{AffineRepr, CurveGroup};
2+
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,47 +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| {
64-
let combined: Vec<_> = a
65-
.into_iter()
66-
.zip(b.into_iter())
67-
// NOTE: ideally we can use C::normalize_batch(), but C is not exposed,
68-
// minor optimization, so ignore for now.
69-
.map(|(x, y)| (x + y).into_affine())
70-
.collect();
71-
combined.into()
72-
})
73-
.ok_or_else(|| anyhow!("no commitments provided"))?;
74-
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)?;
7561

7662
// derive key material
77-
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)
7864
}
7965

8066
/// inner routine to construct from a single (aggregated or interpolated) DKG output,
8167
/// shared in both DKG and resharing logic.
8268
fn from_single_dkg(
8369
committee_size: usize,
8470
node_idx: usize,
85-
commitment: &<Vss as VerifiableSecretSharing>::Commitment,
8671
key_share: <Vss as VerifiableSecretSharing>::SecretShare,
72+
commitment: &<Vss as VerifiableSecretSharing>::Commitment,
8773
) -> anyhow::Result<Self> {
8874
// note: all .into() are made available via derive_more::From on those structs
8975
let pk: PublicKey = commitment

0 commit comments

Comments
 (0)