Skip to content

Commit

Permalink
secret-sharing/src/shamir: Add proactivization
Browse files Browse the repository at this point in the history
  • Loading branch information
peternose committed Oct 8, 2024
1 parent b5f00d5 commit a78df05
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 86 deletions.
Empty file added .changelog/5885.trivial.md
Empty file.
11 changes: 8 additions & 3 deletions secret-sharing/src/poly/point.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use group::Group;
use group::{ff::PrimeField, Group};

/// A point (x,y) on a univariate polynomial f(x), where y = f(x).
pub struct Point<F> {
#[derive(Clone)]
pub struct Point<F: PrimeField> {
/// The x-coordinate of the point.
pub(crate) x: F,
/// The y-coordinate of the point.
pub(crate) y: F,
}

impl<F> Point<F> {
impl<F> Point<F>
where
F: PrimeField,
{
/// Creates a new point.
pub fn new(x: F, y: F) -> Self {
Self { x, y }
Expand All @@ -20,6 +24,7 @@ impl<F> Point<F> {
///
/// The y-coordinate is encrypted as z = y * P, where P is typically
/// a hash of an arbitrary-length byte string, e.g., P = H(id).
#[derive(Clone)]
pub struct EncryptedPoint<G: Group> {
/// The x-coordinate of the point.
pub(crate) x: G::Scalar,
Expand Down
240 changes: 159 additions & 81 deletions secret-sharing/src/shamir/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,121 +81,199 @@ mod tests {

#[test]
fn test_shamir() {
// Prepare scheme.
// Prepare parameters.
let threshold = 2;
let num_shareholders = 5;
let secret = PrimeField::from_u64(100);
let dealer = Dealer::new(threshold, secret, &mut OsRng);

// Prepare a player for secret recovery.
let player = Player::new(threshold);
let min_shares = player.min_shares() as u64;
let min_shares = player.min_shares();

// Not enough shares.
let n = min_shares - 1;
let xs = (1..=n).map(PrimeField::from_u64).collect();
let shares = dealer.make_shares(xs);
let result = player.recover_secret(&shares);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "not enough shares");
// Prepare a dealer and distribute shares to shareholders.
let dealer = Dealer::new(threshold, secret, &mut OsRng);
let shares = (1..=num_shareholders)
.map(|x| dealer.make_share(PrimeField::from_u64(x)))
.collect::<Vec<_>>();
let shareholders = shares
.into_iter()
.map(|share| Shareholder::new(share))
.collect::<Vec<_>>();

// Duplicate shares.
let xs = (1..=n)
.flat_map(|x| std::iter::repeat(x).take(2))
.map(PrimeField::from_u64)
.collect();
let shares = dealer.make_shares(xs);
let result = player.recover_secret(&shares);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "not distinct shares");
// Fetch shares.
let shares = shareholders
.iter()
.map(|shareholder| shareholder.secret_share())
.cloned()
.collect::<Vec<_>>();

// Exact number of shares.
let n = min_shares;
let xs = (1..=n).map(PrimeField::from_u64).collect();
let shares = dealer.make_shares(xs);
let recovered = player.recover_secret(&shares).unwrap();
// Recover the secret (exact number of shares).
let recovered = player.recover_secret(&shares[0..min_shares]).unwrap();
assert_eq!(secret, recovered);
let recovered = player.recover_secret(&shares[2..min_shares + 2]).unwrap();
assert_eq!(secret, recovered);

// Too many shares.
let n = min_shares + 10;
let xs = (1..=n).map(PrimeField::from_u64).collect();
let shares = dealer.make_shares(xs);
// Recover the secret (too many shares).
let recovered = player.recover_secret(&shares).unwrap();
assert_eq!(secret, recovered);

// Attempt to recover the secret (not enough shares).
let result = player.recover_secret(&shares[0..min_shares - 1]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "not enough shares");

// Fetch duplicate shares.
let shares = (0..min_shares)
.map(|_| shareholders[0].secret_share())
.cloned()
.collect::<Vec<_>>();

// Attempt to recover the secret (duplicate shares).
let result = player.recover_secret(&shares);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "not distinct shares");
}

#[test]
fn test_kdc() {
// Prepare scheme.
// Prepare parameters.
let threshold = 2;
let num_shareholders = 5;
let secret = PrimeField::from_u64(100);

// Compute the key.
let key_id = b"key id";
let dst = b"encode key share";
let secret = PrimeField::from_u64(100);
let hash = Suite::hash_to_group(key_id, dst).unwrap();
let key = hash * secret;
let dealer = Dealer::new(threshold, secret, &mut OsRng);

// Prepare a player for key recovery.
let player = Player::new(threshold);
let min_shares = player.min_shares() as u64;
let min_shares = player.min_shares();

// Not enough shares.
let n = min_shares - 1;
let xs = (1..=n).map(PrimeField::from_u64).collect();
let shares = dealer.make_shares(xs);
let shareholders: Vec<_> = shares
// Prepare a dealer and distribute shares.
let dealer: Dealer<::p384::Scalar> = Dealer::new(threshold, secret, &mut OsRng);
let shares = (1..=num_shareholders)
.map(|x| dealer.make_share(PrimeField::from_u64(x)))
.collect::<Vec<_>>();
let shareholders = shares
.into_iter()
.map(|share| Shareholder::new(share))
.collect();
let key_shares: Vec<_> = shareholders
.collect::<Vec<_>>();

// Fetch shares.
let shares = shareholders
.iter()
.map(|sh| sh.make_key_share::<Suite>(key_id, dst).unwrap())
.collect();
let result = player.recover_key(&key_shares);
.map(|shareholder| shareholder.make_key_share::<Suite>(key_id, dst).unwrap())
.collect::<Vec<_>>();

// Recover the key (exact number of shares).
let recovered = player.recover_key(&shares[0..min_shares]).unwrap();
assert_eq!(key, recovered);
let recovered = player.recover_key(&shares[2..min_shares + 2]).unwrap();
assert_eq!(key, recovered);

// Recover the key (too many shares).
let recovered = player.recover_key(&shares).unwrap();
assert_eq!(key, recovered);

// Attempt to recover the key (not enough shares).
let result = player.recover_key(&shares[0..min_shares - 1]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "not enough shares");

// Duplicate shares.
let xs = (1..=n)
.flat_map(|x| std::iter::repeat(x).take(2))
.map(PrimeField::from_u64)
.collect();
let shares = dealer.make_shares(xs);
let shareholders: Vec<_> = shares
.into_iter()
.map(|share| Shareholder::new(share))
.collect();
let key_shares: Vec<_> = shareholders
.iter()
.map(|sh| sh.make_key_share::<Suite>(key_id, dst).unwrap())
.collect();
let result = player.recover_key(&key_shares);
// Fetch duplicate shares.
let shares = (0..min_shares)
.map(|_| {
shareholders[0]
.make_key_share::<Suite>(key_id, dst)
.unwrap()
})
.collect::<Vec<_>>();

// Attempt to recover the key (duplicate shares).
let result = player.recover_key(&shares);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "not distinct shares");
}

// Exact number of shares.
let n = min_shares;
let xs = (1..=n).map(PrimeField::from_u64).collect();
let shares = dealer.make_shares(xs);
let shareholders: Vec<_> = shares
#[test]
fn test_proactivization() {
// Prepare parameters.
let threshold = 2;
let num_dealers = 5;
let num_shareholders = 5;
let secret = PrimeField::from_u64(100);

// Prepare a player for secret recovery.
let player = Player::new(threshold);
let min_shares = player.min_shares();

// Prepare a dealer and distribute shares.
let dealer = Dealer::new(threshold, secret, &mut OsRng);
let shares = (1..=num_shareholders)
.map(|x| dealer.make_share(PrimeField::from_u64(x)))
.collect::<Vec<_>>();
let mut shareholders = shares
.into_iter()
.map(|share| Shareholder::new(share))
.collect();
let key_shares: Vec<_> = shareholders
.collect::<Vec<_>>();

// Fetch shares.
let shares = shareholders
.iter()
.map(|sh| sh.make_key_share::<Suite>(key_id, dst).unwrap())
.collect();
let recovered = player.recover_key(&key_shares).unwrap();
assert_eq!(key, recovered);
.map(|shareholder| shareholder.secret_share())
.cloned()
.collect::<Vec<_>>();

// Too many shares.
let n = min_shares + 10;
let xs = (1..=n).map(PrimeField::from_u64).collect();
let shares = dealer.make_shares(xs);
let shareholders: Vec<_> = shares
.into_iter()
.map(|share| Shareholder::new(share))
.collect();
let key_shares: Vec<_> = shareholders
// Recover the secret.
let recovered = player.recover_secret(&shares[0..min_shares]).unwrap();
assert_eq!(secret, recovered);
let recovered = player.recover_secret(&shares[2..min_shares + 2]).unwrap();
assert_eq!(secret, recovered);

// Prepare dealers of proactive shares.
let dealers = (0..num_dealers)
.map(|_| Dealer::new(threshold, PrimeField::ZERO, &mut OsRng))
.collect::<Vec<_>>();

// Proactivize shares.
for shareholder in shareholders.iter_mut() {
let proactive_shares = dealers
.iter()
.map(|dealer| dealer.make_share(shareholder.secret_share().x))
.collect::<Vec<_>>();
shareholder.proactivize(&proactive_shares).unwrap();
}

// Fetch shares.
let new_shares = shareholders
.iter()
.map(|sh| sh.make_key_share::<Suite>(key_id, dst).unwrap())
.collect();
let recovered = player.recover_key(&key_shares).unwrap();
assert_eq!(key, recovered);
.map(|shareholder| shareholder.secret_share())
.cloned()
.collect::<Vec<_>>();

// Recover the secret.
let recovered = player.recover_secret(&new_shares[0..min_shares]).unwrap();
assert_eq!(secret, recovered);
let recovered = player
.recover_secret(&new_shares[2..min_shares + 2])
.unwrap();
assert_eq!(secret, recovered);

// Verify that the shares have changed (brute-force).
for share in &shares {
for new_share in &new_shares {
if share.x == new_share.x {
assert_ne!(share.y, new_share.y, "share hasn't changed");
}
}
}

// Invalid proactive share.
let proactive_share = dealers[0].make_share(shareholders[0].secret_share().x);
let result = shareholders[1].proactivize(&[proactive_share]);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "invalid proactive share");
}
}
34 changes: 32 additions & 2 deletions secret-sharing/src/shamir/shareholder.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
use anyhow::{bail, Result};
use group::ff::PrimeField;

use crate::{kdc::PointShareholder, poly::Point};

/// A holder of a secret share.
pub struct Shareholder<F> {
pub struct Shareholder<F: PrimeField> {
/// Secret share point of the shared secret.
share: Point<F>,
}

impl<F> Shareholder<F> {
impl<F> Shareholder<F>
where
F: PrimeField,
{
/// Creates a new shareholder with the given secret share.
pub fn new(share: Point<F>) -> Self {
Self { share }
}

/// Returns secret share.
pub fn secret_share(&self) -> &Point<F> {
&self.share
}

/// Proactively refreshes the secret share using proactive shares derived
/// from zero-hole polynomials.
///
/// In verifiable secret sharing, the shareholder must verify that the
/// proactive shares were indeed derived from zero-hole polynomials.
pub fn proactivize(&mut self, shares: &[Point<F>]) -> Result<()> {
// Ensure all shares were derived for the correct shareholder.
//
// Can be short-circuit as x-coordinates don't contain sensitive data.
if shares.iter().any(|share| share.x != self.share.x) {
bail!("invalid proactive share");
}

// Proactivize the share.
for share in shares {
self.share.y += share.y;
}

Ok(())
}
}

impl<F> PointShareholder<F> for Shareholder<F>
Expand Down

0 comments on commit a78df05

Please sign in to comment.