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);