WIP add some state machine stuff from dvc
This commit is contained in:
parent
e1fb9bc953
commit
89b2f8842a
|
@ -8,5 +8,6 @@ members = [
|
|||
"frost-ristretto255",
|
||||
"frost-secp256k1",
|
||||
"frost-rerandomized",
|
||||
"gencode"
|
||||
"gencode",
|
||||
"tss",
|
||||
]
|
||||
|
|
|
@ -30,6 +30,26 @@ use super::compute_lagrange_coefficient;
|
|||
pub mod dkg;
|
||||
pub mod repairable;
|
||||
|
||||
/// Sum the commitments from all peers into a group commitment.
|
||||
#[cfg_attr(feature = "internals", visibility::make(pub))]
|
||||
pub(crate) fn compute_group_commitment<C: Ciphersuite>(
|
||||
commitments: &[VerifiableSecretSharingCommitment<C>],
|
||||
) -> VerifiableSecretSharingCommitment<C> {
|
||||
let mut group_commitment = vec![
|
||||
CoefficientCommitment(<C::Group>::identity());
|
||||
commitments.first().expect("Put a match here").0.len()
|
||||
];
|
||||
for commitment in commitments {
|
||||
for i in 0..group_commitment.len() {
|
||||
*group_commitment.get_mut(i).expect("asdf") = CoefficientCommitment(
|
||||
group_commitment.get(i).expect("PUt a match here").value()
|
||||
+ commitment.0.get(i).expect("put a match here").value(),
|
||||
);
|
||||
}
|
||||
}
|
||||
VerifiableSecretSharingCommitment(group_commitment)
|
||||
}
|
||||
|
||||
/// Sum the commitments from all participants in a distributed key generation
|
||||
/// run into a single group commitment.
|
||||
#[cfg_attr(feature = "internals", visibility::make(pub))]
|
||||
|
|
|
@ -35,8 +35,8 @@ use std::{collections::BTreeMap, iter};
|
|||
use rand_core::{CryptoRng, RngCore};
|
||||
|
||||
use crate::{
|
||||
Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature,
|
||||
SigningKey, VerifyingKey,
|
||||
compute_group_commitment, Challenge, Ciphersuite, Element, Error, Field, Group, Header,
|
||||
Identifier, Scalar, Signature, SigningKey, VerifyingKey,
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
|
|
@ -10,9 +10,13 @@ use crate::{
|
|||
use debugless_unwrap::DebuglessUnwrap;
|
||||
use rand_core::{CryptoRng, RngCore};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::keys::{generate_with_dealer, IdentifierList, PublicKeyPackage};
|
||||
use crate::Ciphersuite;
|
||||
use crate::keys::{
|
||||
compute_group_commitment, generate_with_dealer, reconstruct, IdentifierList, KeyPackage,
|
||||
PublicKeyPackage, SecretShare, SigningShare, VerifyingShare,
|
||||
};
|
||||
use crate::{Ciphersuite, Field, VerifyingKey};
|
||||
|
||||
/// Test serialize VerifiableSecretSharingCommitment
|
||||
pub fn check_serialize_vss_commitment<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R) {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "tss"
|
||||
authors = [ "Dev <Dev@analog.one>" ]
|
||||
edition = "2021"
|
||||
homepage = "https://www.analog.one/"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/Analog-Labs/tesseract/"
|
||||
version = "0.0.1"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
frost-evm = { git = "https://github.com/analog-labs/frost-evm.git", version = "0.1.0", features = ["serde"] }
|
||||
log = "0.4.20"
|
||||
rand = "0.8"
|
||||
serde = { version = "1.0.183", features = [ "derive" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.10.0"
|
|
@ -0,0 +1,159 @@
|
|||
use frost_evm::frost_secp256k1::Signature;
|
||||
use frost_evm::keys::dkg::*;
|
||||
use frost_evm::keys::{
|
||||
KeyPackage, PublicKeyPackage, SecretShare, SigningShare, VerifiableSecretSharingCommitment,
|
||||
};
|
||||
use frost_evm::{Identifier, Scalar};
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DkgAction {
|
||||
Commit(VerifiableSecretSharingCommitment, Signature),
|
||||
Send(Vec<(Identifier, DkgMessage)>),
|
||||
Complete(KeyPackage, PublicKeyPackage, VerifiableSecretSharingCommitment),
|
||||
Failure,
|
||||
}
|
||||
|
||||
/// Tss message.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct DkgMessage(round2::Package);
|
||||
|
||||
impl std::fmt::Display for DkgMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "dkg")
|
||||
}
|
||||
}
|
||||
|
||||
/// Distributed key generation state machine.
|
||||
pub struct Dkg {
|
||||
id: Identifier,
|
||||
members: BTreeSet<Identifier>,
|
||||
threshold: u16,
|
||||
secret_package: Option<round1::SecretPackage>,
|
||||
commitment: Option<VerifiableSecretSharingCommitment>,
|
||||
sent_round2_packages: bool,
|
||||
round2_packages: HashMap<Identifier, round2::Package>,
|
||||
}
|
||||
|
||||
impl Dkg {
|
||||
pub fn new(id: Identifier, members: BTreeSet<Identifier>, threshold: u16) -> Self {
|
||||
debug_assert!(members.contains(&id));
|
||||
Self {
|
||||
id,
|
||||
members,
|
||||
threshold,
|
||||
secret_package: None,
|
||||
commitment: None,
|
||||
sent_round2_packages: false,
|
||||
round2_packages: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_commit(&mut self, commitment: VerifiableSecretSharingCommitment) {
|
||||
self.commitment = Some(commitment);
|
||||
}
|
||||
|
||||
pub fn on_message(&mut self, peer: Identifier, msg: DkgMessage) {
|
||||
self.round2_packages.insert(peer, msg.0);
|
||||
}
|
||||
|
||||
pub fn next_action(&mut self) -> Option<DkgAction> {
|
||||
let Some(secret_package) = self.secret_package.as_ref() else {
|
||||
let (secret_package, round1_package) = match part1(self.id, self.members.len() as _, self.threshold, OsRng) {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
log::error!("dkg failed with {:?}", error);
|
||||
return Some(DkgAction::Failure)
|
||||
}
|
||||
};
|
||||
self.secret_package = Some(secret_package);
|
||||
return Some(DkgAction::Commit(round1_package.commitment().clone(), *round1_package.proof_of_knowledge()));
|
||||
};
|
||||
let Some(commitment) = self.commitment.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
if !self.sent_round2_packages {
|
||||
let mut msgs = Vec::with_capacity(self.members.len());
|
||||
for peer in &self.members {
|
||||
if *peer == self.id {
|
||||
continue;
|
||||
}
|
||||
let share = SigningShare::from_coefficients(secret_package.coefficients(), *peer);
|
||||
msgs.push((*peer, DkgMessage(round2::Package::new(share))));
|
||||
}
|
||||
self.sent_round2_packages = true;
|
||||
return Some(DkgAction::Send(msgs));
|
||||
}
|
||||
if self.round2_packages.len() != self.members.len() - 1 {
|
||||
return None;
|
||||
}
|
||||
let signing_share = self
|
||||
.round2_packages
|
||||
.values()
|
||||
.map(|package| package.secret_share().clone())
|
||||
.chain(std::iter::once(SigningShare::from_coefficients(
|
||||
secret_package.coefficients(),
|
||||
self.id,
|
||||
)))
|
||||
.fold(SigningShare::new(Scalar::ZERO), |acc, e| {
|
||||
SigningShare::new(acc.to_scalar() + e.to_scalar())
|
||||
});
|
||||
let secret_share = SecretShare::new(self.id, signing_share, commitment.clone());
|
||||
let key_package = match KeyPackage::try_from(secret_share) {
|
||||
Ok(key_package) => key_package,
|
||||
Err(error) => {
|
||||
log::error!("dkg failed with {:?}", error);
|
||||
return Some(DkgAction::Failure);
|
||||
},
|
||||
};
|
||||
let public_key_package = PublicKeyPackage::from_commitment(&self.members, &commitment);
|
||||
Some(DkgAction::Complete(key_package, public_key_package, commitment.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frost_evm::frost_core::frost::keys::dkg::verify_proof_of_knowledge;
|
||||
use frost_evm::frost_core::frost::keys::{compute_group_commitment, default_identifiers};
|
||||
|
||||
#[test]
|
||||
fn test_dkg() {
|
||||
env_logger::try_init().ok();
|
||||
let members: BTreeSet<_> = default_identifiers(3).into_iter().collect();
|
||||
let threshold = 2;
|
||||
let mut dkgs: HashMap<_, _> = members
|
||||
.iter()
|
||||
.map(|id| (*id, Dkg::new(*id, members.clone(), threshold)))
|
||||
.collect();
|
||||
let mut commitments = Vec::with_capacity(members.len());
|
||||
loop {
|
||||
for from in &members {
|
||||
match dkgs.get_mut(from).unwrap().next_action() {
|
||||
Some(DkgAction::Commit(commitment, proof_of_knowledge)) => {
|
||||
verify_proof_of_knowledge(*from, &commitment, proof_of_knowledge).unwrap();
|
||||
commitments.push(commitment);
|
||||
if commitments.len() == members.len() {
|
||||
let commitment = compute_group_commitment(&commitments);
|
||||
for dkg in dkgs.values_mut() {
|
||||
dkg.on_commit(commitment.clone());
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(DkgAction::Send(msgs)) => {
|
||||
for (to, msg) in msgs {
|
||||
dkgs.get_mut(&to).unwrap().on_message(*from, msg);
|
||||
}
|
||||
},
|
||||
Some(DkgAction::Complete(_key_package, _public_key_package, _commitment)) => {
|
||||
return;
|
||||
},
|
||||
Some(DkgAction::Failure) => unreachable!(),
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
use crate::dkg::{Dkg, DkgAction, DkgMessage};
|
||||
use crate::roast::{Roast, RoastAction, RoastRequest, RoastSignerResponse};
|
||||
use crate::rts::{Rts, RtsAction, RtsHelper, RtsRequest, RtsResponse};
|
||||
use anyhow::Result;
|
||||
use frost_evm::keys::{
|
||||
KeyPackage, PublicKeyPackage, SecretShare, VerifiableSecretSharingCommitment,
|
||||
};
|
||||
use frost_evm::{Identifier, Signature, VerifyingKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
pub mod dkg;
|
||||
pub mod roast;
|
||||
pub mod rts;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
enum TssState<I> {
|
||||
Dkg(Dkg),
|
||||
Rts(Rts),
|
||||
Roast {
|
||||
rts: RtsHelper,
|
||||
key_package: KeyPackage,
|
||||
public_key_package: PublicKeyPackage,
|
||||
signing_sessions: BTreeMap<I, Roast>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum TssAction<I, P> {
|
||||
Send(Vec<(P, TssRequest<I>)>),
|
||||
Commit(VerifiableSecretSharingCommitment, frost_evm::frost_secp256k1::Signature),
|
||||
PublicKey(VerifyingKey),
|
||||
Signature(I, [u8; 32], Signature),
|
||||
Failure,
|
||||
}
|
||||
|
||||
/// Tss message.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum TssRequest<I> {
|
||||
Dkg { msg: DkgMessage },
|
||||
Rts { msg: RtsRequest },
|
||||
Roast { id: I, msg: RoastRequest },
|
||||
}
|
||||
|
||||
impl<I: std::fmt::Display> std::fmt::Display for TssRequest<I> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Dkg { msg } => write!(f, "dkg {}", msg),
|
||||
Self::Rts { msg } => write!(f, "rts {}", msg),
|
||||
Self::Roast { id, msg } => write!(f, "roast {} {}", id, msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum TssResponse<I> {
|
||||
Rts { msg: RtsResponse },
|
||||
Roast { id: I, msg: RoastSignerResponse },
|
||||
}
|
||||
|
||||
impl<I: std::fmt::Display> std::fmt::Display for TssResponse<I> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Rts { msg } => write!(f, "rts {}", msg),
|
||||
Self::Roast { id, .. } => write!(f, "roast {}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn peer_to_frost(peer: impl std::fmt::Display) -> Identifier {
|
||||
Identifier::derive(peer.to_string().as_bytes()).expect("non zero")
|
||||
}
|
||||
|
||||
/// Tss state machine.
|
||||
pub struct Tss<I, P> {
|
||||
peer_id: P,
|
||||
frost_id: Identifier,
|
||||
frost_to_peer: BTreeMap<Identifier, P>,
|
||||
threshold: u16,
|
||||
coordinators: BTreeSet<Identifier>,
|
||||
state: TssState<I>,
|
||||
}
|
||||
|
||||
impl<I, P> Tss<I, P>
|
||||
where
|
||||
I: Clone + Ord + std::fmt::Display,
|
||||
P: Clone + Ord + std::fmt::Display,
|
||||
{
|
||||
pub fn new(
|
||||
peer_id: P,
|
||||
members: BTreeSet<P>,
|
||||
threshold: u16,
|
||||
commitment: Option<VerifiableSecretSharingCommitment>,
|
||||
) -> Self {
|
||||
debug_assert!(members.contains(&peer_id));
|
||||
let frost_id = peer_to_frost(&peer_id);
|
||||
let frost_to_peer: BTreeMap<_, _> =
|
||||
members.into_iter().map(|peer| (peer_to_frost(&peer), peer)).collect();
|
||||
let members: BTreeSet<_> = frost_to_peer.keys().copied().collect();
|
||||
let coordinators: BTreeSet<_> =
|
||||
members.iter().copied().take(members.len() - threshold as usize + 1).collect();
|
||||
let is_coordinator = coordinators.contains(&frost_id);
|
||||
log::debug!(
|
||||
"{} initialize {}/{} coordinator = {}",
|
||||
peer_id,
|
||||
threshold,
|
||||
members.len(),
|
||||
is_coordinator
|
||||
);
|
||||
Self {
|
||||
peer_id,
|
||||
frost_id,
|
||||
frost_to_peer,
|
||||
threshold,
|
||||
coordinators,
|
||||
state: if let Some(commitment) = commitment {
|
||||
TssState::Rts(Rts::new(frost_id, members, threshold, commitment))
|
||||
} else {
|
||||
TssState::Dkg(Dkg::new(frost_id, members, threshold))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> &P {
|
||||
&self.peer_id
|
||||
}
|
||||
|
||||
fn frost_to_peer(&self, frost: &Identifier) -> P {
|
||||
self.frost_to_peer.get(frost).unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn total_nodes(&self) -> usize {
|
||||
self.frost_to_peer.len()
|
||||
}
|
||||
|
||||
pub fn threshold(&self) -> usize {
|
||||
self.threshold as _
|
||||
}
|
||||
|
||||
pub fn on_request(
|
||||
&mut self,
|
||||
peer_id: P,
|
||||
request: TssRequest<I>,
|
||||
) -> Result<Option<TssResponse<I>>> {
|
||||
log::debug!("{} on_request {} {}", self.peer_id, peer_id, request);
|
||||
if self.peer_id == peer_id {
|
||||
anyhow::bail!("{} received message from self", self.peer_id);
|
||||
}
|
||||
let frost_id = peer_to_frost(&peer_id);
|
||||
if !self.frost_to_peer.contains_key(&frost_id) {
|
||||
anyhow::bail!("{} received message unknown peer {}", self.peer_id, peer_id);
|
||||
}
|
||||
match (&mut self.state, request) {
|
||||
(TssState::Dkg(dkg), TssRequest::Dkg { msg }) => {
|
||||
dkg.on_message(frost_id, msg);
|
||||
Ok(None)
|
||||
},
|
||||
(TssState::Roast { rts, .. }, TssRequest::Rts { msg }) => {
|
||||
let msg = rts.on_request(frost_id, msg)?;
|
||||
Ok(Some(TssResponse::Rts { msg }))
|
||||
},
|
||||
(TssState::Roast { signing_sessions, .. }, TssRequest::Roast { id, msg }) => {
|
||||
if let Some(session) = signing_sessions.get_mut(&id) {
|
||||
if let Some(msg) = session.on_request(frost_id, msg)? {
|
||||
Ok(Some(TssResponse::Roast { id, msg }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("invalid signing session");
|
||||
}
|
||||
},
|
||||
(_, msg) => {
|
||||
anyhow::bail!("unexpected request {}", msg);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_response(&mut self, peer_id: P, response: Option<TssResponse<I>>) {
|
||||
let frost_id = peer_to_frost(&peer_id);
|
||||
match (&mut self.state, response) {
|
||||
(TssState::Dkg(_), _) => {},
|
||||
(TssState::Rts(rts), Some(TssResponse::Rts { msg })) => {
|
||||
rts.on_response(frost_id, Some(msg));
|
||||
},
|
||||
(TssState::Rts(rts), None) => {
|
||||
rts.on_response(frost_id, None);
|
||||
},
|
||||
(TssState::Roast { signing_sessions, .. }, Some(TssResponse::Roast { id, msg })) => {
|
||||
if let Some(session) = signing_sessions.get_mut(&id) {
|
||||
session.on_response(frost_id, msg);
|
||||
} else {
|
||||
log::error!("invalid signing session");
|
||||
}
|
||||
},
|
||||
(TssState::Roast { .. }, None) => {},
|
||||
(_, Some(msg)) => {
|
||||
log::error!("invalid state ({}, {}, {})", self.peer_id, peer_id, msg);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_commit(&mut self, commitment: VerifiableSecretSharingCommitment) {
|
||||
log::debug!("{} commit", self.peer_id);
|
||||
match &mut self.state {
|
||||
TssState::Dkg(dkg) => dkg.on_commit(commitment),
|
||||
_ => log::error!("unexpected commit"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_sign(&mut self, id: I, data: Vec<u8>) {
|
||||
log::debug!("{} sign {}", self.peer_id, id);
|
||||
match &mut self.state {
|
||||
TssState::Roast {
|
||||
key_package,
|
||||
public_key_package,
|
||||
signing_sessions,
|
||||
..
|
||||
} => {
|
||||
let roast = Roast::new(
|
||||
self.frost_id,
|
||||
self.threshold,
|
||||
key_package.clone(),
|
||||
public_key_package.clone(),
|
||||
data,
|
||||
self.coordinators.clone(),
|
||||
);
|
||||
signing_sessions.insert(id, roast);
|
||||
},
|
||||
_ => {
|
||||
log::error!("not ready to sign");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_action(&mut self) -> Option<TssAction<I, P>> {
|
||||
match &mut self.state {
|
||||
TssState::Dkg(dkg) => {
|
||||
match dkg.next_action()? {
|
||||
DkgAction::Send(msgs) => {
|
||||
return Some(TssAction::Send(
|
||||
msgs.into_iter()
|
||||
.map(|(peer, msg)| {
|
||||
(self.frost_to_peer(&peer), TssRequest::Dkg { msg })
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
},
|
||||
DkgAction::Commit(commitment, proof_of_knowledge) => {
|
||||
return Some(TssAction::Commit(commitment, proof_of_knowledge));
|
||||
},
|
||||
DkgAction::Complete(key_package, public_key_package, commitment) => {
|
||||
let secret_share = SecretShare::new(
|
||||
self.frost_id,
|
||||
*key_package.secret_share(),
|
||||
commitment,
|
||||
);
|
||||
let public_key =
|
||||
VerifyingKey::new(public_key_package.group_public().to_element());
|
||||
let members = self.frost_to_peer.keys().copied().collect();
|
||||
let rts =
|
||||
RtsHelper::new(self.frost_id, members, self.threshold, secret_share);
|
||||
self.state = TssState::Roast {
|
||||
rts,
|
||||
key_package,
|
||||
public_key_package,
|
||||
signing_sessions: Default::default(),
|
||||
};
|
||||
return Some(TssAction::PublicKey(public_key));
|
||||
},
|
||||
DkgAction::Failure => return Some(TssAction::Failure),
|
||||
};
|
||||
},
|
||||
TssState::Rts(rts) => match rts.next_action()? {
|
||||
RtsAction::Send(msgs) => {
|
||||
return Some(TssAction::Send(
|
||||
msgs.into_iter()
|
||||
.map(|(peer, msg)| (self.frost_to_peer(&peer), TssRequest::Rts { msg }))
|
||||
.collect(),
|
||||
));
|
||||
},
|
||||
RtsAction::Complete(key_package, public_key_package, commitment) => {
|
||||
let secret_share =
|
||||
SecretShare::new(self.frost_id, *key_package.secret_share(), commitment);
|
||||
let public_key =
|
||||
VerifyingKey::new(public_key_package.group_public().to_element());
|
||||
let members = self.frost_to_peer.keys().copied().collect();
|
||||
let rts = RtsHelper::new(self.frost_id, members, self.threshold, secret_share);
|
||||
self.state = TssState::Roast {
|
||||
rts,
|
||||
key_package,
|
||||
public_key_package,
|
||||
signing_sessions: Default::default(),
|
||||
};
|
||||
return Some(TssAction::PublicKey(public_key));
|
||||
},
|
||||
RtsAction::Failure => return Some(TssAction::Failure),
|
||||
},
|
||||
TssState::Roast { signing_sessions, .. } => {
|
||||
let session_ids: Vec<_> = signing_sessions.keys().cloned().collect();
|
||||
for id in session_ids {
|
||||
let session = signing_sessions.get_mut(&id).unwrap();
|
||||
while let Some(action) = session.next_action() {
|
||||
let (peers, send_to_self, msg) = match action {
|
||||
RoastAction::Send(peer, msg) => {
|
||||
if peer == self.frost_id {
|
||||
(vec![], true, msg)
|
||||
} else {
|
||||
(vec![peer], false, msg)
|
||||
}
|
||||
},
|
||||
RoastAction::SendMany(all_peers, msg) => {
|
||||
let peers: Vec<_> = all_peers
|
||||
.iter()
|
||||
.filter(|peer| **peer != self.frost_id)
|
||||
.copied()
|
||||
.collect();
|
||||
let send_to_self = peers.len() != all_peers.len();
|
||||
(peers, send_to_self, msg)
|
||||
},
|
||||
RoastAction::Complete(hash, signature) => {
|
||||
signing_sessions.remove(&id);
|
||||
return Some(TssAction::Signature(id, hash, signature));
|
||||
},
|
||||
};
|
||||
if send_to_self {
|
||||
if let Some(response) = session
|
||||
.on_request(self.frost_id, msg.clone())
|
||||
.expect("something wrong")
|
||||
{
|
||||
session.on_response(self.frost_id, response);
|
||||
}
|
||||
}
|
||||
if !peers.is_empty() {
|
||||
return Some(TssAction::Send(
|
||||
peers
|
||||
.into_iter()
|
||||
.map(|peer| {
|
||||
(
|
||||
self.frost_to_peer(&peer),
|
||||
TssRequest::Roast {
|
||||
id: id.clone(),
|
||||
msg: msg.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
use anyhow::Result;
|
||||
use frost_evm::{
|
||||
keys::{KeyPackage, PublicKeyPackage},
|
||||
round1::{self, SigningCommitments, SigningNonces},
|
||||
round2::{self, SignatureShare},
|
||||
Identifier, Signature, SigningPackage, VerifyingKey,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub struct RoastSignerRequest {
|
||||
session_id: u16,
|
||||
commitments: BTreeMap<Identifier, SigningCommitments>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub struct RoastSignerResponse {
|
||||
session_id: u16,
|
||||
signature_share: SignatureShare,
|
||||
commitment: SigningCommitments,
|
||||
}
|
||||
|
||||
struct RoastSigner {
|
||||
key_package: KeyPackage,
|
||||
data: Vec<u8>,
|
||||
coordinators: BTreeMap<Identifier, SigningNonces>,
|
||||
}
|
||||
|
||||
impl RoastSigner {
|
||||
pub fn new(key_package: KeyPackage, data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
key_package,
|
||||
data,
|
||||
coordinators: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn commit(&mut self, coordinator: Identifier) -> SigningCommitments {
|
||||
let (nonces, commitment) = round1::commit(self.key_package.secret_share(), &mut OsRng);
|
||||
self.coordinators.insert(coordinator, nonces);
|
||||
commitment
|
||||
}
|
||||
|
||||
pub fn sign(
|
||||
&mut self,
|
||||
coordinator: Identifier,
|
||||
request: RoastSignerRequest,
|
||||
) -> Result<RoastSignerResponse> {
|
||||
let session_id = request.session_id;
|
||||
let signing_package = SigningPackage::new(request.commitments, &self.data);
|
||||
let nonces = self
|
||||
.coordinators
|
||||
.remove(&coordinator)
|
||||
.expect("we sent the coordinator a commitment");
|
||||
let signature_share = round2::sign(&signing_package, &nonces, &self.key_package)?;
|
||||
let commitment = self.commit(coordinator);
|
||||
Ok(RoastSignerResponse {
|
||||
session_id,
|
||||
signature_share,
|
||||
commitment,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct RoastSession {
|
||||
commitments: BTreeMap<Identifier, SigningCommitments>,
|
||||
signature_shares: HashMap<Identifier, SignatureShare>,
|
||||
}
|
||||
|
||||
impl RoastSession {
|
||||
fn new(commitments: BTreeMap<Identifier, SigningCommitments>) -> Self {
|
||||
Self {
|
||||
commitments,
|
||||
signature_shares: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_signature_share(&mut self, peer: Identifier, signature_share: SignatureShare) {
|
||||
if self.commitments.contains_key(&peer) {
|
||||
self.signature_shares.insert(peer, signature_share);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
self.commitments.len() == self.signature_shares.len()
|
||||
}
|
||||
}
|
||||
|
||||
struct RoastCoordinator {
|
||||
threshold: u16,
|
||||
session_id: u16,
|
||||
commitments: BTreeMap<Identifier, SigningCommitments>,
|
||||
sessions: BTreeMap<u16, RoastSession>,
|
||||
}
|
||||
|
||||
impl RoastCoordinator {
|
||||
fn new(threshold: u16) -> Self {
|
||||
Self {
|
||||
threshold,
|
||||
session_id: 0,
|
||||
commitments: Default::default(),
|
||||
sessions: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_commit(&mut self, peer: Identifier, commitment: SigningCommitments) {
|
||||
self.commitments.insert(peer, commitment);
|
||||
}
|
||||
|
||||
fn on_response(&mut self, peer: Identifier, message: RoastSignerResponse) {
|
||||
if let Some(session) = self.sessions.get_mut(&message.session_id) {
|
||||
self.commitments.insert(peer, message.commitment);
|
||||
session.on_signature_share(peer, message.signature_share);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_session(&mut self) -> Option<RoastSignerRequest> {
|
||||
if self.commitments.len() < self.threshold as _ {
|
||||
log::debug!("commitments {}/{}", self.commitments.len(), self.threshold);
|
||||
return None;
|
||||
}
|
||||
let session_id = self.session_id;
|
||||
self.session_id += 1;
|
||||
let mut commitments = std::mem::take(&mut self.commitments);
|
||||
while commitments.len() > self.threshold as _ {
|
||||
let (peer, commitment) = commitments.pop_last().unwrap();
|
||||
self.commitments.insert(peer, commitment);
|
||||
}
|
||||
self.sessions
|
||||
.insert(session_id, RoastSession::new(commitments.clone()));
|
||||
Some(RoastSignerRequest {
|
||||
session_id,
|
||||
commitments,
|
||||
})
|
||||
}
|
||||
|
||||
fn aggregate_signature(&mut self) -> Option<RoastSession> {
|
||||
let session_id = self
|
||||
.sessions
|
||||
.iter()
|
||||
.filter(|(_, session)| session.is_complete())
|
||||
.map(|(session_id, _)| *session_id)
|
||||
.next()?;
|
||||
self.sessions.remove(&session_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub enum RoastRequest {
|
||||
Commit(SigningCommitments),
|
||||
Sign(RoastSignerRequest),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RoastRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Commit(_) => write!(f, "commit"),
|
||||
Self::Sign(_) => write!(f, "sign"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum RoastAction {
|
||||
Send(Identifier, RoastRequest),
|
||||
SendMany(Vec<Identifier>, RoastRequest),
|
||||
Complete([u8; 32], Signature),
|
||||
}
|
||||
|
||||
/// ROAST state machine.
|
||||
pub struct Roast {
|
||||
signer: RoastSigner,
|
||||
coordinator: Option<RoastCoordinator>,
|
||||
public_key_package: PublicKeyPackage,
|
||||
coordinators: BTreeSet<Identifier>,
|
||||
}
|
||||
|
||||
impl Roast {
|
||||
pub fn new(
|
||||
id: Identifier,
|
||||
threshold: u16,
|
||||
key_package: KeyPackage,
|
||||
public_key_package: PublicKeyPackage,
|
||||
data: Vec<u8>,
|
||||
coordinators: BTreeSet<Identifier>,
|
||||
) -> Self {
|
||||
let is_coordinator = coordinators.contains(&id);
|
||||
Self {
|
||||
signer: RoastSigner::new(key_package, data),
|
||||
coordinator: if is_coordinator {
|
||||
Some(RoastCoordinator::new(threshold))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
public_key_package,
|
||||
coordinators,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_request(
|
||||
&mut self,
|
||||
peer: Identifier,
|
||||
request: RoastRequest,
|
||||
) -> Result<Option<RoastSignerResponse>> {
|
||||
match request {
|
||||
RoastRequest::Commit(commitment) => {
|
||||
if let Some(coordinator) = self.coordinator.as_mut() {
|
||||
coordinator.on_commit(peer, commitment);
|
||||
Ok(None)
|
||||
} else {
|
||||
anyhow::bail!("not coordinator");
|
||||
}
|
||||
}
|
||||
RoastRequest::Sign(request) => Ok(Some(self.signer.sign(peer, request)?)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_response(&mut self, peer: Identifier, response: RoastSignerResponse) {
|
||||
if let Some(coordinator) = self.coordinator.as_mut() {
|
||||
coordinator.on_response(peer, response);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_action(&mut self) -> Option<RoastAction> {
|
||||
if let Some(coordinator) = self.coordinator.as_mut() {
|
||||
if let Some(session) = coordinator.aggregate_signature() {
|
||||
let signing_package = SigningPackage::new(session.commitments, self.signer.data());
|
||||
if let Ok(signature) = frost_evm::aggregate(
|
||||
&signing_package,
|
||||
&session.signature_shares,
|
||||
&self.public_key_package,
|
||||
) {
|
||||
let hash = VerifyingKey::message_hash(self.signer.data());
|
||||
return Some(RoastAction::Complete(hash, signature));
|
||||
}
|
||||
}
|
||||
if let Some(request) = coordinator.start_session() {
|
||||
let peers = request.commitments.keys().copied().collect();
|
||||
return Some(RoastAction::SendMany(peers, RoastRequest::Sign(request)));
|
||||
}
|
||||
}
|
||||
if let Some(coordinator) = self.coordinators.pop_last() {
|
||||
return Some(RoastAction::Send(
|
||||
coordinator,
|
||||
RoastRequest::Commit(self.signer.commit(coordinator)),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use frost_evm::keys::{generate_with_dealer, IdentifierList};
|
||||
|
||||
#[test]
|
||||
fn test_roast() -> Result<()> {
|
||||
env_logger::try_init().ok();
|
||||
let signers = 3;
|
||||
let threshold = 2;
|
||||
let coordinator = 1;
|
||||
let data = b"a message to sing".to_vec();
|
||||
let (secret_shares, public_key_package) =
|
||||
generate_with_dealer(signers, threshold, IdentifierList::Default, &mut OsRng).unwrap();
|
||||
let coordinators: BTreeSet<_> = secret_shares.keys().copied().take(coordinator).collect();
|
||||
let mut roasts: BTreeMap<_, _> = secret_shares
|
||||
.into_iter()
|
||||
.map(|(peer, secret_share)| {
|
||||
(
|
||||
peer,
|
||||
Roast::new(
|
||||
peer,
|
||||
threshold,
|
||||
KeyPackage::try_from(secret_share).unwrap(),
|
||||
public_key_package.clone(),
|
||||
data.clone(),
|
||||
coordinators.clone(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let members: Vec<_> = roasts.keys().copied().collect();
|
||||
loop {
|
||||
for from in &members {
|
||||
if let Some(action) = roasts.get_mut(from).unwrap().next_action() {
|
||||
match action {
|
||||
RoastAction::Send(to, commitment) => {
|
||||
if let Some(response) =
|
||||
roasts.get_mut(&to).unwrap().on_request(*from, commitment)?
|
||||
{
|
||||
roasts.get_mut(from).unwrap().on_response(to, response);
|
||||
}
|
||||
}
|
||||
RoastAction::SendMany(peers, request) => {
|
||||
for to in peers {
|
||||
if let Some(response) = roasts
|
||||
.get_mut(&to)
|
||||
.unwrap()
|
||||
.on_request(*from, request.clone())?
|
||||
{
|
||||
roasts.get_mut(from).unwrap().on_response(to, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
RoastAction::Complete(_hash, _signature) => {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,409 @@
|
|||
use anyhow::Result;
|
||||
use frost_evm::frost_secp256k1::Secp256K1Sha256;
|
||||
use frost_evm::k256::elliptic_curve::PrimeField;
|
||||
use frost_evm::keys::repairable;
|
||||
use frost_evm::keys::{
|
||||
KeyPackage, PublicKeyPackage, SecretShare, VerifiableSecretSharingCommitment,
|
||||
};
|
||||
use frost_evm::{Identifier, Scalar};
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum RtsRequest {
|
||||
Delta {
|
||||
session_id: u32,
|
||||
helpers: BTreeSet<Identifier>,
|
||||
},
|
||||
Sigma {
|
||||
session_id: u32,
|
||||
deltas: BTreeMap<Identifier, [u8; 32]>,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RtsRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Delta { .. } => write!(f, "delta"),
|
||||
Self::Sigma { .. } => write!(f, "sigma"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum RtsResponse {
|
||||
// TODO: delta share probably needs to be encrypted/authenticated
|
||||
Delta {
|
||||
session_id: u32,
|
||||
deltas: BTreeMap<Identifier, [u8; 32]>,
|
||||
},
|
||||
Sigma {
|
||||
session_id: u32,
|
||||
sigma: [u8; 32],
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RtsResponse {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Delta { .. } => write!(f, "delta"),
|
||||
Self::Sigma { .. } => write!(f, "sigma"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RtsHelper {
|
||||
identifier: Identifier,
|
||||
members: BTreeSet<Identifier>,
|
||||
threshold: u16,
|
||||
secret_share: SecretShare,
|
||||
secret_deltas: BTreeMap<(Identifier, u32), Scalar>,
|
||||
}
|
||||
|
||||
impl RtsHelper {
|
||||
pub fn new(
|
||||
identifier: Identifier,
|
||||
members: BTreeSet<Identifier>,
|
||||
threshold: u16,
|
||||
secret_share: SecretShare,
|
||||
) -> Self {
|
||||
debug_assert!(members.contains(&identifier));
|
||||
Self {
|
||||
identifier,
|
||||
members,
|
||||
threshold,
|
||||
secret_share,
|
||||
secret_deltas: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_request(&mut self, peer: Identifier, msg: RtsRequest) -> Result<RtsResponse> {
|
||||
match msg {
|
||||
RtsRequest::Delta {
|
||||
session_id,
|
||||
helpers,
|
||||
} => {
|
||||
if !helpers.contains(&self.identifier)
|
||||
|| !helpers.is_subset(&self.members)
|
||||
|| helpers.len() != self.threshold as usize
|
||||
{
|
||||
anyhow::bail!("invalid helpers");
|
||||
}
|
||||
let mut deltas = repairable::repair_share_step_1::<Secp256K1Sha256, _>(
|
||||
&helpers.into_iter().collect::<Vec<_>>(),
|
||||
&self.secret_share,
|
||||
&mut OsRng,
|
||||
peer,
|
||||
)?;
|
||||
let secret_delta = deltas.remove(&self.identifier).unwrap();
|
||||
self.secret_deltas.insert((peer, session_id), secret_delta);
|
||||
let deltas = deltas
|
||||
.into_iter()
|
||||
.map(|(id, delta)| (id, delta.to_bytes().into()))
|
||||
.collect();
|
||||
Ok(RtsResponse::Delta { session_id, deltas })
|
||||
}
|
||||
RtsRequest::Sigma { session_id, deltas } => {
|
||||
if deltas.contains_key(&self.identifier) {
|
||||
anyhow::bail!("invalid deltas");
|
||||
}
|
||||
for id in deltas.keys() {
|
||||
if !self.members.contains(id) {
|
||||
anyhow::bail!("invalid deltas");
|
||||
}
|
||||
}
|
||||
let Some(secret_delta) = self.secret_deltas.remove(&(peer, session_id)) else {
|
||||
anyhow::bail!("invalid session");
|
||||
};
|
||||
let mut deltas = deltas
|
||||
.into_iter()
|
||||
.filter_map(|(_, delta)| Option::from(Scalar::from_repr(delta.into())))
|
||||
.collect::<Vec<_>>();
|
||||
if deltas.len() != self.threshold as usize - 1 {
|
||||
anyhow::bail!("invalid deltas");
|
||||
}
|
||||
deltas.push(secret_delta);
|
||||
let sigma = repairable::repair_share_step_2(&deltas);
|
||||
Ok(RtsResponse::Sigma {
|
||||
session_id,
|
||||
sigma: sigma.to_bytes().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RtsAction {
|
||||
Send(Vec<(Identifier, RtsRequest)>),
|
||||
Complete(
|
||||
KeyPackage,
|
||||
PublicKeyPackage,
|
||||
VerifiableSecretSharingCommitment,
|
||||
),
|
||||
Failure,
|
||||
}
|
||||
|
||||
struct RtsSession {
|
||||
helpers: BTreeSet<Identifier>,
|
||||
deltas: BTreeMap<Identifier, BTreeMap<Identifier, Scalar>>,
|
||||
sigmas: BTreeMap<Identifier, Scalar>,
|
||||
}
|
||||
|
||||
impl RtsSession {
|
||||
fn new(helpers: BTreeSet<Identifier>) -> Self {
|
||||
Self {
|
||||
helpers,
|
||||
deltas: Default::default(),
|
||||
sigmas: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_deltas(&mut self, peer: Identifier, deltas: BTreeMap<Identifier, [u8; 32]>) -> bool {
|
||||
if !self.helpers.contains(&peer) {
|
||||
return false;
|
||||
}
|
||||
let deltas: BTreeMap<_, _> = deltas
|
||||
.into_iter()
|
||||
.filter(|(to, _)| *to != peer && self.helpers.contains(to))
|
||||
.filter_map(|(to, delta)| Some((to, Option::from(Scalar::from_repr(delta.into()))?)))
|
||||
.collect();
|
||||
if deltas.len() != self.helpers.len() - 1 {
|
||||
return false;
|
||||
}
|
||||
self.deltas.insert(peer, deltas);
|
||||
true
|
||||
}
|
||||
|
||||
fn on_sigma(&mut self, peer: Identifier, sigma: [u8; 32]) -> bool {
|
||||
if !self.helpers.contains(&peer) {
|
||||
return false;
|
||||
}
|
||||
let Some(sigma) = Option::from(Scalar::from_repr(sigma.into())) else {
|
||||
return false;
|
||||
};
|
||||
self.sigmas.insert(peer, sigma);
|
||||
true
|
||||
}
|
||||
|
||||
fn deltas(
|
||||
&self,
|
||||
) -> Option<impl Iterator<Item = (&Identifier, BTreeMap<Identifier, [u8; 32]>)>> {
|
||||
if self.deltas.len() == self.helpers.len() {
|
||||
Some(self.helpers.iter().map(|to| {
|
||||
let deltas = self
|
||||
.deltas
|
||||
.iter()
|
||||
.filter_map(|(from, deltas)| {
|
||||
let delta = deltas.get(to)?.to_bytes().into();
|
||||
Some((*from, delta))
|
||||
})
|
||||
.collect();
|
||||
(to, deltas)
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn sigmas(&self) -> Option<Vec<Scalar>> {
|
||||
if self.sigmas.len() == self.helpers.len() {
|
||||
Some(self.sigmas.values().copied().collect())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rts {
|
||||
id: Identifier,
|
||||
members: BTreeSet<Identifier>,
|
||||
threshold: u16,
|
||||
commitment: VerifiableSecretSharingCommitment,
|
||||
public_key_package: PublicKeyPackage,
|
||||
unhelpful: BTreeSet<Identifier>,
|
||||
session_id: u32,
|
||||
session: Option<RtsSession>,
|
||||
}
|
||||
|
||||
impl Rts {
|
||||
pub fn new(
|
||||
id: Identifier,
|
||||
members: BTreeSet<Identifier>,
|
||||
threshold: u16,
|
||||
commitment: VerifiableSecretSharingCommitment,
|
||||
) -> Self {
|
||||
let public_key_package =
|
||||
PublicKeyPackage::from_commitment(&members, &commitment).expect("put a match here");
|
||||
Self {
|
||||
id,
|
||||
members,
|
||||
threshold,
|
||||
commitment,
|
||||
public_key_package,
|
||||
unhelpful: Default::default(),
|
||||
session_id: 0,
|
||||
session: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn session(&mut self, session_id: u32) -> Option<&mut RtsSession> {
|
||||
if self.session_id != session_id {
|
||||
return None;
|
||||
}
|
||||
self.session.as_mut()
|
||||
}
|
||||
|
||||
fn abort_session(&mut self, peer: Identifier) {
|
||||
log::info!("aborting session {}", self.session_id);
|
||||
self.unhelpful.insert(peer);
|
||||
self.session.take();
|
||||
self.session_id += 1;
|
||||
}
|
||||
|
||||
pub fn on_response(&mut self, peer: Identifier, msg: Option<RtsResponse>) {
|
||||
match msg {
|
||||
Some(RtsResponse::Delta { session_id, deltas }) => {
|
||||
let Some(session) = self.session(session_id) else {
|
||||
return;
|
||||
};
|
||||
if !session.on_deltas(peer, deltas) {
|
||||
self.abort_session(peer);
|
||||
}
|
||||
}
|
||||
Some(RtsResponse::Sigma { session_id, sigma }) => {
|
||||
let Some(session) = self.session(session_id) else {
|
||||
return;
|
||||
};
|
||||
if !session.on_sigma(peer, sigma) {
|
||||
self.abort_session(peer);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.abort_session(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_action(&mut self) -> Option<RtsAction> {
|
||||
loop {
|
||||
if let Some(session) = self.session.as_mut() {
|
||||
if let Some(sigmas) = session.sigmas() {
|
||||
let secret_share =
|
||||
repairable::repair_share_step_3(&sigmas, self.id, &self.commitment);
|
||||
// TODO: handle failure somehow. maybe try a different set of random peers?
|
||||
let Ok(key_package) = KeyPackage::try_from(secret_share) else {
|
||||
return Some(RtsAction::Failure);
|
||||
};
|
||||
return Some(RtsAction::Complete(
|
||||
key_package,
|
||||
self.public_key_package.clone(),
|
||||
self.commitment.clone(),
|
||||
));
|
||||
} else if let Some(deltas) = session.deltas() {
|
||||
return Some(RtsAction::Send(
|
||||
deltas
|
||||
.map(|(to, deltas)| {
|
||||
(
|
||||
*to,
|
||||
RtsRequest::Sigma {
|
||||
session_id: self.session_id,
|
||||
deltas,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
let helpers: BTreeSet<_> = self
|
||||
.members
|
||||
.iter()
|
||||
.filter(|helper| **helper != self.id)
|
||||
.filter(|helper| !self.unhelpful.contains(helper))
|
||||
.take(self.threshold as _)
|
||||
.copied()
|
||||
.collect();
|
||||
if helpers.len() != self.threshold as usize {
|
||||
return Some(RtsAction::Failure);
|
||||
}
|
||||
let session_id = self.session_id;
|
||||
self.session = Some(RtsSession::new(helpers.clone()));
|
||||
return Some(RtsAction::Send(
|
||||
helpers
|
||||
.iter()
|
||||
.map(|helper| {
|
||||
(
|
||||
*helper,
|
||||
RtsRequest::Delta {
|
||||
session_id,
|
||||
helpers: helpers.clone(),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use frost_evm::keys::{generate_with_dealer, IdentifierList};
|
||||
|
||||
#[test]
|
||||
fn test_rts() -> Result<()> {
|
||||
env_logger::try_init().ok();
|
||||
let signers = 3;
|
||||
let threshold = 2;
|
||||
let (secret_shares, public_key_package) =
|
||||
generate_with_dealer(signers, threshold, IdentifierList::Default, &mut OsRng).unwrap();
|
||||
let members: BTreeSet<_> = secret_shares.keys().copied().collect();
|
||||
let commitment = secret_shares.values().next().unwrap().commitment();
|
||||
let mut helpers: BTreeMap<_, _> = members
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|peer| {
|
||||
(
|
||||
*peer,
|
||||
RtsHelper::new(
|
||||
*peer,
|
||||
members.clone(),
|
||||
threshold,
|
||||
secret_shares.get(peer).unwrap().clone(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let id = *members.iter().next().unwrap();
|
||||
let secret_share = secret_shares.get(&id).unwrap().clone();
|
||||
let key_package = KeyPackage::try_from(secret_share).unwrap();
|
||||
let mut rts = Rts::new(id, members, threshold, commitment.clone());
|
||||
while let Some(action) = rts.next_action() {
|
||||
match action {
|
||||
RtsAction::Send(msgs) => {
|
||||
for (peer, msg) in msgs {
|
||||
let response = helpers.get_mut(&peer).unwrap().on_request(id, msg).unwrap();
|
||||
rts.on_response(peer, Some(response));
|
||||
}
|
||||
}
|
||||
RtsAction::Complete(
|
||||
recovered_key_package,
|
||||
recovered_public_key_package,
|
||||
recovered_commitment,
|
||||
) => {
|
||||
assert_eq!(key_package, recovered_key_package);
|
||||
assert_eq!(public_key_package, recovered_public_key_package);
|
||||
assert_eq!(commitment, &recovered_commitment);
|
||||
return Ok(());
|
||||
}
|
||||
RtsAction::Failure => break,
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
use crate::{Tss, TssAction, TssRequest, TssResponse};
|
||||
use frost_evm::frost_core::frost::keys::compute_group_commitment;
|
||||
//use frost_evm::frost_core::frost::keys::dkg::verify_proof_of_knowledge;
|
||||
//use frost_evm::keys::SigningShare;
|
||||
//use frost_evm::round2::SignatureShare;
|
||||
use frost_evm::{Signature, VerifyingKey};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
type Peer = u8;
|
||||
type Id = u8;
|
||||
|
||||
#[derive(Default)]
|
||||
struct TssEvents {
|
||||
pubkeys: BTreeMap<Peer, VerifyingKey>,
|
||||
signatures: BTreeMap<Id, BTreeMap<Peer, Signature>>,
|
||||
}
|
||||
|
||||
impl TssEvents {
|
||||
fn assert_pubkeys(&self, n: usize) -> Option<VerifyingKey> {
|
||||
assert_eq!(self.pubkeys.len(), n);
|
||||
let first = self.pubkeys.values().next()?;
|
||||
for pubkey in self.pubkeys.values() {
|
||||
assert_eq!(pubkey, first);
|
||||
}
|
||||
Some(*first)
|
||||
}
|
||||
|
||||
fn assert_signatures(&self, n: usize, pubkey: &VerifyingKey, id: Id, message: &[u8]) {
|
||||
let signatures = self.signatures.get(&id).unwrap();
|
||||
assert_eq!(signatures.len(), n);
|
||||
for sig in signatures.values() {
|
||||
pubkey.verify(message, sig).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RequestFaultInjector = Box<dyn FnMut(Peer, Peer, TssRequest<Id>) -> Option<TssRequest<Id>>>;
|
||||
type ResponseFaultInjector =
|
||||
Box<dyn FnMut(Peer, Peer, Option<TssResponse<Id>>) -> Option<Option<TssResponse<Id>>>>;
|
||||
|
||||
struct TssTester {
|
||||
tss: Vec<Tss<Id, Peer>>,
|
||||
events: TssEvents,
|
||||
request_fault_injector: RequestFaultInjector,
|
||||
response_fault_injector: ResponseFaultInjector,
|
||||
}
|
||||
|
||||
impl TssTester {
|
||||
pub fn new(n: usize, t: usize) -> Self {
|
||||
Self::new_with_fault_injector(
|
||||
n,
|
||||
t,
|
||||
Box::new(|_, _, msg| Some(msg)),
|
||||
Box::new(|_, _, msg| Some(msg)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_fault_injector(
|
||||
n: usize,
|
||||
t: usize,
|
||||
request_fault_injector: RequestFaultInjector,
|
||||
response_fault_injector: ResponseFaultInjector,
|
||||
) -> Self {
|
||||
let members = (0..n).map(|i| i as _).collect::<BTreeSet<_>>();
|
||||
let mut tss = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
tss.push(Tss::new(i as _, members.clone(), t as _, None));
|
||||
}
|
||||
Self {
|
||||
tss,
|
||||
events: Default::default(),
|
||||
request_fault_injector,
|
||||
response_fault_injector,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(&mut self, id: u8, data: &[u8]) {
|
||||
for tss in &mut self.tss {
|
||||
tss.on_sign(id, data.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> TssEvents {
|
||||
loop {
|
||||
let mut progress = false;
|
||||
let mut commitments = vec![];
|
||||
for i in 0..self.tss.len() {
|
||||
let from = *self.tss[i].peer_id();
|
||||
while let Some(action) = self.tss[i].next_action() {
|
||||
progress = true;
|
||||
match action {
|
||||
TssAction::Commit(commitment, _proof_of_knowledge) => {
|
||||
//verify_proof_of_knowledge(from, &commitment, proof_of_knowledge)
|
||||
// .unwrap();
|
||||
commitments.push(commitment);
|
||||
if commitments.len() == self.tss.len() {
|
||||
let commitment = compute_group_commitment(&commitments);
|
||||
for tss in &mut self.tss {
|
||||
tss.on_commit(commitment.clone());
|
||||
}
|
||||
}
|
||||
},
|
||||
TssAction::Send(msgs) => {
|
||||
for (to, msg) in msgs {
|
||||
if let Some(msg) = (self.request_fault_injector)(from, to, msg) {
|
||||
let msg = match self.tss[to as usize].on_request(from, msg) {
|
||||
Ok(msg) => msg,
|
||||
Err(error) => {
|
||||
log::error!("request error {}", error);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
if let Some(msg) = (self.response_fault_injector)(to, from, msg)
|
||||
{
|
||||
self.tss[from as usize].on_response(to, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
TssAction::PublicKey(pubkey) => {
|
||||
log::info!("{} action pubkey", from);
|
||||
assert!(self.events.pubkeys.insert(from, pubkey).is_none());
|
||||
},
|
||||
TssAction::Signature(id, _hash, sig) => {
|
||||
log::info!("{} action {} signature", from, id);
|
||||
assert!(self
|
||||
.events
|
||||
.signatures
|
||||
.entry(id)
|
||||
.or_default()
|
||||
.insert(from, sig)
|
||||
.is_none());
|
||||
},
|
||||
TssAction::Failure => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
if !progress {
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::mem::take(&mut self.events)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
env_logger::try_init().ok();
|
||||
let n = 3;
|
||||
let t = 3;
|
||||
let sigs = n - t + 1;
|
||||
let msg = b"a message";
|
||||
let mut tester = TssTester::new(n, t);
|
||||
let pubkey = tester.run().assert_pubkeys(n).unwrap();
|
||||
tester.sign(0, msg);
|
||||
tester.run().assert_signatures(sigs, &pubkey, 0, msg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_signing_sessions() {
|
||||
env_logger::try_init().ok();
|
||||
let n = 3;
|
||||
let t = 3;
|
||||
let sigs = n - t + 1;
|
||||
let msg_a = b"a message";
|
||||
let msg_b = b"another message";
|
||||
let mut tester = TssTester::new(n, t);
|
||||
let pubkey = tester.run().assert_pubkeys(n).unwrap();
|
||||
tester.sign(0, msg_a);
|
||||
tester.sign(1, msg_b);
|
||||
let events = tester.run();
|
||||
events.assert_signatures(sigs, &pubkey, 0, msg_a);
|
||||
events.assert_signatures(sigs, &pubkey, 1, msg_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_threshold_sign() {
|
||||
env_logger::try_init().ok();
|
||||
let n = 3;
|
||||
let t = 2;
|
||||
let sigs = n - t + 1;
|
||||
let msg = b"a message";
|
||||
let mut tester = TssTester::new(n, t);
|
||||
let pubkey = tester.run().assert_pubkeys(n).unwrap();
|
||||
tester.sign(0, msg);
|
||||
tester.run().assert_signatures(sigs, &pubkey, 0, msg);
|
||||
}
|
||||
|
||||
/*#[test]
|
||||
fn test_fault_dkg() {
|
||||
env_logger::try_init().ok();
|
||||
let n = 3;
|
||||
let t = 3;
|
||||
let mut tester = TssTester::new_with_fault_injector(
|
||||
n,
|
||||
t,
|
||||
Box::new(|peer_id, msg| {
|
||||
if peer_id == 0 {
|
||||
if let TssRequest::Dkg { msg: DkgMessage::DkgR2 { .. } } = msg {
|
||||
let round2_package = frost_evm::keys::dkg::round2::Package::new(
|
||||
SigningShare::deserialize([42; 32]).unwrap(),
|
||||
);
|
||||
return Some(TssRequest::Dkg {
|
||||
msg: TssRequest::DkgR2 { round2_package },
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(msg)
|
||||
}),
|
||||
);
|
||||
// the only one succeeding in generating a pubkey would be peer 0
|
||||
tester.run().assert_pubkeys(1);
|
||||
}*/
|
||||
|
||||
/*#[test]
|
||||
fn test_fault_sign() {
|
||||
env_logger::try_init().ok();
|
||||
let n = 3;
|
||||
let t = 2;
|
||||
let sigs = n - t + 1;
|
||||
let msg = b"a message";
|
||||
let mut tester = TssTester::new_with_fault_injector(
|
||||
n,
|
||||
t,
|
||||
Box::new(|peer_id, mut msg| {
|
||||
if peer_id == 0 {
|
||||
if let TssRequest::Roast {
|
||||
msg: RoastRequest::Sign(request),
|
||||
..
|
||||
} = &mut msg
|
||||
{
|
||||
request.signature_share = SignatureShare::deserialize([42; 32]).unwrap();
|
||||
}
|
||||
}
|
||||
Some(msg)
|
||||
}),
|
||||
);
|
||||
let pubkey = tester.run().assert_pubkeys(n).unwrap();
|
||||
tester.sign(0, msg);
|
||||
tester.run().assert_signatures(sigs, &pubkey, 0, msg);
|
||||
}*/
|
Loading…
Reference in New Issue