From b5f00d541b5765ab7c8671f02fe3dd999c3e0604 Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Mon, 7 Oct 2024 10:57:08 +0200 Subject: [PATCH 1/2] secret-sharing/src/shamir/dealer: Remove proactivization --- secret-sharing/src/shamir/dealer.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/secret-sharing/src/shamir/dealer.rs b/secret-sharing/src/shamir/dealer.rs index f583a31028d..955deb5e84f 100644 --- a/secret-sharing/src/shamir/dealer.rs +++ b/secret-sharing/src/shamir/dealer.rs @@ -30,14 +30,6 @@ where Self { poly } } - /// Proactively refreshes the secret polynomial. - pub fn proactivize(&mut self, rng: &mut impl RngCore) { - let deg = self.poly.size() - 1; - let mut poly = Polynomial::random(deg as u8, rng); - poly.to_zero_hole(); - self.poly += poly; - } - /// Generates shares of the secret for the given shareholders. pub fn make_shares(&self, xs: Vec) -> Vec> { xs.into_iter().map(|x| self.make_share(x)).collect() From a78df05e675c6d51609f33f9a8b7fb43a31a329c Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Tue, 8 Oct 2024 13:36:46 +0200 Subject: [PATCH 2/2] secret-sharing/src/shamir: Add proactivization --- .changelog/5885.trivial.md | 0 secret-sharing/src/poly/point.rs | 11 +- secret-sharing/src/shamir/player.rs | 240 +++++++++++++++-------- secret-sharing/src/shamir/shareholder.rs | 34 +++- 4 files changed, 199 insertions(+), 86 deletions(-) create mode 100644 .changelog/5885.trivial.md diff --git a/.changelog/5885.trivial.md b/.changelog/5885.trivial.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/secret-sharing/src/poly/point.rs b/secret-sharing/src/poly/point.rs index 0dfeb0fb1c5..d7d97b3b2c1 100644 --- a/secret-sharing/src/poly/point.rs +++ b/secret-sharing/src/poly/point.rs @@ -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 { +#[derive(Clone)] +pub struct Point { /// The x-coordinate of the point. pub(crate) x: F, /// The y-coordinate of the point. pub(crate) y: F, } -impl Point { +impl Point +where + F: PrimeField, +{ /// Creates a new point. pub fn new(x: F, y: F) -> Self { Self { x, y } @@ -20,6 +24,7 @@ impl Point { /// /// 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 { /// The x-coordinate of the point. pub(crate) x: G::Scalar, diff --git a/secret-sharing/src/shamir/player.rs b/secret-sharing/src/shamir/player.rs index 194767289f6..d305b0bbcb6 100644 --- a/secret-sharing/src/shamir/player.rs +++ b/secret-sharing/src/shamir/player.rs @@ -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::>(); + let shareholders = shares + .into_iter() + .map(|share| Shareholder::new(share)) + .collect::>(); - // 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::>(); - // 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::>(); + + // 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::>(); + let shareholders = shares .into_iter() .map(|share| Shareholder::new(share)) - .collect(); - let key_shares: Vec<_> = shareholders + .collect::>(); + + // Fetch shares. + let shares = shareholders .iter() - .map(|sh| sh.make_key_share::(key_id, dst).unwrap()) - .collect(); - let result = player.recover_key(&key_shares); + .map(|shareholder| shareholder.make_key_share::(key_id, dst).unwrap()) + .collect::>(); + + // 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::(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::(key_id, dst) + .unwrap() + }) + .collect::>(); + + // 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::>(); + let mut shareholders = shares .into_iter() .map(|share| Shareholder::new(share)) - .collect(); - let key_shares: Vec<_> = shareholders + .collect::>(); + + // Fetch shares. + let shares = shareholders .iter() - .map(|sh| sh.make_key_share::(key_id, dst).unwrap()) - .collect(); - let recovered = player.recover_key(&key_shares).unwrap(); - assert_eq!(key, recovered); + .map(|shareholder| shareholder.secret_share()) + .cloned() + .collect::>(); - // 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::>(); + + // Proactivize shares. + for shareholder in shareholders.iter_mut() { + let proactive_shares = dealers + .iter() + .map(|dealer| dealer.make_share(shareholder.secret_share().x)) + .collect::>(); + shareholder.proactivize(&proactive_shares).unwrap(); + } + + // Fetch shares. + let new_shares = shareholders .iter() - .map(|sh| sh.make_key_share::(key_id, dst).unwrap()) - .collect(); - let recovered = player.recover_key(&key_shares).unwrap(); - assert_eq!(key, recovered); + .map(|shareholder| shareholder.secret_share()) + .cloned() + .collect::>(); + + // 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"); } } diff --git a/secret-sharing/src/shamir/shareholder.rs b/secret-sharing/src/shamir/shareholder.rs index 8d21a0abb75..5646d3e191f 100644 --- a/secret-sharing/src/shamir/shareholder.rs +++ b/secret-sharing/src/shamir/shareholder.rs @@ -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 { +pub struct Shareholder { /// Secret share point of the shared secret. share: Point, } -impl Shareholder { +impl Shareholder +where + F: PrimeField, +{ /// Creates a new shareholder with the given secret share. pub fn new(share: Point) -> Self { Self { share } } + + /// Returns secret share. + pub fn secret_share(&self) -> &Point { + &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]) -> 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 PointShareholder for Shareholder