diff --git a/tests/processor/Cargo.toml b/tests/processor/Cargo.toml index 2eba0940..fdfd23c3 100644 --- a/tests/processor/Cargo.toml +++ b/tests/processor/Cargo.toml @@ -23,8 +23,8 @@ zeroize = { version = "1", default-features = false } rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } curve25519-dalek = "4" -ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["secp256k1", "ristretto"] } -dkg = { path = "../../crypto/dkg", default-features = false, features = ["tests"] } +ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["secp256k1", "ed25519", "ristretto"] } +dkg = { path = "../../crypto/dkg", default-features = false, features = ["std"] } bitcoin-serai = { path = "../../networks/bitcoin" } diff --git a/tests/processor/src/lib.rs b/tests/processor/src/lib.rs index c5dc678a..a54f58ec 100644 --- a/tests/processor/src/lib.rs +++ b/tests/processor/src/lib.rs @@ -3,9 +3,16 @@ use std::sync::{OnceLock, Mutex}; use zeroize::Zeroizing; -use rand_core::{RngCore, OsRng}; +use rand_core::OsRng; -use ciphersuite::{group::ff::PrimeField, Ciphersuite, Ristretto}; +use ciphersuite::{ + group::{ + ff::{Field, PrimeField}, + GroupEncoding, + }, + Ciphersuite, Secp256k1, Ed25519, Ristretto, +}; +use dkg::evrf::*; use serai_client::primitives::NetworkId; use messages::{ProcessorMessage, CoordinatorMessage}; @@ -24,13 +31,40 @@ mod tests; static UNIQUE_ID: OnceLock> = OnceLock::new(); +#[allow(dead_code)] +#[derive(Clone)] +pub struct EvrfPublicKeys { + substrate: [u8; 32], + network: Vec, +} + pub fn processor_instance( network: NetworkId, port: u32, message_queue_key: ::F, -) -> Vec { - let mut entropy = [0; 32]; - OsRng.fill_bytes(&mut entropy); +) -> (Vec, EvrfPublicKeys) { + let substrate_evrf_key = + <::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng); + let substrate_evrf_pub_key = + (::EmbeddedCurve::generator() * substrate_evrf_key).to_bytes(); + let substrate_evrf_key = substrate_evrf_key.to_repr(); + + let (network_evrf_key, network_evrf_pub_key) = match network { + NetworkId::Serai => panic!("starting a processor for Serai"), + NetworkId::Bitcoin | NetworkId::Ethereum => { + let evrf_key = + <::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng); + let pub_key = + (::EmbeddedCurve::generator() * evrf_key).to_bytes().to_vec(); + (evrf_key.to_repr(), pub_key) + } + NetworkId::Monero => { + let evrf_key = <::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng); + let pub_key = + (::EmbeddedCurve::generator() * evrf_key).to_bytes().to_vec(); + (evrf_key.to_repr(), pub_key) + } + }; let network_str = match network { NetworkId::Serai => panic!("starting a processor for Serai"), @@ -47,7 +81,8 @@ pub fn processor_instance( .replace_env( [ ("MESSAGE_QUEUE_KEY".to_string(), hex::encode(message_queue_key.to_repr())), - ("ENTROPY".to_string(), hex::encode(entropy)), + ("SUBSTRATE_EVRF_KEY".to_string(), hex::encode(substrate_evrf_key)), + ("NETWORK_EVRF_KEY".to_string(), hex::encode(network_evrf_key)), ("NETWORK".to_string(), network_str.to_string()), ("NETWORK_RPC_LOGIN".to_string(), format!("{RPC_USER}:{RPC_PASS}")), ("NETWORK_RPC_PORT".to_string(), port.to_string()), @@ -75,20 +110,25 @@ pub fn processor_instance( ); } - res + (res, EvrfPublicKeys { substrate: substrate_evrf_pub_key, network: network_evrf_pub_key }) +} + +pub struct ProcessorKeys { + coordinator: ::F, + evrf: EvrfPublicKeys, } pub type Handles = (String, String, String, String); pub fn processor_stack( network: NetworkId, network_hostname_override: Option, -) -> (Handles, ::F, Vec) { +) -> (Handles, ProcessorKeys, Vec) { let (network_composition, network_rpc_port) = network_instance(network); let (coord_key, message_queue_keys, message_queue_composition) = serai_message_queue_tests::instance(); - let mut processor_compositions = + let (mut processor_compositions, evrf_keys) = processor_instance(network, network_rpc_port, message_queue_keys[&network]); // Give every item in this stack a unique ID @@ -155,7 +195,7 @@ pub fn processor_stack( handles[2].clone(), handles.get(3).cloned().unwrap_or(String::new()), ), - coord_key, + ProcessorKeys { coordinator: coord_key, evrf: evrf_keys }, compositions, ) } @@ -170,6 +210,8 @@ pub struct Coordinator { processor_handle: String, relayer_handle: String, + evrf_keys: EvrfPublicKeys, + next_send_id: u64, next_recv_id: u64, queue: MessageQueue, @@ -180,7 +222,7 @@ impl Coordinator { network: NetworkId, ops: &DockerOperations, handles: Handles, - coord_key: ::F, + keys: ProcessorKeys, ) -> Coordinator { let rpc = ops.handle(&handles.1).host_port(2287).unwrap(); let rpc = rpc.0.to_string() + ":" + &rpc.1.to_string(); @@ -193,9 +235,11 @@ impl Coordinator { processor_handle: handles.2, relayer_handle: handles.3, + evrf_keys: keys.evrf, + next_send_id: 0, next_recv_id: 0, - queue: MessageQueue::new(Service::Coordinator, rpc, Zeroizing::new(coord_key)), + queue: MessageQueue::new(Service::Coordinator, rpc, Zeroizing::new(keys.coordinator)), }; // Sleep for up to a minute in case the external network's RPC has yet to start @@ -302,6 +346,11 @@ impl Coordinator { res } + /// Get the eVRF keys for the associated processor. + pub fn evrf_keys(&self) -> EvrfPublicKeys { + self.evrf_keys.clone() + } + /// Send a message to a processor as its coordinator. pub async fn send_message(&mut self, msg: impl Into) { let msg: CoordinatorMessage = msg.into(); diff --git a/tests/processor/src/tests/batch.rs b/tests/processor/src/tests/batch.rs index 6170270a..5d5d6475 100644 --- a/tests/processor/src/tests/batch.rs +++ b/tests/processor/src/tests/batch.rs @@ -3,6 +3,8 @@ use std::{ time::{SystemTime, Duration}, }; +use rand_core::RngCore; + use dkg::{Participant, tests::clone_without}; use messages::{coordinator::*, SubstrateContext}; diff --git a/tests/processor/src/tests/key_gen.rs b/tests/processor/src/tests/key_gen.rs index 7dea0bfd..c84bb7fb 100644 --- a/tests/processor/src/tests/key_gen.rs +++ b/tests/processor/src/tests/key_gen.rs @@ -1,30 +1,24 @@ -use std::{collections::HashMap, time::SystemTime}; +use std::time::SystemTime; -use dkg::{Participant, ThresholdParams, tests::clone_without}; +use dkg::Participant; use serai_client::{ primitives::{NetworkId, BlockHash, PublicKey}, validator_sets::primitives::{Session, KeyPair}, }; -use messages::{SubstrateContext, key_gen::KeyGenId, CoordinatorMessage, ProcessorMessage}; +use messages::{SubstrateContext, CoordinatorMessage, ProcessorMessage}; use crate::{*, tests::*}; pub(crate) async fn key_gen(coordinators: &mut [Coordinator]) -> KeyPair { // Perform an interaction with all processors via their coordinators - async fn interact_with_all< - FS: Fn(Participant) -> messages::key_gen::CoordinatorMessage, - FR: FnMut(Participant, messages::key_gen::ProcessorMessage), - >( + async fn interact_with_all( coordinators: &mut [Coordinator], - message: FS, mut recv: FR, ) { for (i, coordinator) in coordinators.iter_mut().enumerate() { let participant = Participant::new(u16::try_from(i + 1).unwrap()).unwrap(); - coordinator.send_message(CoordinatorMessage::KeyGen(message(participant))).await; - match coordinator.recv_message().await { ProcessorMessage::KeyGen(msg) => recv(participant, msg), _ => panic!("processor didn't return KeyGen message"), @@ -33,85 +27,67 @@ pub(crate) async fn key_gen(coordinators: &mut [Coordinator]) -> KeyPair { } // Order a key gen - let id = KeyGenId { session: Session(0), attempt: 0 }; + let session = Session(0); - let mut commitments = HashMap::new(); - interact_with_all( - coordinators, - |participant| messages::key_gen::CoordinatorMessage::GenerateKey { - id, - params: ThresholdParams::new( - u16::try_from(THRESHOLD).unwrap(), - u16::try_from(COORDINATORS).unwrap(), + let mut evrf_public_keys = vec![]; + for coordinator in &*coordinators { + let keys = coordinator.evrf_keys(); + evrf_public_keys.push((keys.substrate, keys.network)); + } + + let mut participations = vec![]; + for coordinator in &mut *coordinators { + coordinator + .send_message(CoordinatorMessage::KeyGen( + messages::key_gen::CoordinatorMessage::GenerateKey { + session, + threshold: u16::try_from(THRESHOLD).unwrap(), + evrf_public_keys: evrf_public_keys.clone(), + }, + )) + .await; + } + // This takes forever on debug, as we use in these tests + tokio::time::sleep(core::time::Duration::from_secs(600)).await; + interact_with_all(coordinators, |participant, msg| match msg { + messages::key_gen::ProcessorMessage::Participation { session: this_session, participation } => { + assert_eq!(this_session, session); + participations.push(messages::key_gen::CoordinatorMessage::Participation { + session, participant, - ) - .unwrap(), - shares: 1, - }, - |participant, msg| match msg { - messages::key_gen::ProcessorMessage::Commitments { - id: this_id, - commitments: mut these_commitments, - } => { - assert_eq!(this_id, id); - assert_eq!(these_commitments.len(), 1); - commitments.insert(participant, these_commitments.swap_remove(0)); - } - _ => panic!("processor didn't return Commitments in response to GenerateKey"), - }, - ) + participation, + }); + } + _ => panic!("processor didn't return Participation in response to GenerateKey"), + }) .await; - // Send the commitments to all parties - let mut shares = HashMap::new(); - interact_with_all( - coordinators, - |participant| messages::key_gen::CoordinatorMessage::Commitments { - id, - commitments: clone_without(&commitments, &participant), - }, - |participant, msg| match msg { - messages::key_gen::ProcessorMessage::Shares { id: this_id, shares: mut these_shares } => { - assert_eq!(this_id, id); - assert_eq!(these_shares.len(), 1); - shares.insert(participant, these_shares.swap_remove(0)); - } - _ => panic!("processor didn't return Shares in response to GenerateKey"), - }, - ) - .await; - - // Send the shares + // Send the participations let mut substrate_key = None; let mut network_key = None; - interact_with_all( - coordinators, - |participant| messages::key_gen::CoordinatorMessage::Shares { - id, - shares: vec![shares - .iter() - .filter_map(|(this_participant, shares)| { - shares.get(&participant).cloned().map(|share| (*this_participant, share)) - }) - .collect()], - }, - |_, msg| match msg { - messages::key_gen::ProcessorMessage::GeneratedKeyPair { - id: this_id, - substrate_key: this_substrate_key, - network_key: this_network_key, - } => { - assert_eq!(this_id, id); - if substrate_key.is_none() { - substrate_key = Some(this_substrate_key); - network_key = Some(this_network_key.clone()); - } - assert_eq!(substrate_key.unwrap(), this_substrate_key); - assert_eq!(network_key.as_ref().unwrap(), &this_network_key); + for participation in participations { + for coordinator in &mut *coordinators { + coordinator.send_message(participation.clone()).await; + } + } + // This also takes a while on debug + tokio::time::sleep(core::time::Duration::from_secs(240)).await; + interact_with_all(coordinators, |_, msg| match msg { + messages::key_gen::ProcessorMessage::GeneratedKeyPair { + session: this_session, + substrate_key: this_substrate_key, + network_key: this_network_key, + } => { + assert_eq!(this_session, session); + if substrate_key.is_none() { + substrate_key = Some(this_substrate_key); + network_key = Some(this_network_key.clone()); } - _ => panic!("processor didn't return GeneratedKeyPair in response to GenerateKey"), - }, - ) + assert_eq!(substrate_key.unwrap(), this_substrate_key); + assert_eq!(network_key.as_ref().unwrap(), &this_network_key); + } + _ => panic!("processor didn't return GeneratedKeyPair in response to all Participations"), + }) .await; // Confirm the key pair @@ -132,7 +108,7 @@ pub(crate) async fn key_gen(coordinators: &mut [Coordinator]) -> KeyPair { .send_message(CoordinatorMessage::Substrate( messages::substrate::CoordinatorMessage::ConfirmKeyPair { context, - session: id.session, + session, key_pair: key_pair.clone(), }, )) diff --git a/tests/processor/src/tests/mod.rs b/tests/processor/src/tests/mod.rs index afda97d5..62d22098 100644 --- a/tests/processor/src/tests/mod.rs +++ b/tests/processor/src/tests/mod.rs @@ -1,5 +1,3 @@ -use ciphersuite::{Ciphersuite, Ristretto}; - use serai_client::primitives::NetworkId; use dockertest::DockerTest; @@ -17,18 +15,18 @@ mod send; pub(crate) const COORDINATORS: usize = 4; pub(crate) const THRESHOLD: usize = ((COORDINATORS * 2) / 3) + 1; -fn new_test(network: NetworkId) -> (Vec<(Handles, ::F)>, DockerTest) { +fn new_test(network: NetworkId) -> (Vec<(Handles, ProcessorKeys)>, DockerTest) { let mut coordinators = vec![]; let mut test = DockerTest::new().with_network(dockertest::Network::Isolated); let mut eth_handle = None; for _ in 0 .. COORDINATORS { - let (handles, coord_key, compositions) = processor_stack(network, eth_handle.clone()); + let (handles, keys, compositions) = processor_stack(network, eth_handle.clone()); // TODO: Remove this once https://github.com/foundry-rs/foundry/issues/7955 // This has all processors share an Ethereum node until we can sync controlled nodes if network == NetworkId::Ethereum { eth_handle = eth_handle.or_else(|| Some(handles.0.clone())); } - coordinators.push((handles, coord_key)); + coordinators.push((handles, keys)); for composition in compositions { test.provide_container(composition); } diff --git a/tests/processor/src/tests/send.rs b/tests/processor/src/tests/send.rs index 62e80c09..93091a5e 100644 --- a/tests/processor/src/tests/send.rs +++ b/tests/processor/src/tests/send.rs @@ -3,6 +3,8 @@ use std::{ time::{SystemTime, Duration}, }; +use rand_core::RngCore; + use dkg::{Participant, tests::clone_without}; use messages::{sign::SignId, SubstrateContext};