forked from novifinancial/tss-ecdsa
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
inv-tshare: Threshold (re-)sharing protocol
- Loading branch information
Aurélien Nicolas
committed
Jul 20, 2024
1 parent
dbe9f29
commit b0d074b
Showing
11 changed files
with
1,813 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.