WIP add some state machine stuff from dvc

This commit is contained in:
sosaucily 2024-01-29 14:08:21 +07:00
parent e1fb9bc953
commit 89b2f8842a
10 changed files with 1535 additions and 5 deletions

View File

@ -8,5 +8,6 @@ members = [
"frost-ristretto255",
"frost-secp256k1",
"frost-rerandomized",
"gencode"
"gencode",
"tss",
]

View File

@ -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))]

View File

@ -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::{

View File

@ -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) {

18
tss/Cargo.toml Normal file
View File

@ -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"

159
tss/src/dkg.rs Normal file
View File

@ -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 => {},
}
}
}
}
}

357
tss/src/lib.rs Normal file
View File

@ -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
}
}

321
tss/src/roast.rs Normal file
View File

@ -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(());
}
}
}
}
}
}
}

409
tss/src/rts.rs Normal file
View File

@ -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!();
}
}

241
tss/src/tests.rs Normal file
View File

@ -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);
}*/