Skip to content

Commit 3295621

Browse files
authored
Merge pull request #406 from EspressoSystems/ax/reshare-core
Key resharing for Feldman VSS
2 parents e19fdf7 + a8dddf2 commit 3295621

File tree

7 files changed

+469
-73
lines changed

7 files changed

+469
-73
lines changed

timeboost-crypto/src/feldman.rs

Lines changed: 284 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ use ark_serialize::{CanonicalSerialize, SerializationError, serialize_to_vec};
66
use ark_std::marker::PhantomData;
77
use ark_std::rand::Rng;
88
use derive_more::{Deref, From, IntoIterator};
9+
use rayon::prelude::*;
910
use serde::{Deserialize, Serialize};
1011
use serde_with::serde_as;
11-
use std::{iter::successors, num::NonZeroUsize};
12+
use std::{iter::successors, num::NonZeroUsize, ops::Add};
1213

1314
use crate::{
14-
interpolation::interpolate,
15-
traits::dkg::{VerifiableSecretSharing, VssError},
15+
interpolation::{interpolate, interpolate_in_exponent},
16+
traits::dkg::{KeyResharing, VerifiableSecretSharing, VssError},
1617
};
1718

1819
/// Feldman VSS: <https://www.cs.umd.edu/~gasarch/TOPICS/secretsharing/feldmanVSS.pdf>
@@ -152,27 +153,26 @@ impl<C: CurveGroup> VerifiableSecretSharing for FeldmanVss<C> {
152153

153154
fn reconstruct(
154155
pp: &Self::PublicParam,
155-
shares: impl Iterator<Item = (usize, Self::SecretShare)>,
156+
shares: impl ExactSizeIterator<Item = (usize, Self::SecretShare)> + Clone,
156157
) -> Result<Self::Secret, VssError> {
157-
let shares = shares.collect::<Vec<_>>();
158158
let n = pp.n.get();
159159
let t = pp.t.get();
160160
// input validation
161161
if shares.len() != t {
162162
return Err(VssError::MismatchedSharesCount(t, shares.len()));
163163
}
164-
for (idx, _) in shares.iter() {
165-
if *idx >= n {
166-
return Err(VssError::IndexOutOfBound(n - 1, *idx));
164+
for (idx, _) in shares.clone() {
165+
if idx >= n {
166+
return Err(VssError::IndexOutOfBound(n - 1, idx));
167167
}
168168
}
169169

170170
// Lagrange interpolate to get back the secret
171171
let eval_points: Vec<_> = shares
172-
.iter()
173-
.map(|&(idx, _)| C::ScalarField::from(idx as u64 + 1))
172+
.clone()
173+
.map(|(idx, _)| C::ScalarField::from(idx as u64 + 1))
174174
.collect();
175-
let evals: Vec<_> = shares.iter().map(|&(_, share)| share).collect();
175+
let evals: Vec<_> = shares.map(|(_, share)| share).collect();
176176
interpolate::<C>(&eval_points, &evals)
177177
.map_err(|e| VssError::FailedReconstruction(e.to_string()))
178178
}
@@ -206,15 +206,130 @@ impl<C: CurveGroup> FeldmanCommitment<C> {
206206
pub fn try_from_bytes<const N: usize>(value: &[u8]) -> Result<Self, SerializationError> {
207207
crate::try_from_bytes::<Self, N>(value)
208208
}
209-
210209
pub fn try_from_str<const N: usize>(value: &str) -> Result<Self, SerializationError> {
211210
crate::try_from_str::<Self, N>(value)
212211
}
213212
}
214213

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+
222+
impl<C: CurveGroup> Add<&FeldmanCommitment<C>> for FeldmanCommitment<C> {
223+
type Output = FeldmanCommitment<C>;
224+
225+
fn add(self, other: &FeldmanCommitment<C>) -> Self::Output {
226+
&self + other
227+
}
228+
}
229+
230+
impl<C: CurveGroup> Add<&FeldmanCommitment<C>> for &FeldmanCommitment<C> {
231+
type Output = FeldmanCommitment<C>;
232+
233+
fn add(self, other: &FeldmanCommitment<C>) -> Self::Output {
234+
let combined: Vec<C> = self
235+
.comm
236+
.iter()
237+
.zip(other.comm.iter())
238+
.map(|(x, y)| *x + y)
239+
.collect();
240+
C::normalize_batch(&combined).into()
241+
}
242+
}
243+
244+
impl<C: CurveGroup> KeyResharing<Self> for FeldmanVss<C> {
245+
fn reshare<R: Rng>(
246+
new_pp: &FeldmanVssPublicParam,
247+
old_share: &C::ScalarField,
248+
rng: &mut R,
249+
) -> (Vec<C::ScalarField>, FeldmanCommitment<C>) {
250+
let (poly, comm) = Self::rand_poly_and_commit(new_pp, *old_share, rng);
251+
let reshares = Self::compute_shares(new_pp, &poly).collect();
252+
(reshares, comm)
253+
}
254+
255+
fn verify_reshare(
256+
old_pp: &FeldmanVssPublicParam,
257+
new_pp: &FeldmanVssPublicParam,
258+
send_node_idx: usize,
259+
recv_node_idx: usize,
260+
old_commitment: &FeldmanCommitment<C>,
261+
row_commitment: &FeldmanCommitment<C>,
262+
reshare: &C::ScalarField,
263+
) -> Result<(), VssError> {
264+
let old_public_share = Self::derive_public_share(old_pp, send_node_idx, old_commitment)?;
265+
let new_public_share = Self::derive_public_share(new_pp, recv_node_idx, row_commitment)?;
266+
267+
if C::generator().mul(reshare) == new_public_share
268+
&& row_commitment[0] == old_public_share.into_affine()
269+
{
270+
Ok(())
271+
} else {
272+
Err(VssError::FailedVerification)
273+
}
274+
}
275+
276+
fn combine(
277+
old_pp: &FeldmanVssPublicParam,
278+
new_pp: &FeldmanVssPublicParam,
279+
recv_node_idx: usize,
280+
reshares: impl ExactSizeIterator<Item = (usize, C::ScalarField, FeldmanCommitment<C>)> + Clone,
281+
) -> Result<(C::ScalarField, FeldmanCommitment<C>), VssError> {
282+
// input validation
283+
let n = old_pp.n.get();
284+
if reshares.len() == 0 {
285+
return Err(VssError::EmptyReshare);
286+
}
287+
for (idx, _, _) in reshares.clone() {
288+
if idx >= n {
289+
return Err(VssError::IndexOutOfBound(n - 1, idx));
290+
}
291+
}
292+
293+
let new_n = new_pp.n.get();
294+
let new_t = new_pp.t.get();
295+
if recv_node_idx >= new_n {
296+
return Err(VssError::IndexOutOfBound(new_n - 1, recv_node_idx));
297+
}
298+
for (_, _, row_commitment) in reshares.clone() {
299+
if row_commitment.len() != new_t {
300+
return Err(VssError::InvalidCommitment);
301+
}
302+
}
303+
304+
// interpolate reshares to get new secret share
305+
let eval_points: Vec<_> = reshares
306+
.clone()
307+
.map(|(idx, _, _)| C::ScalarField::from(idx as u64 + 1))
308+
.collect();
309+
let recv_reshares: Vec<_> = reshares.clone().map(|(_, share, _)| share).collect();
310+
let new_secret = interpolate::<C>(&eval_points, &recv_reshares)
311+
.map_err(|e| VssError::FailedCombine(e.to_string()))?;
312+
313+
// interpolate in the exponent to get new Feldman commitment
314+
let row_commitments: Vec<_> = reshares.map(|(_, _, commitment)| commitment).collect();
315+
let new_commitment = (0..new_t)
316+
.into_par_iter()
317+
.map(|j| {
318+
let j_th_coeffs: Vec<C::Affine> =
319+
row_commitments.iter().map(|row| row[j]).collect();
320+
interpolate_in_exponent::<C>(&eval_points, &j_th_coeffs)
321+
.map_err(|e| VssError::FailedCombine(e.to_string()))
322+
})
323+
.collect::<Result<Vec<_>, VssError>>()?;
324+
let new_commitment = C::normalize_batch(&new_commitment);
325+
326+
Ok((new_secret, new_commitment.into()))
327+
}
328+
}
329+
215330
#[cfg(test)]
216331
mod tests {
217-
use ark_bls12_381::G1Projective;
332+
use ark_bls12_381::{Fr, G1Projective};
218333
use ark_std::{UniformRand, rand::seq::SliceRandom, test_rng};
219334

220335
use super::*;
@@ -297,4 +412,160 @@ mod tests {
297412
fn test_feldman_vss() {
298413
test_feldman_vss_helper::<G1Projective>();
299414
}
415+
416+
// Core key resharing workflow
417+
fn run_reshare_scenario(
418+
old_t: usize,
419+
old_n: usize,
420+
new_t: usize,
421+
new_n: usize,
422+
rng: &mut impl Rng,
423+
) {
424+
let old_pp = FeldmanVssPublicParam::new(
425+
NonZeroUsize::new(old_t).unwrap(),
426+
NonZeroUsize::new(old_n).unwrap(),
427+
);
428+
let new_pp = FeldmanVssPublicParam::new(
429+
NonZeroUsize::new(new_t).unwrap(),
430+
NonZeroUsize::new(new_n).unwrap(),
431+
);
432+
433+
let secret = Fr::rand(rng);
434+
435+
let (old_shares, old_commitment) = FeldmanVss::<G1Projective>::share(&old_pp, rng, secret);
436+
437+
// Verify original shares
438+
for (node_idx, share) in old_shares.iter().enumerate() {
439+
assert!(
440+
FeldmanVss::<G1Projective>::verify(&old_pp, node_idx, share, &old_commitment)
441+
.is_ok()
442+
);
443+
}
444+
445+
let mut reshare_matrix = Vec::new();
446+
let mut row_commitments = Vec::new();
447+
448+
for old_share in old_shares.iter() {
449+
let (reshare_row, row_commitment) =
450+
FeldmanVss::<G1Projective>::reshare(&new_pp, old_share, rng);
451+
reshare_matrix.push(reshare_row);
452+
row_commitments.push(row_commitment);
453+
}
454+
455+
// Verify reshares
456+
for i in 0..old_n {
457+
for j in 0..new_n {
458+
assert!(
459+
FeldmanVss::<G1Projective>::verify_reshare(
460+
&old_pp,
461+
&new_pp,
462+
i,
463+
j,
464+
&old_commitment,
465+
&row_commitments[i],
466+
&reshare_matrix[i][j],
467+
)
468+
.is_ok()
469+
);
470+
}
471+
}
472+
473+
let mut new_shares = Vec::new();
474+
let mut new_commitments = Vec::new();
475+
476+
for j in 0..new_n {
477+
let recv_reshares: Vec<Fr> = (0..old_t)
478+
.collect::<Vec<_>>()
479+
.iter()
480+
.map(|&i| reshare_matrix[i][j])
481+
.collect();
482+
let selected_row_commitments: Vec<FeldmanCommitment<_>> =
483+
(0..old_t).map(|i| row_commitments[i].clone()).collect();
484+
485+
let reshares_iter =
486+
(0..old_t).map(|i| (i, recv_reshares[i], selected_row_commitments[i].clone()));
487+
let (new_secret_share, new_commitment) =
488+
FeldmanVss::<G1Projective>::combine(&old_pp, &new_pp, j, reshares_iter).unwrap();
489+
490+
new_shares.push(new_secret_share);
491+
new_commitments.push(new_commitment);
492+
493+
assert!(
494+
FeldmanVss::<G1Projective>::verify(
495+
&new_pp,
496+
j,
497+
&new_secret_share,
498+
&new_commitments[j]
499+
)
500+
.is_ok()
501+
);
502+
}
503+
504+
// Reconstruct secret
505+
let reconstructed_secret = FeldmanVss::<G1Projective>::reconstruct(
506+
&new_pp,
507+
(0..new_t).map(|i| (i, new_shares[i])),
508+
)
509+
.unwrap();
510+
511+
assert_eq!(reconstructed_secret, secret);
512+
}
513+
514+
// Test success-path for identical (t,n) → (t',n') case
515+
#[test]
516+
fn test_key_resharing_identical_params() {
517+
let rng = &mut test_rng();
518+
519+
// Run 7 random trials (between 5-10)
520+
for _ in 0..7 {
521+
// Generate random (t,n) parameters
522+
let n = rng.gen_range(5..12);
523+
let t = rng.gen_range(2..n);
524+
run_reshare_scenario(t, n, t, n, rng);
525+
}
526+
}
527+
528+
// Test success-path for different (t,n) → (t',n') cases
529+
#[test]
530+
fn test_key_resharing_different_threshold_committee_sizes() {
531+
let rng = &mut test_rng();
532+
533+
// Run multiple random trials with different parameter combinations
534+
for _ in 0..10 {
535+
// Randomly choose (t,n) parameters for the original committee
536+
let old_n = rng.gen_range(5..15);
537+
let old_t = rng.gen_range(2..old_n);
538+
539+
// Randomly choose (t',n') parameters for the new committee with variations
540+
// Sometimes larger, sometimes smaller than original
541+
let new_n = match rng.gen_range(0..3) {
542+
0 => rng.gen_range(5..old_n), // Smaller committee
543+
1 => rng.gen_range(old_n..20), // Larger committee
544+
_ => rng.gen_range(5..20), // Random size
545+
};
546+
let new_t = rng.gen_range(2..new_n);
547+
548+
run_reshare_scenario(old_t, old_n, new_t, new_n, rng);
549+
}
550+
}
551+
552+
// Test specific edge cases for minimal thresholds and committee size limits
553+
#[test]
554+
fn test_edge_case_minimal_thresholds() {
555+
let rng = &mut test_rng();
556+
557+
// Edge case scenarios: (t, n) → (t', n')
558+
let test_cases = vec![
559+
((1, 3), (1, 5)), // (t=1, n=3) → (t'=1, n'=5): minimal threshold expanding committee
560+
((2, 2), (2, 2)), // (t=2, n=2) → (t'=2, n'=2): minimal committee size (t=n)
561+
((1, 2), (1, 3)), // (t=1, n=2) → (t'=1, n'=3): minimal viable committee expanding
562+
((1, 4), (1, 2)), // (t=1, n=4) → (t'=1, n'=2): shrinking to minimal viable size
563+
((2, 3), (1, 4)), // (t=2, n=3) → (t'=1, n'=4): threshold reduction with expansion
564+
((1, 5), (2, 3)), // (t=1, n=5) → (t'=2, n'=3): threshold increase with shrinking
565+
];
566+
567+
for ((old_t, old_n), (new_t, new_n)) in test_cases {
568+
run_reshare_scenario(old_t, old_n, new_t, new_n, rng);
569+
}
570+
}
300571
}

0 commit comments

Comments
 (0)