Skip to content

Commit

Permalink
inv-tshare: Threshold (re-)sharing protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
Aurélien Nicolas committed Jul 20, 2024
1 parent dbe9f29 commit b0d074b
Show file tree
Hide file tree
Showing 11 changed files with 1,813 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/broadcast/participant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub(crate) struct BroadcastParticipant {
pub(crate) enum BroadcastTag {
AuxinfoR1CommitHash,
KeyGenR1CommitHash,
TshareR1CommitHash,
KeyRefreshR1CommitHash,
PresignR1Ciphertexts,
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ pub mod presign;
mod protocol;
mod ring_pedersen;
pub mod sign;
pub mod tshare;
mod utils;
mod zkp;
mod zkstar;
Expand Down
31 changes: 31 additions & 0 deletions src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub enum MessageType {
Auxinfo(AuxinfoMessageType),
/// Keygen messages
Keygen(KeygenMessageType),
/// Tshare messages
Tshare(TshareMessageType),
/// Keyrefresh messages
Keyrefresh(KeyrefreshMessageType),
/// Presign messages
Expand Down Expand Up @@ -68,6 +70,22 @@ pub enum KeygenMessageType {
R3Proof,
}

/// An enum consisting of all tshare message types
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum TshareMessageType {
/// Signal to self that we're ready to run the protocol
Ready,
/// A hash commitment to the public keyshare and associated proofs
R1CommitHash,
/// The information committed to in Round 1
R2Decommit,
/// A proof of knowledge of the discrete log of the value decommitted in
/// Round 2
R3Proofs,
/// The encrypted private share from a participant to another.
R3PrivateShare,
}

/// An enum consisting of all keyrefresh message types
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum KeyrefreshMessageType {
Expand Down Expand Up @@ -211,4 +229,17 @@ impl Message {
}
Ok(())
}

/// Check if the message type is one of the valid options.
pub(crate) fn check_one_of_type(&self, expected_types: &[MessageType]) -> Result<()> {
if !expected_types.contains(&self.message_type()) {
error!(
"A message was misrouted. Expected one of {:?}, Got {:?}",
expected_types,
self.message_type()
);
return Err(InternalError::InternalInvariantFailed);
}
Ok(())
}
}
7 changes: 7 additions & 0 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use tracing::{error, info, instrument, trace};
#[derive(Debug)]
pub enum ProtocolType {
Keygen,
Tshare,
Keyrefresh,
AuxInfo,
Presign,
Expand Down Expand Up @@ -168,6 +169,7 @@ impl<P: ProtocolParticipant> Participant<P> {
match (message.message_type(), P::protocol_type()) {
(MessageType::Auxinfo(_), ProtocolType::AuxInfo)
| (MessageType::Keygen(_), ProtocolType::Keygen)
| (MessageType::Tshare(_), ProtocolType::Tshare)
| (MessageType::Keyrefresh(_), ProtocolType::Keyrefresh)
| (MessageType::Presign(_), ProtocolType::Presign)
| (MessageType::Sign(_), ProtocolType::Sign)
Expand Down Expand Up @@ -420,6 +422,11 @@ impl ParticipantIdentifier {
pub fn from_u128(id: u128) -> Self {
Self(id)
}

/// Get the ID as a number.
pub fn as_u128(&self) -> u128 {
self.0
}
}

/// The `SharedContext` contains fixed known parameters across the entire
Expand Down
139 changes: 139 additions & 0 deletions src/tshare/commit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// Modifications Copyright (c) 2022-2023 Bolt Labs Holdings, Inc
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.

use super::share::CoeffPublic;
use crate::{
errors::{InternalError, Result},
messages::{Message, MessageType, TshareMessageType},
protocol::{Identifier, ParticipantIdentifier},
utils::CurvePoint,
};
use merlin::Transcript;
use rand::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use tracing::error;

/// Public commitment to `TshareDecommit` in round 1.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub(crate) struct TshareCommit {
hash: [u8; 32],
}
impl TshareCommit {
pub(crate) fn from_message(message: &Message) -> Result<Self> {
message.check_type(MessageType::Tshare(TshareMessageType::R1CommitHash))?;
let tshare_commit: TshareCommit = deserialize!(&message.unverified_bytes)?;
Ok(tshare_commit)
}
}

/// Decommitment published in round 2.
#[derive(Serialize, Deserialize, Clone)]
pub(crate) struct TshareDecommit {
sid: Identifier,
sender: ParticipantIdentifier,
u_i: [u8; 32], // The blinding factor is never read but it is included in the commitment.
pub rid: [u8; 32],
pub coeff_publics: Vec<CoeffPublic>,
pub As: Vec<CurvePoint>,
}

impl TshareDecommit {
///`sid` corresponds to a unique session identifier.
pub(crate) fn new<R: RngCore + CryptoRng>(
rng: &mut R,
sid: &Identifier,
sender: &ParticipantIdentifier,
coeff_publics: Vec<CoeffPublic>,
sch_precoms: Vec<CurvePoint>,
) -> Self {
let mut rid = [0u8; 32];
let mut u_i = [0u8; 32];
rng.fill_bytes(rid.as_mut_slice());
rng.fill_bytes(u_i.as_mut_slice());
Self {
sid: *sid,
sender: *sender,
rid,
u_i,
coeff_publics,
As: sch_precoms,
}
}

/// Deserialize a TshareDecommit from a message and verify it.
pub(crate) fn from_message(
message: &Message,
com: &TshareCommit,
threshold: usize,
) -> Result<Self> {
message.check_type(MessageType::Tshare(TshareMessageType::R2Decommit))?;
let tshare_decommit: TshareDecommit = deserialize!(&message.unverified_bytes)?;
tshare_decommit.verify(message.id(), message.from(), com, threshold)?;
Ok(tshare_decommit)
}

pub(crate) fn commit(&self) -> Result<TshareCommit> {
let mut transcript = Transcript::new(b"TshareR1");
transcript.append_message(b"decom", &serialize!(&self)?);
let mut hash = [0u8; 32];
transcript.challenge_bytes(b"hashing r1", &mut hash);
Ok(TshareCommit { hash })
}

/// Verify this TshareDecommit against a commitment and expected
/// content.
fn verify(
&self,
sid: Identifier,
sender: ParticipantIdentifier,
com: &TshareCommit,
threshold: usize,
) -> Result<()> {
// Check the commitment.
let rebuilt_com = self.commit()?;
if &rebuilt_com != com {
error!("decommitment does not match original commitment");
return Err(InternalError::ProtocolError(Some(sender)));
}

// Check the session ID and sender ID.
if self.sid != sid {
error!("Incorrect session ID");
return Err(InternalError::ProtocolError(Some(sender)));
}
if self.sender != sender {
error!("Incorrect sender ID");
return Err(InternalError::ProtocolError(Some(sender)));
}

// Check the number of commitments As.
if self.As.len() != threshold {
error!("Incorrect number of As");
return Err(InternalError::ProtocolError(Some(sender)));
}

// Check the set of coefficients.
if self.coeff_publics.len() != threshold {
error!("Incorrect number of public shares");
return Err(InternalError::ProtocolError(Some(sender)));
}

Ok(())
}
}

// Implement custom Debug to avoid leaking secret information.
impl std::fmt::Debug for TshareDecommit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TshareDecommit")
.field("sid", &self.sid)
.field("sender", &self.sender)
.field("...", &"[redacted]")
.finish()
}
}
138 changes: 138 additions & 0 deletions src/tshare/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright (c) 2022-2023 Bolt Labs Holdings, Inc
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.
use std::collections::HashSet;

use tracing::error;

use crate::{
auxinfo::{self, AuxInfoPrivate, AuxInfoPublic},
errors::{CallerError, InternalError, Result},
ParticipantConfig, ParticipantIdentifier,
};

use super::share::CoeffPrivate;

/// Input needed for a
/// [`TshareParticipant`](crate::tshare::TshareParticipant) to run.
#[derive(Debug, Clone)]
pub struct Input {
/// How many parties are needed to sign.
threshold: usize,
/// An additive share to turn into Shamir sharing.
/// Or None to generate a random share.
share: Option<CoeffPrivate>,
/// The auxiliary info to encrypt/decrypt messages with other participants.
auxinfo_output: auxinfo::Output,
}

impl Input {
/// Creates a new [`Input`] from the outputs of the
/// [`auxinfo`](crate::auxinfo::AuxInfoParticipant) and
/// [`keygen`](crate::keygen::KeygenParticipant) protocols.
pub fn new(auxinfo_output: auxinfo::Output, threshold: usize) -> Result<Self> {
// The constructor for auxinfo output already check other important
// properties, like that the private component maps to one of public
// components for each one.
Ok(Self {
auxinfo_output,
share: None,
threshold,
})
}

pub fn share(&self) -> Option<&CoeffPrivate> {
self.share.as_ref()
}

pub fn threshold(&self) -> usize {
self.threshold
}

fn auxinfo_pids(&self) -> HashSet<ParticipantIdentifier> {
self.auxinfo_output
.public_auxinfo()
.iter()
.map(AuxInfoPublic::participant)
.collect()
}

// Check the consistency of participant IDs.
pub(crate) fn check_participant_config(&self, config: &ParticipantConfig) -> Result<()> {
let config_pids = config
.all_participants()
.iter()
.cloned()
.collect::<HashSet<_>>();
if config_pids != self.auxinfo_pids() {
error!("Public auxinfo and participant inputs weren't from the same set of parties.");
Err(CallerError::BadInput)?
}

if config.id() != self.auxinfo_output.private_pid()? {
error!("Expected private auxinfo output and tshare input to correspond to the same participant, but they didn't");
Err(CallerError::BadInput)?
}

Ok(())
}

pub(crate) fn private_auxinfo(&self) -> &AuxInfoPrivate {
self.auxinfo_output.private_auxinfo()
}

/// Returns the [`AuxInfoPublic`] associated with the given
/// [`ParticipantIdentifier`].
pub(crate) fn find_auxinfo_public(&self, pid: ParticipantIdentifier) -> Result<&AuxInfoPublic> {
self.auxinfo_output.find_public(pid)
.ok_or_else(|| {
error!("Presign input doesn't contain a public auxinfo for {}, even though we checked for it at construction.", pid);
InternalError::InternalInvariantFailed
})
}
}

#[cfg(test)]
mod test {
use super::{super::TshareParticipant, Input};
use crate::{
auxinfo,
errors::{CallerError, InternalError, Result},
keygen,
utils::testing::init_testing,
Identifier, ParticipantConfig, ProtocolParticipant,
};

#[test]
fn protocol_participants_must_match_input_participants() -> Result<()> {
let rng = &mut init_testing();
let SIZE = 5;

// Create valid input set with random PIDs
let config = ParticipantConfig::random(5, rng);
let auxinfo_output = auxinfo::Output::simulate(&config.all_participants(), rng);
let input = Input::new(auxinfo_output, 2)?;

// Create valid config with PIDs independent of those used to make the input set
let config = ParticipantConfig::random(SIZE, rng);

let result = TshareParticipant::new(
Identifier::random(rng),
config.id(),
config.other_ids().to_vec(),
input,
);
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
InternalError::CallingApplicationMistake(CallerError::BadInput)
);

Ok(())
}

// TODO: tests for other invalid inputs.
}
16 changes: 16 additions & 0 deletions src/tshare/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Types and functions related to key refresh sub-protocol.
// Copyright (c) Facebook, Inc. and its affiliates.
// Modifications Copyright (c) 2022-2023 Bolt Labs Holdings, Inc
//
// This source code is licensed under both the MIT license found in the
// LICENSE-MIT file in the root directory of this source tree and the Apache
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
// of this source tree.

mod commit;
mod input;
mod output;
mod participant;
mod share;

pub use participant::TshareParticipant;
Loading

0 comments on commit b0d074b

Please sign in to comment.