Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

secret-sharing/src/churp: Implement handoff #5617

Merged
merged 5 commits into from
Apr 3, 2024
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
1 change: 1 addition & 0 deletions .changelog/5617.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
secret-sharing/src/churp: Implement handoff
10 changes: 7 additions & 3 deletions go/keymanager/churp/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ type Status struct {
// and key derivation.
GroupID uint8 `json:"group_id"`

// Threshold is the minimum number of distinct shares required
// to reconstruct a key.
// Threshold represents the degree of the secret-sharing polynomial.
//
// In a (t,n) secret-sharing scheme, where t represents the threshold,
// any combination of t+1 or more shares can reconstruct the secret,
// while losing n-t or fewer shares still allows the secret to be
// recovered.
Threshold uint8 `json:"threshold"`

// ActiveHandoff is the epoch of the last successfully executed handoff.
Expand Down Expand Up @@ -69,7 +73,7 @@ type Status struct {
// Committee is a vector of nodes holding a share of the secret
// in the active handoff.
//
// A client needs to obtain at least a threshold number of key shares
// A client needs to obtain more than a threshold number of key shares
// from the nodes in this vector to construct the key.
Committee []signature.PublicKey `json:"committee,omitempty"`

Expand Down
2 changes: 0 additions & 2 deletions keymanager/src/churp/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,4 @@ pub enum Error {
RuntimeMismatch,
#[error("status not published")]
StatusNotPublished,
#[error("zero threshold")]
ZeroThreshold,
}
3 changes: 0 additions & 3 deletions keymanager/src/churp/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,6 @@ impl Churp {
if status.next_handoff != req.handoff {
return Err(Error::HandoffMismatch.into());
}
if status.threshold == 0 {
return Err(Error::ZeroThreshold.into());
}
if status.next_handoff == HANDOFFS_DISABLED {
return Err(Error::HandoffsDisabled.into());
}
Expand Down
57 changes: 38 additions & 19 deletions keymanager/src/churp/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,20 @@ mod tests {
.load_bivariate_polynomial::<p384::Scalar>(churp_id, handoff)
.expect("bivariate polynomial should be loaded")
.expect("bivariate polynomial should exist");
assert_eq!(polynomial, restored);
assert!(polynomial == restored);

// Non-existing ID.
let restored = storage
.load_bivariate_polynomial::<p384::Scalar>(churp_id + 1, handoff)
.expect("bivariate polynomial should be loaded");
assert_eq!(None, restored);
assert!(None == restored);

// Invalid handoff, decryption should fail.
storage
.load_bivariate_polynomial::<p384::Scalar>(churp_id, handoff + 1)
.expect_err("decryption of bivariate polynomial should fail");
let res = storage.load_bivariate_polynomial::<p384::Scalar>(churp_id, handoff + 1);
assert!(
res.is_err(),
"decryption of bivariate polynomial should fail"
);

// Manipulate local storage.
let right_key = Storage::create_bivariate_polynomial_key(churp_id);
Expand All @@ -198,14 +200,18 @@ mod tests {
.expect("bivariate polynomial should be stored");

// Invalid ID, decryption should fail.
storage
.load_bivariate_polynomial::<p384::Scalar>(churp_id + 1, handoff)
.expect_err("decryption of bivariate polynomial should fail");
let res = storage.load_bivariate_polynomial::<p384::Scalar>(churp_id + 1, handoff);
assert!(
res.is_err(),
"decryption of bivariate polynomial should fail"
);

// Corrupted ciphertext, decryption should fail.
storage
.load_bivariate_polynomial::<p384::Scalar>(churp_id, handoff)
.expect_err("decryption of bivariate polynomial should fail");
let res = storage.load_bivariate_polynomial::<p384::Scalar>(churp_id, handoff);
assert!(
res.is_err(),
"decryption of bivariate polynomial should fail"
);
}

#[test]
Expand All @@ -222,26 +228,39 @@ mod tests {

// Invalid ID, decryption should fail.
let mut ciphertext = Storage::encrypt_bivariate_polynomial(&polynomial, churp_id, handoff);
Storage::decrypt_bivariate_polynomial::<p384::Scalar>(
let res = Storage::decrypt_bivariate_polynomial::<p384::Scalar>(
&mut ciphertext,
churp_id + 1,
handoff,
)
.expect_err("decryption of bivariate polynomial should fail");
);
assert!(
res.is_err(),
"decryption of bivariate polynomial should fail"
);

// Invalid handoff, decryption should fail.
let mut ciphertext = Storage::encrypt_bivariate_polynomial(&polynomial, churp_id, handoff);
Storage::decrypt_bivariate_polynomial::<p384::Scalar>(
let res = Storage::decrypt_bivariate_polynomial::<p384::Scalar>(
&mut ciphertext,
churp_id,
handoff + 1,
)
.expect_err("decryption of bivariate polynomial should fail");
);
assert!(
res.is_err(),
"decryption of bivariate polynomial should fail"
);

// Corrupted ciphertext, decryption should fail.
let mut ciphertext = Storage::encrypt_bivariate_polynomial(&polynomial, churp_id, handoff);
(ciphertext[0], _) = ciphertext[0].overflowing_add(1);
Storage::decrypt_bivariate_polynomial::<p384::Scalar>(&mut ciphertext, churp_id, handoff)
.expect_err("decryption of bivariate polynomial should fail");
let res = Storage::decrypt_bivariate_polynomial::<p384::Scalar>(
&mut ciphertext,
churp_id,
handoff,
);
assert!(
res.is_err(),
"decryption of bivariate polynomial should fail"
);
}
}
9 changes: 7 additions & 2 deletions runtime/src/consensus/keymanager/churp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ pub struct Status {
/// and key derivation.
pub group_id: GroupID,

/// The minimum number of distinct shares required to reconstruct a key.
/// The degree of the secret-sharing polynomial.
///
/// In a (t,n) secret-sharing scheme, where t represents the threshold,
/// any combination of t+1 or more shares can reconstruct the secret,
/// while losing n-t or fewer shares still allows the secret to be
/// recovered.
pub threshold: u8,

/// The epoch of the last successfully executed handoff.
Expand All @@ -75,7 +80,7 @@ pub struct Status {

/// A vector of nodes holding a share of the secret in the active handoff.
///
/// A client needs to obtain at least a threshold number of key shares
/// A client needs to obtain more than a threshold number of key shares
/// from the nodes in this vector to construct the key.
#[cbor(optional)]
pub committee: Vec<PublicKey>,
Expand Down
39 changes: 22 additions & 17 deletions secret-sharing/src/churp/dealer.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
//! CHURP dealer.

use anyhow::{anyhow, Result};
use anyhow::Result;

use group::{
ff::{Field, PrimeField},
Group, GroupEncoding,
};
use group::{ff::PrimeField, Group, GroupEncoding};
use rand_core::RngCore;

use crate::vss::{
matrix::VerificationMatrix,
polynomial::{BivariatePolynomial, Polynomial},
};

use super::Error;
use super::{Error, HandoffKind};

/// Shareholder identifier.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
Expand All @@ -37,9 +34,8 @@ pub trait DealerParams {
///
/// Shares must always be distributed over a secure channel and verified
/// against the matrix. Reconstructing the secret bivariate polynomial
/// requires obtaining at least a threshold number of shares from distinct
/// requires obtaining more than a threshold number of shares from distinct
/// participants.
#[derive(Debug, Clone)]
pub struct Dealer<D: DealerParams> {
/// Secret bivariate polynomial.
bp: BivariatePolynomial<D::PrimeField>,
Expand All @@ -54,8 +50,8 @@ where
{
/// Creates a new dealer.
pub fn new(threshold: u8, dealing_phase: bool, rng: &mut impl RngCore) -> Self {
let dx = threshold.saturating_sub(1); // Handle threshold 0 as 1.
let dy = 2 * dx;
let dx = threshold;
let dy = 2 * threshold;

match dealing_phase {
true => Dealer::random(dx, dy, rng),
Expand Down Expand Up @@ -86,12 +82,20 @@ where
&self.vm
}

// Returns a secret share for the given recipient.
pub fn share(&self, recipient: &D::PrimeField) -> Result<Polynomial<D::PrimeField>> {
if recipient.is_zero().into() {
return Err(anyhow!("zero-value recipient not allowed"));
}
Ok(self.bp.eval_x(recipient))
/// Returns a secret share for the given shareholder.
pub fn derive_bivariate_share(
&self,
id: Shareholder,
kind: HandoffKind,
) -> Result<Polynomial<D::PrimeField>> {
let v = D::encode_shareholder(id)?;
let p = match kind {
HandoffKind::DealingPhase => self.bp.eval_x(&v),
HandoffKind::CommitteeChanged => self.bp.eval_y(&v),
HandoffKind::CommitteeUnchanged => self.bp.eval_x(&v),
};

Ok(p)
}
}

Expand All @@ -110,6 +114,7 @@ where
pub type NistP384Dealer = Dealer<NistP384>;

/// NIST P-384 dealer parameters.
#[derive(Debug)]
pub struct NistP384;

impl DealerParams for NistP384 {
Expand Down Expand Up @@ -139,7 +144,7 @@ mod tests {
fn test_new() {
let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]);

let threshold = 3;
let threshold = 2;
for dealing_phase in vec![true, false] {
let dealer = NistP384Dealer::new(threshold, dealing_phase, &mut rng);
assert_eq!(dealer.verification_matrix().is_zero_hole(), !dealing_phase);
Expand Down
30 changes: 30 additions & 0 deletions secret-sharing/src/churp/errors.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("duplicate shareholder")]
DuplicateShareholder,
#[error("invalid handoff kind")]
InvalidKind,
#[error("invalid polynomial")]
InvalidPolynomial,
#[error("invalid switch point")]
InvalidSwitchPoint,
#[error("invalid state")]
InvalidState,
#[error("not enough bivariate shares")]
NotEnoughBivariateShares,
#[error("not enough shareholders")]
NotEnoughShareholders,
#[error("not enough switch points")]
NotEnoughSwitchPoints,
#[error("merging not finished")]
MergingNotFinished,
#[error("polynomial degree mismatch")]
PolynomialDegreeMismatch,
#[error("shareholder encoding failed")]
ShareholderEncodingFailed,
#[error("too many switch points")]
TooManySwitchPoints,
#[error("unknown shareholder")]
UnknownShareholder,
#[error("verification matrix dimension mismatch")]
VerificationMatrixDimensionMismatch,
#[error("verification matrix zero-hole mismatch")]
VerificationMatrixZeroHoleMismatch,
#[error("verification matrix required")]
VerificationMatrixRequired,
#[error("zero value shareholder")]
ZeroValueShareholder,
}
Loading
Loading