From 9c911a54ef5a143533cf50f2e5fb97b67d73b24c Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Fri, 30 Aug 2024 11:33:55 +0200 Subject: [PATCH 1/4] secret-sharing: Support trait std::error::Error in p384 --- secret-sharing/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/secret-sharing/Cargo.toml b/secret-sharing/Cargo.toml index 611eda4cda4..a8fab6f8e69 100644 --- a/secret-sharing/Cargo.toml +++ b/secret-sharing/Cargo.toml @@ -8,7 +8,10 @@ edition = "2018" anyhow = { version = "1.0" } group = { version = "0.13", default-features = false } honggfuzz = { version = "0.5" } -p384 = { version = "0.13", default-features = false, features = ["hash2curve"] } +p384 = { version = "0.13", default-features = false, features = [ + "std", + "hash2curve", +] } rand = { version = "0.8" } rand_core = { version = "0.6" } sha3 = { version = "0.10" } From 6cb04970d392ebd2d44291c3bc8f294dc1f720a1 Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Thu, 29 Aug 2024 10:14:13 +0200 Subject: [PATCH 2/4] secret-sharing/src/churp: Separate handoff kinds --- .changelog/5847.trivial.md | 0 keymanager/src/churp/handler.rs | 44 ++- secret-sharing/src/churp/handoff.rs | 451 +++++++++++++++------------- 3 files changed, 267 insertions(+), 228 deletions(-) create mode 100644 .changelog/5847.trivial.md diff --git a/.changelog/5847.trivial.md b/.changelog/5847.trivial.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/keymanager/src/churp/handler.rs b/keymanager/src/churp/handler.rs index 06c3706c370..b8e0752a493 100644 --- a/keymanager/src/churp/handler.rs +++ b/keymanager/src/churp/handler.rs @@ -33,7 +33,10 @@ use oasis_core_runtime::{ }; use secret_sharing::{ - churp::{encode_shareholder, Dealer, Handoff, HandoffKind, Shareholder, VerifiableSecretShare}, + churp::{ + encode_shareholder, CommitteeChanged, CommitteeUnchanged, Dealer, DealingPhase, Handoff, + HandoffKind, Shareholder, VerifiableSecretShare, + }, kdc::KeySharer, poly::{scalar_from_bytes, scalar_to_bytes}, suites::{p384, Suite}, @@ -114,10 +117,10 @@ struct HandoffInfo { /// The handoff epoch. epoch: EpochTime, /// The handoff associated with this information. - handoff: Arc>, + handoff: Arc>>, } -pub(crate) trait Handler { +pub(crate) trait Handler: Send + Sync { /// Returns the verification matrix of the shared secret bivariate /// polynomial from the last successfully completed handoff. /// @@ -340,7 +343,7 @@ pub struct Churp { churp_state: ChurpState, /// Cached instances. - instances: Mutex>>, + instances: Mutex>>, /// Cached verified policies. policies: Arc, } @@ -369,11 +372,7 @@ impl Churp { } } - fn get_instance( - &self, - churp_id: u8, - runtime_id: Namespace, - ) -> Result> { + fn get_instance(&self, churp_id: u8, runtime_id: Namespace) -> Result> { // Ensure runtime_id matches. if self.runtime_id != runtime_id { return Err(Error::RuntimeMismatch.into()); @@ -597,7 +596,7 @@ impl Instance { &self, node_id: PublicKey, status: &Status, - handoff: &Handoff, + handoff: &Arc>>, client: &RemoteClient, ) -> Result { let x = encode_shareholder::(&node_id.0, &self.shareholder_dst)?; @@ -652,7 +651,7 @@ impl Instance { &self, node_id: PublicKey, status: &Status, - handoff: &Handoff, + handoff: &Arc>>, client: &RemoteClient, ) -> Result { let x = encode_shareholder::(&node_id.0, &self.shareholder_dst)?; @@ -686,7 +685,7 @@ impl Instance { &self, node_id: PublicKey, status: &Status, - handoff: &Handoff, + handoff: &Arc>>, client: &RemoteClient, ) -> Result { let x = encode_shareholder::(&node_id.0, &self.shareholder_dst)?; @@ -930,7 +929,7 @@ impl Instance { } /// Returns the handoff for the given epoch. - fn get_handoff(&self, epoch: EpochTime) -> Result>> { + fn get_handoff(&self, epoch: EpochTime) -> Result>>> { let handoff_guard = self.handoff.lock().unwrap(); let handoff_info = handoff_guard @@ -943,7 +942,7 @@ impl Instance { /// Creates a handoff for the next handoff epoch. If a handoff already /// exists, the existing one is returned. - fn get_or_create_handoff(&self, status: &Status) -> Result>> { + fn get_or_create_handoff(&self, status: &Status) -> Result>>> { // Make sure to lock the handoff so that we don't create two handoffs // for the same epoch. let mut handoff_guard = self.handoff.lock().unwrap(); @@ -965,8 +964,21 @@ impl Instance { shareholders.push(x); } let kind = Self::handoff_kind(status); - let handoff = Handoff::new(threshold, me, shareholders, kind)?; - let handoff = Arc::new(handoff); + let handoff: Arc>> = match kind { + HandoffKind::DealingPhase => { + Arc::new(Box::new(DealingPhase::new(threshold, me, shareholders)?)) + } + HandoffKind::CommitteeUnchanged => Arc::new(Box::new(CommitteeUnchanged::new( + threshold, + me, + shareholders, + )?)), + HandoffKind::CommitteeChanged => Arc::new(Box::new(CommitteeChanged::new( + threshold, + me, + shareholders, + )?)), + }; // If the committee hasn't changed, we need the latest shareholder // to randomize its share. diff --git a/secret-sharing/src/churp/handoff.rs b/secret-sharing/src/churp/handoff.rs index c05acc5931c..437b68d616b 100644 --- a/secret-sharing/src/churp/handoff.rs +++ b/secret-sharing/src/churp/handoff.rs @@ -78,255 +78,293 @@ impl HandoffKind { } } -/// Handoff proactivizes the shared secret (changes associated shares) while -/// transferring the secret from an old committee to a new, possibly -/// intersecting one. -pub struct Handoff { - /// Handoff kind. - kind: HandoffKind, +/// Handoff generates a new shared secret and distributes the associated +/// shares among committee members, or proactivizes an existing secret by +/// randomizing the shares while transferring the secret from an old committee +/// to a new, possibly intersecting one. +pub trait Handoff: Send + Sync { + /// Checks if the handoff needs the verification matrix from the previous + /// handoff. + fn needs_verification_matrix(&self) -> Result { + Err(Error::InvalidKind.into()) + } - /// The share reduction phase of the handoff. - share_reduction: Option>, + /// Sets the verification matrix from the previous handoff. + fn set_verification_matrix(&self, _vm: VerificationMatrix) -> Result<()> { + Err(Error::InvalidKind.into()) + } + + /// Checks if the handoff needs the shareholder from the previous handoff. + fn needs_shareholder(&self) -> Result { + Err(Error::InvalidKind.into()) + } + + /// Sets the shareholder from the previous handoff. + fn set_shareholder(&self, _shareholder: Arc>) -> Result<()> { + Err(Error::InvalidKind.into()) + } + + /// Checks if share reduction needs a switch point from the given + /// shareholder. + fn needs_share_reduction_switch_point(&self, _x: &G::Scalar) -> Result { + Err(Error::InvalidKind.into()) + } + + /// Adds the given switch point to share reduction. + fn add_share_reduction_switch_point(&self, _x: G::Scalar, _bij: G::Scalar) -> Result { + Err(Error::InvalidKind.into()) + } + + /// Checks if full share distribution needs a switch point from the given + /// shareholder. + fn needs_full_share_distribution_switch_point(&self, _x: &G::Scalar) -> Result { + Err(Error::InvalidKind.into()) + } + + /// Adds the given switch point to full share distribution. + fn add_full_share_distribution_switch_point( + &self, + _x: G::Scalar, + _bij: G::Scalar, + ) -> Result { + Err(Error::InvalidKind.into()) + } + + /// Checks if bivariate share is needed from the given shareholder. + fn needs_bivariate_share(&self, _x: &G::Scalar) -> Result { + Err(Error::InvalidKind.into()) + } + + /// Adds the given bivariate share. + fn add_bivariate_share( + &self, + _x: &G::Scalar, + _verifiable_share: VerifiableSecretShare, + ) -> Result { + Err(Error::InvalidKind.into()) + } + + /// Returns the shareholder resulting from share reduction. + fn get_reduced_shareholder(&self) -> Result>> { + Err(Error::InvalidKind.into()) + } + + /// Returns the shareholder resulting from full share distribution. + fn get_full_shareholder(&self) -> Result>> { + Err(Error::InvalidKind.into()) + } +} +/// A handoff where the committee collaboratively generates a random secret +/// and secret shares. +pub struct DealingPhase { /// The share distribution phase of the handoff. - share_distribution: Option>, + share_distribution: DimensionSwitch, } -impl Handoff +impl DealingPhase where G: Group + GroupEncoding, { - /// Creates a new handoff using the given shareholders (new committee) - /// to proactivize the shared secret. - pub fn new( - threshold: u8, - me: G::Scalar, - shareholders: Vec, - kind: HandoffKind, - ) -> Result { - let (share_reduction, share_distribution) = match kind { - HandoffKind::DealingPhase => { - let share_distribution = DimensionSwitch::new_full_share_distribution( - threshold, - me, - shareholders, - kind, - )?; - share_distribution.skip_accumulating()?; - share_distribution.start_merging(None)?; - - (None, Some(share_distribution)) - } - HandoffKind::CommitteeUnchanged => { - let share_distribution = DimensionSwitch::new_full_share_distribution( - threshold, - me, - shareholders, - kind, - )?; - share_distribution.skip_accumulating()?; - - (None, Some(share_distribution)) - } - HandoffKind::CommitteeChanged => { - let share_reduction = - DimensionSwitch::new_share_reduction(threshold, me, shareholders, kind)?; - - let share_distribution = DimensionSwitch::new_full_share_distribution( - threshold, - me, - Vec::new(), // Skip proactivization. - kind, - )?; - - (Some(share_reduction), Some(share_distribution)) - } - }; - - Ok(Self { - kind, - share_reduction, - share_distribution, - }) + /// Creates a new handoff where the given shareholders will generate + /// a random secret and receive corresponding secret shares. + pub fn new(threshold: u8, me: G::Scalar, shareholders: Vec) -> Result { + let share_distribution = DimensionSwitch::new_full_share_distribution( + threshold, + me, + shareholders, + HandoffKind::DealingPhase, + )?; + share_distribution.skip_accumulating()?; + share_distribution.start_merging(None)?; + + Ok(Self { share_distribution }) } +} - /// Checks if the handoff needs the verification matrix from the previous - /// handoff. - pub fn needs_verification_matrix(&self) -> Result { - if self.kind != HandoffKind::CommitteeChanged { - return Err(Error::InvalidKind.into()); - } +impl Handoff for DealingPhase +where + G: Group + GroupEncoding, +{ + fn needs_bivariate_share(&self, x: &G::Scalar) -> Result { + self.share_distribution.needs_bivariate_share(x) + } - let needs = self - .share_reduction - .as_ref() - .ok_or(Error::InvalidState)? - .is_waiting_for_verification_matrix(); + fn add_bivariate_share( + &self, + x: &G::Scalar, + verifiable_share: VerifiableSecretShare, + ) -> Result { + self.share_distribution + .add_bivariate_share(x, verifiable_share) + } - Ok(needs) + fn get_full_shareholder(&self) -> Result>> { + self.share_distribution.get_shareholder() } +} - /// Sets the verification matrix from the previous handoff. - pub fn set_verification_matrix(&self, vm: VerificationMatrix) -> Result<()> { - if self.kind != HandoffKind::CommitteeChanged { - return Err(Error::InvalidKind.into()); - } +/// A handoff where the committee remains the same. During this handoff, +/// committee members randomize their secret shares without altering +/// the shared secret. +pub struct CommitteeUnchanged { + /// The share distribution phase of the handoff. + share_distribution: DimensionSwitch, +} - self.share_reduction - .as_ref() - .ok_or(Error::InvalidState)? - .start_accumulating(vm) +impl CommitteeUnchanged +where + G: Group + GroupEncoding, +{ + /// Creates a new handoff where the secret shares of the given shareholders + /// will be randomized. + pub fn new(threshold: u8, me: G::Scalar, shareholders: Vec) -> Result { + let share_distribution = DimensionSwitch::new_full_share_distribution( + threshold, + me, + shareholders, + HandoffKind::CommitteeUnchanged, + )?; + share_distribution.skip_accumulating()?; + + Ok(Self { share_distribution }) } +} - /// Checks if the handoff needs the shareholder from the previous handoff. - pub fn needs_shareholder(&self) -> Result { - if self.kind != HandoffKind::CommitteeUnchanged { - return Err(Error::InvalidKind.into()); - } - - let needs = self - .share_distribution - .as_ref() - .ok_or(Error::InvalidState)? - .is_waiting_for_shareholder(); +impl Handoff for CommitteeUnchanged +where + G: Group + GroupEncoding, +{ + fn needs_shareholder(&self) -> Result { + Ok(self.share_distribution.is_waiting_for_shareholder()) + } - Ok(needs) + fn set_shareholder(&self, shareholder: Arc>) -> Result<()> { + self.share_distribution.start_merging(Some(shareholder)) } - /// Sets the shareholder from the previous handoff. - pub fn set_shareholder(&self, shareholder: Arc>) -> Result<()> { - if self.kind != HandoffKind::CommitteeUnchanged { - return Err(Error::InvalidKind.into()); - } + fn needs_bivariate_share(&self, x: &G::Scalar) -> Result { + self.share_distribution.needs_bivariate_share(x) + } + fn add_bivariate_share( + &self, + x: &G::Scalar, + verifiable_share: VerifiableSecretShare, + ) -> Result { self.share_distribution - .as_ref() - .ok_or(Error::InvalidState)? - .start_merging(Some(shareholder)) + .add_bivariate_share(x, verifiable_share) } - /// Checks if share reduction needs a switch point from the given - /// shareholder. - pub fn needs_share_reduction_switch_point(&self, x: &G::Scalar) -> Result { - if self.kind != HandoffKind::CommitteeChanged { - return Err(Error::InvalidKind.into()); - } + fn get_full_shareholder(&self) -> Result>> { + self.share_distribution.get_shareholder() + } +} + +/// A handoff where the committee changes. During this handoff, committee +/// members transfer the shared secret to the new committee. +pub struct CommitteeChanged { + /// The share reduction phase of the handoff. + share_reduction: DimensionSwitch, + + /// The share distribution phase of the handoff. + share_distribution: DimensionSwitch, +} + +impl CommitteeChanged +where + G: Group + GroupEncoding, +{ + /// Creates a new handoff where the shared secret will be transferred + /// to a new committee composed of the given shareholders. + pub fn new(threshold: u8, me: G::Scalar, shareholders: Vec) -> Result { + let share_reduction = DimensionSwitch::new_share_reduction( + threshold, + me, + shareholders, + HandoffKind::CommitteeChanged, + )?; + + let share_distribution = DimensionSwitch::new_full_share_distribution( + threshold, + me, + Vec::new(), // Skip proactivization. + HandoffKind::CommitteeChanged, + )?; - self.share_reduction - .as_ref() - .ok_or(Error::InvalidState)? - .needs_switch_point(x) + Ok(Self { + share_reduction, + share_distribution, + }) } +} - /// Adds the given switch point to share reduction. - pub fn add_share_reduction_switch_point(&self, x: G::Scalar, bij: G::Scalar) -> Result { - if self.kind != HandoffKind::CommitteeChanged { - return Err(Error::InvalidKind.into()); - } +impl Handoff for CommitteeChanged +where + G: Group + GroupEncoding, +{ + fn needs_verification_matrix(&self) -> Result { + Ok(self.share_reduction.is_waiting_for_verification_matrix()) + } - self.share_reduction - .as_ref() - .ok_or(Error::InvalidState)? - .add_switch_point(x, bij) + fn set_verification_matrix(&self, vm: VerificationMatrix) -> Result<()> { + self.share_reduction.start_accumulating(vm) } - /// Checks if full share distribution needs a switch point from the given - /// shareholder. - pub fn needs_full_share_distribution_switch_point(&self, x: &G::Scalar) -> Result { - if self.kind != HandoffKind::CommitteeChanged { - return Err(Error::InvalidKind.into()); - } + fn needs_share_reduction_switch_point(&self, x: &G::Scalar) -> Result { + self.share_reduction.needs_switch_point(x) + } - self.share_distribution - .as_ref() - .ok_or(Error::InvalidState)? - .needs_switch_point(x) + fn add_share_reduction_switch_point(&self, x: G::Scalar, bij: G::Scalar) -> Result { + self.share_reduction.add_switch_point(x, bij) } - /// Adds the given switch point to full share distribution. - pub fn add_full_share_distribution_switch_point( + fn needs_full_share_distribution_switch_point(&self, x: &G::Scalar) -> Result { + self.share_distribution.needs_switch_point(x) + } + + fn add_full_share_distribution_switch_point( &self, x: G::Scalar, bij: G::Scalar, ) -> Result { - if self.kind != HandoffKind::CommitteeChanged { - return Err(Error::InvalidKind.into()); - } - - self.share_distribution - .as_ref() - .ok_or(Error::InvalidState)? - .add_switch_point(x, bij) + self.share_distribution.add_switch_point(x, bij) } - /// Checks if bivariate share is needed from the given shareholder. - pub fn needs_bivariate_share(&self, x: &G::Scalar) -> Result { - let ds = match self.kind { - HandoffKind::DealingPhase => &self.share_distribution, - HandoffKind::CommitteeUnchanged => &self.share_distribution, - HandoffKind::CommitteeChanged => &self.share_reduction, - }; - - ds.as_ref() - .ok_or(Error::InvalidState)? - .needs_bivariate_share(x) + fn needs_bivariate_share(&self, x: &G::Scalar) -> Result { + self.share_reduction.needs_bivariate_share(x) } - /// Adds the given bivariate share. - pub fn add_bivariate_share( + fn add_bivariate_share( &self, x: &G::Scalar, verifiable_share: VerifiableSecretShare, ) -> Result { - let ds = match self.kind { - HandoffKind::DealingPhase => &self.share_distribution, - HandoffKind::CommitteeUnchanged => &self.share_distribution, - HandoffKind::CommitteeChanged => &self.share_reduction, - }; - - let res = ds - .as_ref() - .ok_or(Error::InvalidState)? - .add_bivariate_share(x, verifiable_share); + let done = self + .share_reduction + .add_bivariate_share(x, verifiable_share)?; // Start full share distribution if share reduction has completed. - if self.kind == HandoffKind::CommitteeChanged && res.as_ref().is_ok_and(|&done| done) { + if done { let vm = self .share_reduction - .as_ref() - .ok_or(Error::InvalidState)? .get_shareholder()? .verifiable_share() .verification_matrix() .clone(); - self.share_distribution - .as_ref() - .ok_or(Error::InvalidState)? - .start_accumulating(vm)?; + self.share_distribution.start_accumulating(vm)?; } - res + Ok(done) } - /// Returns the shareholder resulting from share reduction. - pub fn get_reduced_shareholder(&self) -> Result>> { - if self.kind != HandoffKind::CommitteeChanged { - return Err(Error::InvalidKind.into()); - } - - self.share_reduction - .as_ref() - .ok_or(Error::InvalidState)? - .get_shareholder() + fn get_reduced_shareholder(&self) -> Result>> { + self.share_reduction.get_shareholder() } - /// Returns the shareholder resulting from full share distribution. - pub fn get_full_shareholder(&self) -> Result>> { - self.share_distribution - .as_ref() - .ok_or(Error::InvalidState)? - .get_shareholder() + fn get_full_shareholder(&self) -> Result>> { + self.share_distribution.get_shareholder() } } @@ -337,7 +375,7 @@ mod tests { use rand::{rngs::StdRng, RngCore, SeedableRng}; use crate::{ - churp::{self, HandoffKind, VerifiableSecretShare}, + churp::{self, Handoff, HandoffKind, VerifiableSecretShare}, suites::{self, p384}, }; @@ -346,7 +384,9 @@ mod tests { type PrimeField = ::PrimeField; type Shareholder = churp::Shareholder; type Dealer = churp::Dealer; - type Handoff = churp::Handoff; + type DealingPhase = churp::DealingPhase; + type CommitteeUnchanged = churp::CommitteeUnchanged; + type CommitteeChanged = churp::CommitteeChanged; fn prepare_shareholders(ids: &[u64]) -> Vec { ids.into_iter().map(|&id| id.into()).collect() @@ -389,17 +429,16 @@ mod tests { let threshold = 2; // Handoff 0: Dealing phase. - let kind = HandoffKind::DealingPhase; let committee = prepare_shareholders(&[1, 2, 3, 4]); // At least 4 (threshold + 2). let dealers = prepare_dealers(threshold, true, committee.len(), &mut rng); let mut handoffs = Vec::with_capacity(committee.len()); for alice in committee.iter() { - let handoff = Handoff::new(threshold, alice.clone(), committee.clone(), kind).unwrap(); + let handoff = DealingPhase::new(threshold, alice.clone(), committee.clone()).unwrap(); // Proactivization. for (j, (bob, dealer)) in zip(committee.iter(), dealers.iter()).enumerate() { - let share = dealer.make_share(alice.clone(), kind); + let share = dealer.make_share(alice.clone(), HandoffKind::DealingPhase); let vm = dealer.verification_matrix().clone(); let verifiable_share = VerifiableSecretShare::new(share, vm); @@ -434,18 +473,12 @@ mod tests { verify_shareholders(&shareholders, threshold, true); // Handoff 1: Committee remains unchanged. - let kind = HandoffKind::CommitteeUnchanged; let dealers = prepare_dealers(threshold, false, committee.len(), &mut rng); let mut handoffs = Vec::with_capacity(committee.len()); for (i, alice) in committee.iter().enumerate() { - let handoff = Handoff::new( - threshold, - alice.clone(), - committee.clone(), - HandoffKind::CommitteeUnchanged, - ) - .unwrap(); + let handoff = + CommitteeUnchanged::new(threshold, alice.clone(), committee.clone()).unwrap(); let shareholder = shareholders.get(i).unwrap().clone(); @@ -454,7 +487,7 @@ mod tests { // Proactivization. for (j, (bob, dealer)) in zip(committee.iter(), dealers.iter()).enumerate() { - let share = dealer.make_share(alice.clone(), kind); + let share = dealer.make_share(alice.clone(), HandoffKind::CommitteeUnchanged); let vm = dealer.verification_matrix().clone(); let verifiable_share = VerifiableSecretShare::new(share, vm); @@ -489,19 +522,13 @@ mod tests { verify_shareholders(&shareholders, threshold, true); // Handoff 2: Committee changed. - let kind = HandoffKind::CommitteeChanged; let committee = prepare_shareholders(&[3, 4, 5, 6, 7]); // At least 5 (2 * threshold + 1). let dealers = prepare_dealers(threshold, false, committee.len(), &mut rng); let mut handoffs = Vec::with_capacity(committee.len()); for alice in committee.iter() { - let handoff = Handoff::new( - threshold, - alice.clone(), - committee.clone(), - HandoffKind::CommitteeChanged, - ) - .unwrap(); + let handoff = + CommitteeChanged::new(threshold, alice.clone(), committee.clone()).unwrap(); // Fetch verification matrix from the old committee. assert!(handoff.needs_verification_matrix().unwrap()); @@ -535,7 +562,7 @@ mod tests { // Proactivization. for (j, (bob, dealer)) in zip(committee.iter(), dealers.iter()).enumerate() { - let share = dealer.make_share(alice.clone(), kind); + let share = dealer.make_share(alice.clone(), HandoffKind::CommitteeChanged); let vm = dealer.verification_matrix().clone(); let verifiable_share = VerifiableSecretShare::new(share, vm); From c9a6c4535ede4720dbcc01129c834954884c4f80 Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Thu, 5 Sep 2024 11:31:48 +0200 Subject: [PATCH 3/4] secret-sharing/src/churp: Verify the number of shareholders --- secret-sharing/src/churp/handoff.rs | 18 ++++++++++++++++++ secret-sharing/src/churp/switch.rs | 17 ++++------------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/secret-sharing/src/churp/handoff.rs b/secret-sharing/src/churp/handoff.rs index 437b68d616b..87509b1190b 100644 --- a/secret-sharing/src/churp/handoff.rs +++ b/secret-sharing/src/churp/handoff.rs @@ -169,12 +169,21 @@ where /// Creates a new handoff where the given shareholders will generate /// a random secret and receive corresponding secret shares. pub fn new(threshold: u8, me: G::Scalar, shareholders: Vec) -> Result { + // The number of shareholders must be at least threshold t + 2, + // ensuring that even if t Byzantine dealers reveal their secret, + // an honest shareholder cannot compute the combined bivariate + // polynomial. + if shareholders.len() < threshold as usize + 2 { + return Err(Error::NotEnoughShareholders.into()); + } + let share_distribution = DimensionSwitch::new_full_share_distribution( threshold, me, shareholders, HandoffKind::DealingPhase, )?; + share_distribution.skip_accumulating()?; share_distribution.start_merging(None)?; @@ -219,12 +228,17 @@ where /// Creates a new handoff where the secret shares of the given shareholders /// will be randomized. pub fn new(threshold: u8, me: G::Scalar, shareholders: Vec) -> Result { + if shareholders.len() < threshold as usize + 1 { + return Err(Error::NotEnoughShareholders.into()); + } + let share_distribution = DimensionSwitch::new_full_share_distribution( threshold, me, shareholders, HandoffKind::CommitteeUnchanged, )?; + share_distribution.skip_accumulating()?; Ok(Self { share_distribution }) @@ -278,6 +292,10 @@ where /// Creates a new handoff where the shared secret will be transferred /// to a new committee composed of the given shareholders. pub fn new(threshold: u8, me: G::Scalar, shareholders: Vec) -> Result { + if shareholders.len() < threshold as usize + 1 { + return Err(Error::NotEnoughShareholders.into()); + } + let share_reduction = DimensionSwitch::new_share_reduction( threshold, me, diff --git a/secret-sharing/src/churp/switch.rs b/secret-sharing/src/churp/switch.rs index af6a470648d..ca24399a0db 100644 --- a/secret-sharing/src/churp/switch.rs +++ b/secret-sharing/src/churp/switch.rs @@ -494,16 +494,7 @@ where handoff: HandoffKind, shareholder: Option>>, ) -> Result { - // During the dealing phase, the number of shares must be at least - // threshold + 2, ensuring that even if t Byzantine dealers reveal - // their secret, an honest shareholder cannot compute the combined - // bivariate polynomial. - let min = match handoff { - HandoffKind::DealingPhase => threshold as usize + 2, - HandoffKind::CommitteeUnchanged => 1, - HandoffKind::CommitteeChanged => 1, - }; - if shareholders.len() < min { + if shareholders.is_empty() { return Err(Error::NotEnoughShareholders.into()); } @@ -775,13 +766,13 @@ mod tests { let me = prepare_shareholder(1); let shareholders = prepare_shareholders(&[1, 2, 3]); - // Dealing phase requires at least threshold + 2 dealers. + // There should be at least 1 shareholder. let res = BivariateShares::::new( threshold, me, - shareholders.clone(), + vec![], DimensionSwitchKind::ShareReduction, - HandoffKind::DealingPhase, + hkind, None, ); assert!(res.is_err()); From 5b5a30f5c5b44a1e44b3e4e60c684cbbf2c4dcdc Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Tue, 10 Sep 2024 14:11:52 +0200 Subject: [PATCH 4/4] secret-sharing/src/churp: Decouple switch points and handoff kind --- secret-sharing/src/churp/handoff.rs | 36 ++---- secret-sharing/src/churp/switch.rs | 190 +++++++++++----------------- 2 files changed, 87 insertions(+), 139 deletions(-) diff --git a/secret-sharing/src/churp/handoff.rs b/secret-sharing/src/churp/handoff.rs index 87509b1190b..8ba1e40b594 100644 --- a/secret-sharing/src/churp/handoff.rs +++ b/secret-sharing/src/churp/handoff.rs @@ -177,12 +177,9 @@ where return Err(Error::NotEnoughShareholders.into()); } - let share_distribution = DimensionSwitch::new_full_share_distribution( - threshold, - me, - shareholders, - HandoffKind::DealingPhase, - )?; + let zero_hole = HandoffKind::DealingPhase.require_zero_hole(); + let share_distribution = + DimensionSwitch::new_full_share_distribution(threshold, zero_hole, me, shareholders)?; share_distribution.skip_accumulating()?; share_distribution.start_merging(None)?; @@ -232,12 +229,9 @@ where return Err(Error::NotEnoughShareholders.into()); } - let share_distribution = DimensionSwitch::new_full_share_distribution( - threshold, - me, - shareholders, - HandoffKind::CommitteeUnchanged, - )?; + let zero_hole = HandoffKind::CommitteeUnchanged.require_zero_hole(); + let share_distribution = + DimensionSwitch::new_full_share_distribution(threshold, zero_hole, me, shareholders)?; share_distribution.skip_accumulating()?; @@ -296,19 +290,11 @@ where return Err(Error::NotEnoughShareholders.into()); } - let share_reduction = DimensionSwitch::new_share_reduction( - threshold, - me, - shareholders, - HandoffKind::CommitteeChanged, - )?; - - let share_distribution = DimensionSwitch::new_full_share_distribution( - threshold, - me, - Vec::new(), // Skip proactivization. - HandoffKind::CommitteeChanged, - )?; + let zero_hole = HandoffKind::CommitteeChanged.require_zero_hole(); + let share_reduction = + DimensionSwitch::new_share_reduction(threshold, zero_hole, me, shareholders)?; + let share_distribution = + DimensionSwitch::new_full_share_distribution(threshold, zero_hole, me, Vec::new())?; Ok(Self { share_reduction, diff --git a/secret-sharing/src/churp/switch.rs b/secret-sharing/src/churp/switch.rs index ca24399a0db..bd4c8e90c93 100644 --- a/secret-sharing/src/churp/switch.rs +++ b/secret-sharing/src/churp/switch.rs @@ -8,31 +8,7 @@ use crate::{ vss::{VerificationMatrix, VerificationVector}, }; -use super::{Error, HandoffKind, SecretShare, Shareholder, VerifiableSecretShare}; - -/// Dimension switch kind. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum DimensionSwitchKind { - /// In share reduction, shareholders switch from the degree-t dimension - /// of the secret bivariate polynomial B(x,y) to the degree-2t dimension. - /// As a result, each shareholders in the new committee obtains a reduced - /// share B(x,j) and proactivizes it to B'(x,j). - ShareReduction, - /// In full share distribution, new shares B'(i,y) are generated from - /// proactive reduced shares, by switching back to the degree-t dimension - /// of B'(x,y). - FullShareDistribution, -} - -impl DimensionSwitchKind { - /// Indicates whether bivariate shares should be full or reduced shares. - pub fn require_full_shares(&self) -> bool { - match &self { - DimensionSwitchKind::ShareReduction => false, - DimensionSwitchKind::FullShareDistribution => true, - } - } -} +use super::{Error, SecretShare, Shareholder, VerifiableSecretShare}; /// Dimension switch state. enum DimensionSwitchState @@ -76,11 +52,12 @@ where /// The degree of the secret-sharing polynomial. threshold: u8, - /// The kind of handoff. - handoff: HandoffKind, + /// Indicates whether bivariate shares should be derived from a zero-hole + /// bivariate polynomial. + zero_hole: bool, - /// The kind of dimension switch. - kind: DimensionSwitchKind, + /// Indicates whether bivariate shares should be full or reduced shares. + full_share: bool, /// The encoded identity. me: G::Scalar, @@ -98,41 +75,48 @@ where G: Group + GroupEncoding, { /// Creates a new share reduction dimension switch. + /// + /// In share reduction, shareholders switch from the degree-t dimension + /// of the secret bivariate polynomial B(x,y) to the degree-2t dimension. + /// As a result, each shareholders in the new committee obtains a reduced + /// share B(x,j) and proactivizes it to B'(x,j). pub(crate) fn new_share_reduction( threshold: u8, + zero_hole: bool, me: G::Scalar, shareholders: Vec, - handoff: HandoffKind, ) -> Result { - let kind = DimensionSwitchKind::ShareReduction; - Self::new(threshold, me, shareholders, kind, handoff) + Self::new(threshold, zero_hole, false, me, shareholders) } /// Creates a new full share distribution dimension switch. + /// + /// In full share distribution, new shares B'(i,y) are generated from + /// proactive reduced shares, by switching back to the degree-t dimension + /// of B'(x,y). pub(crate) fn new_full_share_distribution( threshold: u8, + zero_hole: bool, me: G::Scalar, shareholders: Vec, - handoff: HandoffKind, ) -> Result { - let kind = DimensionSwitchKind::FullShareDistribution; - Self::new(threshold, me, shareholders, kind, handoff) + Self::new(threshold, zero_hole, true, me, shareholders) } /// Creates a new dimension switch. fn new( threshold: u8, + zero_hole: bool, + full_share: bool, me: G::Scalar, shareholders: Vec, - kind: DimensionSwitchKind, - handoff: HandoffKind, ) -> Result { let state = Mutex::new(DimensionSwitchState::WaitingForVerificationMatrix); Ok(Self { threshold, - kind, - handoff, + zero_hole, + full_share, me, shareholders, state, @@ -166,7 +150,7 @@ where _ => return Err(Error::InvalidState.into()), } - let sp = SwitchPoints::new(self.threshold, self.me, vm, self.kind)?; + let sp = SwitchPoints::new(self.threshold, self.full_share, self.me, vm)?; *state = DimensionSwitchState::Accumulating(sp); Ok(()) @@ -206,10 +190,10 @@ where } else { let bs = BivariateShares::new( self.threshold, + self.zero_hole, + self.full_share, self.me, self.shareholders.clone(), - self.kind, - self.handoff, Some(shareholder), )?; *state = DimensionSwitchState::Merging(bs); @@ -236,10 +220,10 @@ where let bs = BivariateShares::new( self.threshold, + self.zero_hole, + self.full_share, self.me, self.shareholders.clone(), - self.kind, - self.handoff, shareholder, )?; *state = DimensionSwitchState::Merging(bs); @@ -340,9 +324,9 @@ where /// Creates a new accumulator for switch points. fn new( threshold: u8, + full_share: bool, me: G::Scalar, vm: VerificationMatrix, - kind: DimensionSwitchKind, ) -> Result { let threshold = threshold as usize; let rows = threshold + 1; @@ -354,17 +338,9 @@ where // Precomputing the verification vector speeds up switch point // validation. - let (n, vv) = match kind { - DimensionSwitchKind::ShareReduction => { - let vv = vm.verification_vector_for_x(&me); - let n = rows; - (n, vv) - } - DimensionSwitchKind::FullShareDistribution => { - let vv = vm.verification_vector_for_y(&me); - let n = cols; - (n, vv) - } + let (n, vv) = match full_share { + false => (rows, vm.verification_vector_for_x(&me)), + true => (cols, vm.verification_vector_for_y(&me)), }; // Wrap the identifier and the matrix in an option so that we can take @@ -456,9 +432,6 @@ where /// The degree of the secret-sharing polynomial. threshold: u8, - /// Field element representing the identity of the shareholder. - me: G::Scalar, - /// Indicates whether bivariate shares should be derived from a zero-hole /// bivariate polynomial. zero_hole: bool, @@ -466,19 +439,23 @@ where /// Indicates whether bivariate shares should be full or reduced shares. full_share: bool, + /// Field element representing the identity of the shareholder. + me: G::Scalar, + /// A set of shareholders providing bivariate shares. shareholders: Vec, + /// A set of shareholders whose bivariate share still needs to be received. pending_shareholders: Vec, + /// The shareholder to be proactivized with bivariate shares. + shareholder: Option>>, + /// The sum of the received bivariate shares. p: Option>, /// The sum of the verification matrices of the received bivariate shares. vm: Option>, - - /// The shareholder to be proactivized with bivariate shares. - shareholder: Option>>, } impl BivariateShares @@ -488,10 +465,10 @@ where /// Creates a new accumulator for bivariate shares. fn new( threshold: u8, + zero_hole: bool, + full_share: bool, me: G::Scalar, shareholders: Vec, - kind: DimensionSwitchKind, - handoff: HandoffKind, shareholder: Option>>, ) -> Result { if shareholders.is_empty() { @@ -499,19 +476,17 @@ where } let pending_shareholders = shareholders.clone(); - let zero_hole = handoff.require_zero_hole(); - let full_share = kind.require_full_shares(); Ok(Self { threshold, - me, zero_hole, full_share, + me, shareholders, pending_shareholders, + shareholder, p: None, vm: None, - shareholder, }) } @@ -600,13 +575,13 @@ mod tests { use rand::{rngs::StdRng, SeedableRng}; use crate::{ - churp::{HandoffKind, SecretShare, VerifiableSecretShare}, + churp::{SecretShare, VerifiableSecretShare}, poly, suites::{self, p384}, vss, }; - use super::{BivariateShares, DimensionSwitchKind, Error, SwitchPoints}; + use super::{BivariateShares, Error, SwitchPoints}; type Suite = p384::Sha3_384; type Group = ::Group; @@ -627,13 +602,13 @@ mod tests { sh: u64, bp: &BivariatePolynomial, sp: &mut SwitchPoints, - kind: DimensionSwitchKind, + full_share: bool, ) -> Result { let x = prepare_shareholder(sh); let y = prepare_shareholder(me); - let bij = match kind { - DimensionSwitchKind::ShareReduction => bp.eval(&x, &y), - DimensionSwitchKind::FullShareDistribution => bp.eval(&y, &x), + let bij = match full_share { + false => bp.eval(&x, &y), + true => bp.eval(&y, &x), }; let res = sp.add_point(x, bij); res @@ -650,16 +625,13 @@ mod tests { let vm = VerificationMatrix::from(&bp); let me = prepare_shareholder(1); - for kind in vec![ - DimensionSwitchKind::ShareReduction, - DimensionSwitchKind::FullShareDistribution, - ] { - let mut sp = SwitchPoints::::new(threshold, me, vm.clone(), kind).unwrap(); + for full_share in vec![false, true] { + let mut sp = SwitchPoints::::new(threshold, full_share, me, vm.clone()).unwrap(); let me = 1; let mut sh = 2; // Add invalid point (switch x and y). - let res = add_point(sh, me, &bp, &mut sp, kind); + let res = add_point(sh, me, &bp, &mut sp, full_share); assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), @@ -667,18 +639,18 @@ mod tests { ); // Add point. - let res = add_point(me, me, &bp, &mut sp, kind); + let res = add_point(me, me, &bp, &mut sp, full_share); assert!(res.is_ok()); assert!(!res.unwrap()); // Add another point twice. assert!(sp.needs_point(&prepare_shareholder(sh))); - let res = add_point(me, sh, &bp, &mut sp, kind); + let res = add_point(me, sh, &bp, &mut sp, full_share); assert!(res.is_ok()); assert!(!res.unwrap()); - let res = add_point(me, sh, &bp, &mut sp, kind); + let res = add_point(me, sh, &bp, &mut sp, full_share); assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), @@ -699,9 +671,9 @@ mod tests { } // Full share distribution needs 2 * threshold points. - if kind == DimensionSwitchKind::FullShareDistribution { + if full_share { for _ in 0..threshold { - let res = add_point(me, sh, &bp, &mut sp, kind); + let res = add_point(me, sh, &bp, &mut sp, full_share); assert!(res.is_ok()); assert!(!res.unwrap()); sh += 1; @@ -709,7 +681,7 @@ mod tests { } // Add the last point. - let res = add_point(me, sh, &bp, &mut sp, kind); + let res = add_point(me, sh, &bp, &mut sp, full_share); assert!(res.is_ok()); assert!(res.unwrap()); // Enough points. sh += 1; @@ -718,7 +690,7 @@ mod tests { assert!(!sp.needs_point(&prepare_shareholder(sh))); // Too many points. - let res = add_point(me, sh, &bp, &mut sp, kind); + let res = add_point(me, sh, &bp, &mut sp, full_share); assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), @@ -733,24 +705,24 @@ mod tests { fn add_bivariate_shares( threshold: u8, + zero_hole: bool, + full_share: bool, me: u64, sh: u64, bs: &mut BivariateShares, - dkind: DimensionSwitchKind, - hkind: HandoffKind, ) -> Result { let deg_x = threshold; let deg_y = 2 * threshold; let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]); let mut bp = BivariatePolynomial::random(deg_x, deg_y, &mut rng); - if hkind.require_zero_hole() { + if zero_hole { bp.to_zero_hole(); }; let vm = VerificationMatrix::from(&bp); let x = prepare_shareholder(me); - let p = match dkind { - DimensionSwitchKind::ShareReduction => bp.eval_y(&x), - DimensionSwitchKind::FullShareDistribution => bp.eval_x(&x), + let p = match full_share { + false => bp.eval_y(&x), + true => bp.eval_x(&x), }; let share = SecretShare::new(x, p); let verifiable_share = VerifiableSecretShare::new(share, vm); @@ -761,20 +733,13 @@ mod tests { #[test] fn test_bivariate_shares() { let threshold = 2; - let hkind = HandoffKind::CommitteeChanged; + let zero_hole = true; let me = prepare_shareholder(1); let shareholders = prepare_shareholders(&[1, 2, 3]); // There should be at least 1 shareholder. - let res = BivariateShares::::new( - threshold, - me, - vec![], - DimensionSwitchKind::ShareReduction, - hkind, - None, - ); + let res = BivariateShares::::new(threshold, false, false, me, vec![], None); assert!(res.is_err()); unsafe { assert_eq!( @@ -784,16 +749,13 @@ mod tests { } // Happy path. - for dkind in vec![ - DimensionSwitchKind::ShareReduction, - DimensionSwitchKind::FullShareDistribution, - ] { + for full_share in vec![false, true] { let mut bs = BivariateShares::::new( threshold, + zero_hole, + full_share, me, shareholders.clone(), - dkind, - hkind, None, ) .unwrap(); @@ -802,7 +764,7 @@ mod tests { let mut sh = 2; // Add invalid share (invalid threshold). - let res = add_bivariate_shares(threshold + 1, me, me, &mut bs, dkind, hkind); + let res = add_bivariate_shares(threshold + 1, zero_hole, full_share, me, me, &mut bs); assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), @@ -810,18 +772,18 @@ mod tests { ); // Add share. - let res = add_bivariate_shares(threshold, me, me, &mut bs, dkind, hkind); + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, me, &mut bs); assert!(res.is_ok()); assert!(!res.unwrap()); // Add another share twice. assert!(bs.needs_bivariate_share(&prepare_shareholder(sh))); - let res = add_bivariate_shares(threshold, me, sh, &mut bs, dkind, hkind); + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); assert!(res.is_ok()); assert!(!res.unwrap()); - let res = add_bivariate_shares(threshold, me, sh, &mut bs, dkind, hkind); + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(), @@ -842,7 +804,7 @@ mod tests { } // Add the last share. - let res = add_bivariate_shares(threshold, me, sh, &mut bs, dkind, hkind); + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); assert!(res.is_ok()); assert!(res.unwrap()); // Enough shares. sh += 1; @@ -850,7 +812,7 @@ mod tests { // Unknown shareholder. assert!(!bs.needs_bivariate_share(&prepare_shareholder(sh))); - let res = add_bivariate_shares(threshold, me, sh, &mut bs, dkind, hkind); + let res = add_bivariate_shares(threshold, zero_hole, full_share, me, sh, &mut bs); assert!(res.is_err()); assert_eq!( res.unwrap_err().to_string(),