Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion secp256kfun/src/poly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,70 @@ pub mod scalar {
(0..threshold).map(|_| Scalar::random(rng)).collect()
}

/// Evalulate the polynomial that passes through the points in `x_and_y` at `0`.
/// Generate a [`Scalar`] polynomial for sharing a particular secret scalar.
///
/// ## Panics
///
/// Panics if `threshold` == 0
pub fn generate_shamir_sharing_poly<Z: ZeroChoice>(
secret: Scalar<Secret, Z>,
threshold: usize,
rng: &mut impl RngCore,
) -> Vec<Scalar<Secret, Z>> {
if threshold == 0 {
panic!("threshold cannot be 0");
}
core::iter::once(secret)
.chain((1..threshold).map(|_| Scalar::random(rng).mark_zero_choice()))
.collect()
}

/// Splits up `secret` into `n_shares` shamir secret shares.
///
/// ## Panics
///
/// - if `n_shares` < `threshold`
/// - if `threshold == 0`
///
/// ## Examples
///
/// ```
/// use secp256kfun::{ prelude::*, poly };
/// use rand::seq::SliceRandom;
/// let my_secret = s!(42);
///
/// let shares: Vec<_> = poly::scalar::trusted_dealer_shamir_sharing(my_secret, 3, 5, &mut rand::thread_rng()).collect();
///
/// // Sample 3 random shares (threshold amount) to reconstruct the secret
/// let mut rng = rand::thread_rng();
/// let random_shares: Vec<_> = shares.choose_multiple(&mut rng, 3).cloned().collect();
///
/// let recovered_secret = poly::scalar::interpolate_and_eval_poly_at_0(&random_shares);
/// assert_eq!(recovered_secret, my_secret);
/// ```
pub fn trusted_dealer_shamir_sharing(
secret: Scalar<Secret, impl ZeroChoice>,
threshold: usize,
n_shares: usize,
rng: &mut impl RngCore,
) -> impl Iterator<Item = (Scalar<Public>, Scalar<Secret, NonZero>)> {
if n_shares < threshold {
panic!("n_shares must be >= threshold");
}
let poly = generate_shamir_sharing_poly(secret, threshold, rng);
(1..=n_shares).map(move |i| {
let i = Scalar::<Public, Zero>::from(i).non_zero().expect("> 0");
(
i,
eval(&poly, i)
.non_zero()
.expect("computationally unreachable if rng is really random"),
)
})
}

/// Evalulate the polynomial that passes through the points in `x_and_y` at `0` i.e. reconstruct
/// the shamir shared secret.
///
/// This is useful for recovering a secret from a set of Sharmir Secret Shares. Each shamir
/// secret share is associated with a participant index (index, share).
Expand Down
87 changes: 87 additions & 0 deletions secp256kfun/tests/poly.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![cfg(feature = "alloc")]
use secp256kfun::{poly, prelude::*};

#[cfg(feature = "proptest")]
use proptest::prelude::*;

#[test]
fn test_lagrange_lambda() {
let res = s!((1 * 4 * 5) / { s!((1 - 2) * (4 - 2) * (5 - 2)).non_zero().unwrap() });
Expand Down Expand Up @@ -127,6 +130,34 @@ fn test_reconstruct_shared_secret() {
assert_eq!(scalar_poly[0], reconstructed_secret);
}

#[test]
fn test_trusted_dealer_shamir_sharing() {
let secret = s!(42);
let threshold = 3;
let n_shares = 5;

let shares: Vec<_> = poly::scalar::trusted_dealer_shamir_sharing(
secret,
threshold,
n_shares,
&mut rand::thread_rng(),
)
.collect();

// Verify we got the expected number of shares
assert_eq!(shares.len(), n_shares);

// Take threshold shares and reconstruct
let selected_shares = &shares[0..threshold];
let reconstructed = poly::scalar::interpolate_and_eval_poly_at_0(selected_shares);
assert_eq!(reconstructed, secret);

// Test with different subset of shares
let selected_shares = &shares[2..5];
let reconstructed = poly::scalar::interpolate_and_eval_poly_at_0(selected_shares);
assert_eq!(reconstructed, secret);
}

#[test]
fn test_mul_scalar_poly() {
let poly1 = [s!(1), s!(2), s!(3)];
Expand All @@ -136,3 +167,59 @@ fn test_mul_scalar_poly() {

assert_eq!(res, vec![s!(4), s!(13), s!(22), s!(15)]);
}

#[cfg(feature = "proptest")]
mod proptest_tests {
use super::*;
use rand::seq::SliceRandom;

proptest! {
#[test]
fn trusted_dealer_shamir_sharing_reconstruction(
secret_bytes in any::<[u8; 32]>(),
threshold in 2usize..10usize,
extra_shares in 0usize..5usize,
) {
let secret = Scalar::<Secret, Zero>::from_bytes_mod_order(secret_bytes);
let n_shares = threshold + extra_shares;

let shares: Vec<_> = poly::scalar::trusted_dealer_shamir_sharing(
secret,
threshold,
n_shares,
&mut rand::thread_rng()
).collect();

// Verify we got the expected number of shares
prop_assert_eq!(shares.len(), n_shares);

// Verify all share indices are unique and sequential
for (i, (share_index, _)) in shares.iter().enumerate() {
let expected_index = Scalar::<Public, Zero>::from(i + 1).non_zero().expect("> 0");
prop_assert_eq!(*share_index, expected_index);
}

// Test reconstruction with exactly threshold shares
let mut rng = rand::thread_rng();
let selected_shares: Vec<_> = shares
.choose_multiple(&mut rng, threshold)
.cloned()
.collect();

let reconstructed = poly::scalar::interpolate_and_eval_poly_at_0(&selected_shares);
prop_assert_eq!(reconstructed, secret);

// Test reconstruction with more than threshold shares (overdetermined)
if extra_shares > 0 {
let overdetermined_shares: Vec<_> = shares
.choose_multiple(&mut rng, threshold + 1)
.cloned()
.collect();

let reconstructed_overdetermined =
poly::scalar::interpolate_and_eval_poly_at_0(&overdetermined_shares);
prop_assert_eq!(reconstructed_overdetermined, secret);
}
}
}
}
Loading