diff --git a/Cargo.lock b/Cargo.lock index edcc0a66..c3ed3ef6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1319,6 +1319,7 @@ dependencies = [ "ciphersuite", "flexible-transcript", "futures", + "hex", "lazy_static", "log", "modular-frost", @@ -1326,6 +1327,7 @@ dependencies = [ "processor-messages", "rand_core 0.6.4", "schnorr-signatures", + "schnorrkel", "serai-client", "serai-db", "sp-application-crypto", diff --git a/coordinator/Cargo.toml b/coordinator/Cargo.toml index 548ba53c..199d1076 100644 --- a/coordinator/Cargo.toml +++ b/coordinator/Cargo.toml @@ -29,6 +29,7 @@ frost = { package = "modular-frost", path = "../crypto/frost" } scale = { package = "parity-scale-codec", version = "3", features = ["derive"] } +schnorrkel = "0.10" sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false } serai-db = { path = "../common/db" } @@ -38,6 +39,7 @@ tributary = { package = "tributary-chain", path = "./tributary" } serai-client = { path = "../substrate/client", features = ["serai"] } +hex = "0.4" log = "0.4" tokio = { version = "1", features = ["full"] } diff --git a/coordinator/src/main.rs b/coordinator/src/main.rs index f1e3f9f6..11d19f57 100644 --- a/coordinator/src/main.rs +++ b/coordinator/src/main.rs @@ -12,10 +12,22 @@ use std::{ use zeroize::Zeroizing; use rand_core::OsRng; -use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto}; +use blake2::Digest; + +use ciphersuite::{ + group::{ + ff::{Field, PrimeField}, + GroupEncoding, + }, + Ciphersuite, Ristretto, +}; use serai_db::{DbTxn, Db, MemDb}; -use serai_client::Serai; + +use serai_client::{ + subxt::{config::extrinsic_params::BaseExtrinsicParamsBuilder, tx::Signer}, + Public, PairSigner, Serai, +}; use tokio::{ sync::{ @@ -316,8 +328,11 @@ pub async fn handle_p2p( // missing // sync_block waiting is preferable since we know the block is valid by its commit, meaning // we are the node behind - // A for 1/2, 1 may be preferable since this message may frequently occur - // We at least need to check if we take value from this message before running spawn + // As for 1/2, 1 may be preferable since this message may frequently occur + // This is suitably performant, as tokio HTTP servers will even spawn a new task per + // connection + // In order to reduce congestion though, we should at least check if we take value from + // this message before running spawn // TODO tokio::spawn({ let tributaries = tributaries.clone(); @@ -362,11 +377,25 @@ pub async fn publish_transaction( pub async fn handle_processors( mut db: D, key: Zeroizing<::F>, + serai: Serai, mut processors: Pro, tributaries: Arc>>>, ) { let pub_key = Ristretto::generator() * key.deref(); + // TODO: This is cursed. serai_client has to handle this for us + let substrate_signer = { + let mut bytes = Zeroizing::new([0; 96]); + // Private key + bytes[.. 32].copy_from_slice(&key.to_repr()); + // Nonce + let nonce = Zeroizing::new(blake2::Blake2s256::digest(&bytes)); + bytes[32 .. 64].copy_from_slice(nonce.as_ref()); + // Public key + bytes[64 ..].copy_from_slice(&pub_key.to_bytes()); + PairSigner::new(schnorrkel::keys::Keypair::from_bytes(bytes.as_ref()).unwrap().into()) + }; + loop { let msg = processors.recv().await; @@ -384,15 +413,57 @@ pub async fn handle_processors( }; let tx = match msg.msg { - ProcessorMessage::KeyGen(msg) => match msg { + ProcessorMessage::KeyGen(inner_msg) => match inner_msg { key_gen::ProcessorMessage::Commitments { id, commitments } => { Some(Transaction::DkgCommitments(id.attempt, commitments, Transaction::empty_signed())) } key_gen::ProcessorMessage::Shares { id, shares } => { Some(Transaction::DkgShares(id.attempt, shares, Transaction::empty_signed())) } - // TODO - key_gen::ProcessorMessage::GeneratedKeyPair { .. } => todo!(), + key_gen::ProcessorMessage::GeneratedKeyPair { id, substrate_key, coin_key } => { + assert_eq!( + id.set.network, msg.network, + "processor claimed to be a different network than it was for SubstrateBlockAck", + ); + // TODO: Also check the other KeyGenId fields + + // TODO: Is this safe? + let Ok(nonce) = serai.get_nonce(&substrate_signer.address()).await else { + log::error!("couldn't connect to Serai node to get nonce"); + todo!(); // TODO + }; + + let tx = serai + .sign( + &substrate_signer, + &Serai::vote( + msg.network, + ( + Public(substrate_key), + coin_key + .try_into() + .expect("external key from processor exceeded max external key length"), + ), + ), + nonce, + BaseExtrinsicParamsBuilder::new(), + ) + .expect( + "tried to sign an invalid payload despite creating the payload via serai_client", + ); + + match serai.publish(&tx).await { + Ok(hash) => { + log::info!("voted on key pair for {:?} in TX {}", id.set, hex::encode(hash)) + } + Err(e) => { + log::error!("couldn't connect to Serai node to publish TX: {:?}", e); + todo!(); // TODO + } + } + + None + } }, ProcessorMessage::Sign(msg) => match msg { sign::ProcessorMessage::Preprocess { id, preprocess } => { @@ -423,8 +494,8 @@ pub async fn handle_processors( ProcessorMessage::Coordinator(inner_msg) => match inner_msg { coordinator::ProcessorMessage::SubstrateBlockAck { network, block, plans } => { assert_eq!( - msg.network, network, - "processor claimed to be a different network than it was", + network, msg.network, + "processor claimed to be a different network than it was for SubstrateBlockAck", ); // Safe to use its own txn since this is static and just needs to be written before we @@ -600,7 +671,7 @@ pub async fn run( tokio::spawn(handle_p2p(Ristretto::generator() * key.deref(), p2p, tributaries.clone())); // Handle all messages from processors - handle_processors(raw_db, key, processors, tributaries).await; + handle_processors(raw_db, key, serai, processors, tributaries).await; } #[tokio::main] diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index 73abd1a7..559d7860 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -4,7 +4,10 @@ use scale::{Encode, Decode, Compact}; mod scale_value; pub(crate) use scale_value::{Value, Composite, scale_value, scale_composite}; -use sp_core::{Pair as PairTrait, sr25519::Pair}; +pub use sp_core::{ + Pair as PairTrait, + sr25519::{Public, Pair}, +}; pub use subxt; use subxt::{ @@ -294,6 +297,7 @@ impl Serai { TxClient::new(self.0.offline()) .create_signed_with_nonce(payload, signer, nonce, params) .map(|tx| Encoded(tx.into_encoded())) + // TODO: Don't have this potentially return an error (requires modifying the Payload type) .map_err(|_| SeraiError::InvalidRuntime) }