diff --git a/Cargo.lock b/Cargo.lock index a6b1c37e..3fcda89f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8431,6 +8431,7 @@ dependencies = [ "blake2", "borsh", "ciphersuite", + "dkg", "log", "parity-scale-codec", "rand_core", diff --git a/coordinator/tributary/Cargo.toml b/coordinator/tributary/Cargo.toml index 3e374bc0..431dae3c 100644 --- a/coordinator/tributary/Cargo.toml +++ b/coordinator/tributary/Cargo.toml @@ -21,13 +21,14 @@ workspace = true zeroize = { version = "^1.5", default-features = false, features = ["std"] } rand_core = { version = "0.6", default-features = false, features = ["std"] } -blake2 = { version = "0.10", default-features = false, features = ["std"] } -ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] } -schnorr = { package = "schnorr-signatures", path = "../../crypto/schnorr", default-features = false, features = ["std"] } - scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std", "derive"] } borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] } +blake2 = { version = "0.10", default-features = false, features = ["std"] } +ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] } +dkg = { path = "../../crypto/dkg", default-features = false, features = ["std"] } +schnorr = { package = "schnorr-signatures", path = "../../crypto/schnorr", default-features = false, features = ["std"] } + serai-client = { path = "../../substrate/client", default-features = false, features = ["serai", "borsh"] } serai-db = { path = "../../common/db" } diff --git a/coordinator/tributary/src/db.rs b/coordinator/tributary/src/db.rs index 5812063e..1a475734 100644 --- a/coordinator/tributary/src/db.rs +++ b/coordinator/tributary/src/db.rs @@ -229,7 +229,11 @@ create_db!( db_channel!( CoordinatorTributary { + // Messages to send to the processor ProcessorMessages: (set: ValidatorSet) -> messages::CoordinatorMessage, + // Messages for the DKG confirmation + DkgConfirmationMessages: (set: ValidatorSet) -> messages::sign::CoordinatorMessage, + // Topics which have been explicitly recognized RecognizedTopics: (set: ValidatorSet) -> Topic, } ); diff --git a/coordinator/tributary/src/lib.rs b/coordinator/tributary/src/lib.rs index d8390511..00bd5f51 100644 --- a/coordinator/tributary/src/lib.rs +++ b/coordinator/tributary/src/lib.rs @@ -5,7 +5,10 @@ use core::{marker::PhantomData, future::Future}; use std::collections::HashMap; +use scale::Encode; + use ciphersuite::group::GroupEncoding; +use dkg::Participant; use serai_client::{ primitives::SeraiAddress, @@ -27,7 +30,7 @@ use tributary_sdk::{ use serai_cosign::CosignIntent; use serai_coordinator_substrate::NewSetInformation; -use messages::sign::VariantSignId; +use messages::sign::{VariantSignId, SignId}; mod transaction; pub use transaction::{SigningProtocolRound, Signed, Transaction}; @@ -45,6 +48,24 @@ impl ProcessorMessages { } } +/// Messages for the DKG confirmation. +pub struct DkgConfirmationMessages; +impl DkgConfirmationMessages { + /// Receive a message for the DKG confirmation. + /// + /// These messages use the ProcessorMessage API as that's what existing flows are designed + /// around, enabling their reuse. The ProcessorMessage includes a VariantSignId which isn't + /// applicable to the DKG confirmation (as there's no such variant of the VariantSignId). The + /// actual ID is undefined other than it will be consistent to the signing protocol and unique + /// across validator sets, with no guarantees of uniqueness across contexts. + pub fn try_recv( + txn: &mut impl DbTxn, + set: ValidatorSet, + ) -> Option { + db::DkgConfirmationMessages::try_recv(txn, set) + } +} + /// The cosign intents. pub struct CosignIntents; impl CosignIntents { @@ -158,6 +179,62 @@ impl<'a, TD: Db, TDT: DbTxn, P: P2p> ScanBlock<'a, TD, TDT, P> { }, ); } + + fn accumulate_dkg_confirmation + Borshy>( + &mut self, + block_number: u64, + topic: Topic, + attempt: u32, + data: &D, + signer: SeraiAddress, + ) -> Option<(SignId, HashMap>)> { + match TributaryDb::accumulate::( + self.tributary_txn, + self.set.set, + self.validators, + self.total_weight, + block_number, + topic, + signer, + self.validator_weights[&signer], + data, + ) { + DataSet::None => None, + DataSet::Participating(data_set) => { + // Consistent ID for the DKG confirmation, unqie across sets + let id = { + let mut id = [0; 32]; + let encoded_set = self.set.set.encode(); + id[.. encoded_set.len()].copy_from_slice(&encoded_set); + VariantSignId::Batch(id) + }; + let id = SignId { session: self.set.set.session, id, attempt }; + + // This will be used in a MuSig protocol, so the Participant indexes are the validator's + // position in the list regardless of their weight + let flatten_data_set = |data_set: HashMap<_, D>| { + let mut entries = HashMap::with_capacity(usize::from(self.total_weight)); + for (validator, participation) in data_set { + let (index, (_validator, _weight)) = &self + .set + .validators + .iter() + .enumerate() + .find(|(_i, (validator_i, _weight))| validator == *validator_i) + .unwrap(); + entries.insert( + Participant::new(u16::try_from(*index).unwrap()).unwrap(), + participation.as_ref().to_vec(), + ); + } + entries + }; + let data_set = flatten_data_set(data_set); + Some((id, data_set)) + } + } + } + fn handle_application_tx(&mut self, block_number: u64, tx: Transaction) { let signer = |signed: Signed| SeraiAddress(signed.signer().to_bytes()); @@ -226,12 +303,36 @@ impl<'a, TD: Db, TDT: DbTxn, P: P2p> ScanBlock<'a, TD, TDT, P> { ); } Transaction::DkgConfirmationPreprocess { attempt, preprocess, signed } => { - // Accumulate the preprocesses into our own FROST attempt manager - todo!("TODO") + let topic = topic.unwrap(); + let signer = signer(signed); + + let Some((id, data_set)) = + self.accumulate_dkg_confirmation(block_number, topic, attempt, &preprocess, signer) + else { + return; + }; + + db::DkgConfirmationMessages::send( + self.tributary_txn, + self.set.set, + &messages::sign::CoordinatorMessage::Preprocesses { id, preprocesses: data_set }, + ); } Transaction::DkgConfirmationShare { attempt, share, signed } => { - // Accumulate the shares into our own FROST attempt manager - todo!("TODO: SetKeysTask") + let topic = topic.unwrap(); + let signer = signer(signed); + + let Some((id, data_set)) = + self.accumulate_dkg_confirmation(block_number, topic, attempt, &share, signer) + else { + return; + }; + + db::DkgConfirmationMessages::send( + self.tributary_txn, + self.set.set, + &messages::sign::CoordinatorMessage::Shares { id, shares: data_set }, + ); } Transaction::Cosign { substrate_block_hash } => { @@ -405,7 +506,7 @@ impl<'a, TD: Db, TDT: DbTxn, P: P2p> ScanBlock<'a, TD, TDT, P> { }; } - Transaction::Sign { id, attempt, round, data, signed } => { + Transaction::Sign { id: _, attempt: _, round, data, signed } => { let topic = topic.unwrap(); let signer = signer(signed); @@ -458,7 +559,7 @@ impl<'a, TD: Db, TDT: DbTxn, P: P2p> ScanBlock<'a, TD, TDT, P> { }, ) } - }; + } } } }