6 Commits

Author SHA1 Message Date
Luke Parker
8c50a31633 Improve documentation on functions 2024-08-06 00:27:54 -04:00
Luke Parker
d943e037e5 Remove bad panic in coordinator
It expected ConfirmationShare to be n-of-n, not t-of-n.
2024-08-06 00:27:19 -04:00
Luke Parker
3042697243 Update orchestration 2024-08-06 00:27:07 -04:00
Luke Parker
8de696f169 Add an extra sleep to ensure expected ordering of Participations 2024-08-05 23:50:23 -04:00
Luke Parker
b8912e4b7b cargo machete 2024-08-05 23:43:38 -04:00
Luke Parker
89fc88b283 Get clippy to pass across the repo 2024-08-05 23:29:51 -04:00
23 changed files with 250 additions and 165 deletions

6
Cargo.lock generated
View File

@@ -8140,10 +8140,12 @@ dependencies = [
"ciphersuite",
"dkg",
"dockertest",
"embedwards25519",
"hex",
"parity-scale-codec",
"rand_core",
"schnorrkel",
"secq256k1",
"serai-client",
"serai-docker-tests",
"serai-message-queue",
@@ -8404,11 +8406,13 @@ name = "serai-orchestrator"
version = "0.0.1"
dependencies = [
"ciphersuite",
"embedwards25519",
"flexible-transcript",
"hex",
"home",
"rand_chacha",
"rand_core",
"secq256k1",
"zalloc",
"zeroize",
]
@@ -8418,6 +8422,7 @@ name = "serai-primitives"
version = "0.1.0"
dependencies = [
"borsh",
"ciphersuite",
"frame-support",
"parity-scale-codec",
"rand_core",
@@ -8602,7 +8607,6 @@ dependencies = [
"bitvec",
"frame-support",
"frame-system",
"hashbrown 0.14.5",
"pallet-babe",
"pallet-grandpa",
"parity-scale-codec",

View File

@@ -359,10 +359,7 @@ impl<
)
.await;
}
Accumulation::Ready(DataSet::NotParticipating) => {
panic!("wasn't a participant in DKG confirmination shares")
}
Accumulation::NotReady => {}
Accumulation::Ready(DataSet::NotParticipating) | Accumulation::NotReady => {}
}
}

View File

@@ -161,6 +161,7 @@ fn polynomial<F: PrimeField + Zeroize>(
share
}
#[allow(clippy::type_complexity)]
fn share_verification_statements<C: Ciphersuite>(
rng: &mut (impl RngCore + CryptoRng),
commitments: &[C::G],
@@ -234,6 +235,7 @@ pub struct EvrfDkg<C: EvrfCurve> {
evrf_public_keys: Vec<<C::EmbeddedCurve as Ciphersuite>::G>,
group_key: C::G,
verification_shares: HashMap<Participant, C::G>,
#[allow(clippy::type_complexity)]
encrypted_secret_shares:
HashMap<Participant, HashMap<Participant, ([<C::EmbeddedCurve as Ciphersuite>::G; 2], C::F)>>,
}

View File

@@ -65,6 +65,7 @@ pub fn musig_key<C: Ciphersuite>(context: &[u8], keys: &[C::G]) -> Result<C::G,
let transcript = binding_factor_transcript::<C>(context, keys)?;
let mut res = C::G::identity();
for i in 1 ..= keys_len {
// TODO: Calculate this with a multiexp
res += keys[usize::from(i - 1)] * binding_factor::<C>(transcript.clone(), i);
}
Ok(res)

View File

@@ -24,6 +24,8 @@ rand_chacha = { version = "0.3", default-features = false, features = ["std"] }
transcript = { package = "flexible-transcript", path = "../crypto/transcript", default-features = false, features = ["std", "recommended"] }
ciphersuite = { path = "../crypto/ciphersuite", default-features = false, features = ["std", "ristretto"] }
embedwards25519 = { path = "../crypto/evrf/embedwards25519" }
secq256k1 = { path = "../crypto/evrf/secq256k1" }
zalloc = { path = "../common/zalloc" }

View File

@@ -25,6 +25,8 @@ use ciphersuite::{
},
Ciphersuite, Ristretto,
};
use embedwards25519::Embedwards25519;
use secq256k1::Secq256k1;
mod mimalloc;
use mimalloc::mimalloc;
@@ -267,6 +269,55 @@ fn infrastructure_keys(network: Network) -> InfrastructureKeys {
])
}
struct EmbeddedCurveKeys {
embedwards25519: (Zeroizing<Vec<u8>>, Vec<u8>),
secq256k1: (Zeroizing<Vec<u8>>, Vec<u8>),
}
fn embedded_curve_keys(network: Network) -> EmbeddedCurveKeys {
// Generate entropy for the embedded curve keys
let entropy = {
let path = home::home_dir()
.unwrap()
.join(".serai")
.join(network.label())
.join("embedded_curve_keys_entropy");
// Check if there's existing entropy
if let Ok(entropy) = fs::read(&path).map(Zeroizing::new) {
assert_eq!(entropy.len(), 32, "entropy saved to disk wasn't 32 bytes");
let mut res = Zeroizing::new([0; 32]);
res.copy_from_slice(entropy.as_ref());
res
} else {
// If there isn't, generate fresh entropy
let mut res = Zeroizing::new([0; 32]);
OsRng.fill_bytes(res.as_mut());
fs::write(&path, &res).unwrap();
res
}
};
let mut transcript =
RecommendedTranscript::new(b"Serai Orchestrator Embedded Curve Keys Transcript");
transcript.append_message(b"network", network.label().as_bytes());
transcript.append_message(b"entropy", entropy);
let mut rng = ChaCha20Rng::from_seed(transcript.rng_seed(b"embedded_curve_keys"));
EmbeddedCurveKeys {
embedwards25519: {
let key = Zeroizing::new(<Embedwards25519 as Ciphersuite>::F::random(&mut rng));
let pub_key = Embedwards25519::generator() * key.deref();
(Zeroizing::new(key.to_repr().as_slice().to_vec()), pub_key.to_bytes().to_vec())
},
secq256k1: {
let key = Zeroizing::new(<Secq256k1 as Ciphersuite>::F::random(&mut rng));
let pub_key = Secq256k1::generator() * key.deref();
(Zeroizing::new(key.to_repr().as_slice().to_vec()), pub_key.to_bytes().to_vec())
},
}
}
fn dockerfiles(network: Network) {
let orchestration_path = orchestration_path(network);
@@ -294,18 +345,15 @@ fn dockerfiles(network: Network) {
monero_key.1,
);
let new_entropy = || {
let mut res = Zeroizing::new([0; 32]);
OsRng.fill_bytes(res.as_mut());
res
};
let embedded_curve_keys = embedded_curve_keys(network);
processor(
&orchestration_path,
network,
"bitcoin",
coordinator_key.1,
bitcoin_key.0,
new_entropy(),
embedded_curve_keys.embedwards25519.0.clone(),
embedded_curve_keys.secq256k1.0.clone(),
);
processor(
&orchestration_path,
@@ -313,9 +361,18 @@ fn dockerfiles(network: Network) {
"ethereum",
coordinator_key.1,
ethereum_key.0,
new_entropy(),
embedded_curve_keys.embedwards25519.0.clone(),
embedded_curve_keys.secq256k1.0.clone(),
);
processor(
&orchestration_path,
network,
"monero",
coordinator_key.1,
monero_key.0,
embedded_curve_keys.embedwards25519.0.clone(),
embedded_curve_keys.embedwards25519.0.clone(),
);
processor(&orchestration_path, network, "monero", coordinator_key.1, monero_key.0, new_entropy());
let serai_key = {
let serai_key = Zeroizing::new(
@@ -346,6 +403,7 @@ fn key_gen(network: Network) {
let _ = fs::create_dir_all(&serai_dir);
fs::write(key_file, key.to_repr()).expect("couldn't write key");
// TODO: Move embedded curve key gen here, and print them
println!(
"Public Key: {}",
hex::encode((<Ristretto as Ciphersuite>::generator() * key).to_bytes())

View File

@@ -12,8 +12,9 @@ pub fn processor(
network: Network,
coin: &'static str,
_coordinator_key: <Ristretto as Ciphersuite>::G,
coin_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
entropy: Zeroizing<[u8; 32]>,
processor_key: Zeroizing<<Ristretto as Ciphersuite>::F>,
substrate_evrf_key: Zeroizing<Vec<u8>>,
network_evrf_key: Zeroizing<Vec<u8>>,
) {
let setup = mimalloc(Os::Debian).to_string() +
&build_serai_service(
@@ -53,8 +54,9 @@ RUN apt install -y ca-certificates
let mut env_vars = vec![
("MESSAGE_QUEUE_RPC", format!("serai-{}-message-queue", network.label())),
("MESSAGE_QUEUE_KEY", hex::encode(coin_key.to_repr())),
("ENTROPY", hex::encode(entropy.as_ref())),
("MESSAGE_QUEUE_KEY", hex::encode(processor_key.to_repr())),
("SUBSTRATE_EVRF_KEY", hex::encode(substrate_evrf_key)),
("NETWORK_EVRF_KEY", hex::encode(network_evrf_key)),
("NETWORK", coin.to_string()),
("NETWORK_RPC_LOGIN", format!("{RPC_USER}:{RPC_PASS}")),
("NETWORK_RPC_HOSTNAME", hostname),

View File

@@ -5,6 +5,8 @@ use zeroize::Zeroizing;
use rand_core::OsRng;
use sp_core::{
ConstU32,
bounded_vec::BoundedVec,
sr25519::{Pair, Signature},
Pair as PairTrait,
};
@@ -14,8 +16,9 @@ use frost::dkg::musig::musig;
use schnorrkel::Schnorrkel;
use serai_client::{
primitives::EmbeddedEllipticCurve,
validator_sets::{
primitives::{ValidatorSet, KeyPair, musig_context, set_keys_message},
primitives::{MAX_KEY_LEN, ValidatorSet, KeyPair, musig_context, set_keys_message},
ValidatorSetsEvent,
},
Amount, Serai, SeraiValidatorSets,
@@ -58,7 +61,7 @@ pub async fn set_keys(
let sig = frost::tests::sign_without_caching(
&mut OsRng,
frost::tests::algorithm_machines(&mut OsRng, &Schnorrkel::new(b"substrate"), &musig_keys),
&set_keys_message(&set, &[], &key_pair),
&set_keys_message(&set, &key_pair),
);
// Set the key pair
@@ -66,8 +69,8 @@ pub async fn set_keys(
serai,
&SeraiValidatorSets::set_keys(
set.network,
vec![].try_into().unwrap(),
key_pair.clone(),
vec![1; musig_keys.len()].try_into().unwrap(),
Signature(sig.to_bytes()),
),
)

View File

@@ -6,7 +6,7 @@ use sp_core::{
};
use serai_client::{
primitives::{NETWORKS, NetworkId, BlockHash, insecure_pair_from_name},
primitives::{NETWORKS, EmbeddedEllipticCurve, NetworkId, BlockHash, insecure_pair_from_name},
validator_sets::{
primitives::{Session, ValidatorSet, KeyPair},
ValidatorSetsEvent,
@@ -21,7 +21,7 @@ use serai_client::{
mod common;
use common::{
tx::publish_tx,
validator_sets::{allocate_stake, deallocate_stake, set_keys},
validator_sets::{set_embedded_elliptic_curve_key, allocate_stake, deallocate_stake, set_keys},
};
fn get_random_key_pair() -> KeyPair {
@@ -231,9 +231,9 @@ async fn validator_set_rotation() {
{
set_embedded_elliptic_curve_key(
&serai,
&last_participant,
embedded_elliptic_curve,
vec![0; 32].try_into().unwrap(),
&last_participant,
i.try_into().unwrap(),
)
.await;

View File

@@ -4,10 +4,7 @@ use sp_core::Pair as PairTrait;
use sc_service::ChainType;
use ciphersuite::{
group::{ff::PrimeField, GroupEncoding},
Ciphersuite,
};
use ciphersuite::{group::GroupEncoding, Ciphersuite};
use embedwards25519::Embedwards25519;
use secq256k1::Secq256k1;
@@ -23,13 +20,9 @@ fn account_from_name(name: &'static str) -> PublicKey {
insecure_pair_from_name(name).public()
}
// Panics on names which are too long, or ciphersuites with weirdly encoded scalars
fn insecure_ciphersuite_key_from_name<C: Ciphersuite>(name: &'static str) -> Vec<u8> {
let mut repr = <C::F as PrimeField>::Repr::default();
let repr_len = repr.as_ref().len();
let start = (repr_len / 2) - (name.len() / 2);
repr.as_mut()[start .. (start + name.len())].copy_from_slice(name.as_bytes());
(C::generator() * C::F::from_repr(repr).unwrap()).to_bytes().as_ref().to_vec()
fn insecure_arbitrary_public_key_from_name<C: Ciphersuite>(name: &'static str) -> Vec<u8> {
let key = insecure_arbitrary_key_from_name::<C>(name);
(C::generator() * key).to_bytes().as_ref().to_vec()
}
fn wasm_binary() -> Vec<u8> {
@@ -54,10 +47,10 @@ fn devnet_genesis(
(
account_from_name(name),
AllEmbeddedEllipticCurveKeysAtGenesis {
embedwards25519: insecure_ciphersuite_key_from_name::<Embedwards25519>(name)
embedwards25519: insecure_arbitrary_public_key_from_name::<Embedwards25519>(name)
.try_into()
.unwrap(),
secq256k1: insecure_ciphersuite_key_from_name::<Secq256k1>(name).try_into().unwrap(),
secq256k1: insecure_arbitrary_public_key_from_name::<Secq256k1>(name).try_into().unwrap(),
},
)
})

View File

@@ -18,6 +18,8 @@ workspace = true
[dependencies]
zeroize = { version = "^1.5", features = ["derive"], optional = true }
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, optional = true }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"] }
@@ -35,7 +37,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
[features]
std = ["zeroize", "scale/std", "borsh?/std", "serde?/std", "scale-info/std", "sp-core/std", "sp-runtime/std", "frame-support/std"]
std = ["zeroize", "ciphersuite/std", "scale/std", "borsh?/std", "serde?/std", "scale-info/std", "sp-core/std", "sp-runtime/std", "frame-support/std"]
borsh = ["dep:borsh"]
serde = ["dep:serde"]
default = ["std"]

View File

@@ -90,11 +90,22 @@ impl std::fmt::Display for SeraiAddress {
}
}
/// Create a Substraate key pair by a name.
///
/// This should never be considered to have a secure private key. It has effectively no entropy.
#[cfg(feature = "std")]
pub fn insecure_pair_from_name(name: &str) -> Pair {
Pair::from_string(&format!("//{name}"), None).unwrap()
}
/// Create a private key for an arbitrary ciphersuite by a name.
///
/// This key should never be considered a secure private key. It has effectively no entropy.
#[cfg(feature = "std")]
pub fn insecure_arbitrary_key_from_name<C: ciphersuite::Ciphersuite>(name: &str) -> C::F {
C::hash_to_F(b"insecure arbitrary key", name.as_bytes())
}
pub struct AccountLookup;
impl Lookup for AccountLookup {
type Source = SeraiAddress;

View File

@@ -12,15 +12,11 @@ rust-version = "1.74"
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.cargo-machete]
ignored = ["scale", "scale-info"]
[lints]
workspace = true
[dependencies]
bitvec = { version = "1", default-features = false, features = ["alloc", "serde"] }
hashbrown = { version = "0.14", default-features = false, features = ["ahash", "inline-more"] }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "bit-vec"] }
scale-info = { version = "2", default-features = false, features = ["derive", "bit-vec"] }

View File

@@ -1002,7 +1002,7 @@ pub mod pallet {
EmbeddedEllipticCurve::Secq256k1 => 33,
};
if key.len() != expected_len {
Err(Error::InvalidEmbeddedEllipticCurveKey)?;
Err(Error::<T>::InvalidEmbeddedEllipticCurveKey)?;
}
// This does allow overwriting an existing key which... is unlikely to be done?

View File

@@ -24,7 +24,11 @@ zeroize = { version = "1", default-features = false }
rand_core = { version = "0.6", default-features = false }
blake2 = "0.10"
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["ristretto", "secp256k1"] }
embedwards25519 = { path = "../../crypto/evrf/embedwards25519" }
secq256k1 = { path = "../../crypto/evrf/secq256k1" }
schnorrkel = "0.11"
dkg = { path = "../../crypto/dkg", default-features = false, features = ["tests"] }

View File

@@ -18,6 +18,8 @@ use ciphersuite::{
group::{ff::PrimeField, GroupEncoding},
Ciphersuite, Ristretto,
};
use embedwards25519::Embedwards25519;
use secq256k1::Secq256k1;
use serai_client::primitives::NetworkId;
@@ -118,6 +120,8 @@ pub struct Processor {
queue_for_sending: MessageQueue,
abort_handle: Option<Arc<AbortHandle>>,
evrf_public_keys: ([u8; 32], Vec<u8>),
substrate_key: Arc<AsyncMutex<Option<Zeroizing<<Ristretto as Ciphersuite>::F>>>>,
}
@@ -131,7 +135,7 @@ impl Drop for Processor {
impl Processor {
pub async fn new(
raw_i: u8,
name: &'static str,
network: NetworkId,
ops: &DockerOperations,
handles: Handles,
@@ -168,6 +172,7 @@ impl Processor {
let (msg_send, msg_recv) = mpsc::unbounded_channel();
use serai_client::primitives::insecure_arbitrary_key_from_name;
let substrate_key = Arc::new(AsyncMutex::new(None));
let mut res = Processor {
network,
@@ -183,6 +188,28 @@ impl Processor {
msgs: msg_recv,
abort_handle: None,
evrf_public_keys: (
(Embedwards25519::generator() * insecure_arbitrary_key_from_name::<Embedwards25519>(name))
.to_bytes(),
match network {
NetworkId::Serai => panic!("mock processor for the serai network"),
NetworkId::Bitcoin | NetworkId::Ethereum => {
let key = (Secq256k1::generator() *
insecure_arbitrary_key_from_name::<Secq256k1>(name))
.to_bytes();
let key: &[u8] = key.as_ref();
key.to_vec()
}
NetworkId::Monero => {
let key = (Embedwards25519::generator() *
insecure_arbitrary_key_from_name::<Embedwards25519>(name))
.to_bytes();
let key: &[u8] = key.as_ref();
key.to_vec()
}
},
),
substrate_key: substrate_key.clone(),
};
@@ -256,10 +283,12 @@ impl Processor {
if current_cosign.is_none() || (current_cosign.as_ref().unwrap().block != block) {
*current_cosign = Some(new_cosign);
}
let mut preprocess = [0; 64];
preprocess[.. name.len()].copy_from_slice(name.as_ref());
send_message(
messages::coordinator::ProcessorMessage::CosignPreprocess {
id: id.clone(),
preprocesses: vec![[raw_i; 64]],
preprocesses: vec![preprocess],
}
.into(),
)
@@ -270,12 +299,11 @@ impl Processor {
) => {
// TODO: Assert the ID matches CURRENT_COSIGN
// TODO: Verify the received preprocesses
let mut share = [0; 32];
share[.. name.len()].copy_from_slice(name.as_bytes());
send_message(
messages::coordinator::ProcessorMessage::SubstrateShare {
id,
shares: vec![[raw_i; 32]],
}
.into(),
messages::coordinator::ProcessorMessage::SubstrateShare { id, shares: vec![share] }
.into(),
)
.await;
}
@@ -327,6 +355,10 @@ impl Processor {
res
}
pub fn evrf_public_keys(&self) -> ([u8; 32], Vec<u8>) {
self.evrf_public_keys.clone()
}
pub async fn serai(&self) -> Serai {
Serai::new(self.serai_rpc.clone()).await.unwrap()
}

View File

@@ -1,7 +1,4 @@
use std::{
time::{Duration, SystemTime},
collections::HashMap,
};
use std::time::{Duration, SystemTime};
use zeroize::Zeroizing;
use rand_core::OsRng;
@@ -10,14 +7,14 @@ use ciphersuite::{
group::{ff::Field, GroupEncoding},
Ciphersuite, Ristretto, Secp256k1,
};
use dkg::ThresholdParams;
use dkg::Participant;
use serai_client::{
primitives::NetworkId,
Public,
validator_sets::primitives::{Session, ValidatorSet, KeyPair},
};
use messages::{key_gen::KeyGenId, CoordinatorMessage};
use messages::CoordinatorMessage;
use crate::tests::*;
@@ -29,16 +26,27 @@ pub async fn key_gen<C: Ciphersuite>(
let mut participant_is = vec![];
let set = ValidatorSet { session, network: NetworkId::Bitcoin };
let id = KeyGenId { session: set.session, attempt: 0 };
// This is distinct from the result of evrf_public_keys for each processor, as there'll have some
// ordering algorithm on-chain which won't match our ordering
let mut evrf_public_keys_as_on_chain = None;
for (i, processor) in processors.iter_mut().enumerate() {
let msg = processor.recv_message().await;
match &msg {
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::GenerateKey {
params,
evrf_public_keys,
..
}) => {
participant_is.push(params.i());
if evrf_public_keys_as_on_chain.is_none() {
evrf_public_keys_as_on_chain = Some(evrf_public_keys.clone());
}
assert_eq!(evrf_public_keys_as_on_chain.as_ref().unwrap(), evrf_public_keys);
let i = evrf_public_keys
.iter()
.position(|public_keys| *public_keys == processor.evrf_public_keys())
.unwrap();
let i = Participant::new(1 + u16::try_from(i).unwrap()).unwrap();
participant_is.push(i);
}
_ => panic!("unexpected message: {msg:?}"),
}
@@ -46,63 +54,40 @@ pub async fn key_gen<C: Ciphersuite>(
assert_eq!(
msg,
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::GenerateKey {
id,
params: ThresholdParams::new(
u16::try_from(((coordinators * 2) / 3) + 1).unwrap(),
u16::try_from(coordinators).unwrap(),
participant_is[i],
)
.unwrap(),
shares: 1,
session,
threshold: u16::try_from(((coordinators * 2) / 3) + 1).unwrap(),
evrf_public_keys: evrf_public_keys_as_on_chain.clone().unwrap(),
})
);
processor
.send_message(messages::key_gen::ProcessorMessage::Commitments {
id,
commitments: vec![vec![u8::try_from(u16::from(participant_is[i])).unwrap()]],
.send_message(messages::key_gen::ProcessorMessage::Participation {
session,
participation: vec![u8::try_from(u16::from(participant_is[i])).unwrap()],
})
.await;
// Sleep so this participation gets included, before moving to the next participation
wait_for_tributary().await;
wait_for_tributary().await;
}
wait_for_tributary().await;
for (i, processor) in processors.iter_mut().enumerate() {
let mut commitments = (0 .. u8::try_from(coordinators).unwrap())
.map(|l| {
(
participant_is[usize::from(l)],
vec![u8::try_from(u16::from(participant_is[usize::from(l)])).unwrap()],
)
})
.collect::<HashMap<_, _>>();
commitments.remove(&participant_is[i]);
assert_eq!(
processor.recv_message().await,
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::Commitments {
id,
commitments,
})
);
// Recipient it's for -> (Sender i, Recipient i)
let mut shares = (0 .. u8::try_from(coordinators).unwrap())
.map(|l| {
(
participant_is[usize::from(l)],
vec![
u8::try_from(u16::from(participant_is[i])).unwrap(),
u8::try_from(u16::from(participant_is[usize::from(l)])).unwrap(),
],
)
})
.collect::<HashMap<_, _>>();
shares.remove(&participant_is[i]);
processor
.send_message(messages::key_gen::ProcessorMessage::Shares { id, shares: vec![shares] })
.await;
for processor in processors.iter_mut() {
#[allow(clippy::needless_range_loop)] // This wouldn't improve readability/clarity
for i in 0 .. coordinators {
assert_eq!(
processor.recv_message().await,
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::Participation {
session,
participant: participant_is[i],
participation: vec![u8::try_from(u16::from(participant_is[i])).unwrap()],
})
);
}
}
// Now that we've received all participations, publish the key pair
let substrate_priv_key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
let substrate_key = (<Ristretto as Ciphersuite>::generator() * *substrate_priv_key).to_bytes();
@@ -112,40 +97,24 @@ pub async fn key_gen<C: Ciphersuite>(
let serai = processors[0].serai().await;
let mut last_serai_block = serai.latest_finalized_block().await.unwrap().number();
wait_for_tributary().await;
for (i, processor) in processors.iter_mut().enumerate() {
let i = participant_is[i];
assert_eq!(
processor.recv_message().await,
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::Shares {
id,
shares: {
let mut shares = (0 .. u8::try_from(coordinators).unwrap())
.map(|l| {
(
participant_is[usize::from(l)],
vec![
u8::try_from(u16::from(participant_is[usize::from(l)])).unwrap(),
u8::try_from(u16::from(i)).unwrap(),
],
)
})
.collect::<HashMap<_, _>>();
shares.remove(&i);
vec![shares]
},
})
);
for processor in processors.iter_mut() {
processor
.send_message(messages::key_gen::ProcessorMessage::GeneratedKeyPair {
id,
session,
substrate_key,
network_key: network_key.clone(),
})
.await;
}
// Sleeps for longer since we need to wait for a Substrate block as well
// Wait for the Nonces TXs to go around
wait_for_tributary().await;
// Wait for the Share TXs to go around
wait_for_tributary().await;
// And now we're waiting ro the TX to be published onto Serai
// We need to wait for a finalized Substrate block as well, so this waites for up to 20 blocks
'outer: for _ in 0 .. 20 {
tokio::time::sleep(Duration::from_secs(6)).await;
if std::env::var("GITHUB_CI") == Ok("true".to_string()) {

View File

@@ -41,6 +41,18 @@ impl<F: Send + Future, TB: 'static + Send + Sync + Fn(Vec<Processor>) -> F> Test
}
}
fn name(i: usize) -> &'static str {
match i {
0 => "Alice",
1 => "Bob",
2 => "Charlie",
3 => "Dave",
4 => "Eve",
5 => "Ferdie",
_ => panic!("needed a 7th name for a serai node"),
}
}
pub(crate) async fn new_test(test_body: impl TestBody, fast_epoch: bool) {
let mut unique_id_lock = UNIQUE_ID.get_or_init(|| Mutex::new(0)).lock().await;
@@ -50,15 +62,7 @@ pub(crate) async fn new_test(test_body: impl TestBody, fast_epoch: bool) {
// Spawn one extra coordinator which isn't in-set
#[allow(clippy::range_plus_one)]
for i in 0 .. (COORDINATORS + 1) {
let name = match i {
0 => "Alice",
1 => "Bob",
2 => "Charlie",
3 => "Dave",
4 => "Eve",
5 => "Ferdie",
_ => panic!("needed a 7th name for a serai node"),
};
let name = name(i);
let serai_composition = serai_composition(name, fast_epoch);
let (processor_key, message_queue_keys, message_queue_composition) =
@@ -196,14 +200,7 @@ pub(crate) async fn new_test(test_body: impl TestBody, fast_epoch: bool) {
let mut processors: Vec<Processor> = vec![];
for (i, (handles, key)) in coordinators.iter().enumerate() {
processors.push(
Processor::new(
i.try_into().unwrap(),
NetworkId::Bitcoin,
&outer_ops,
handles.clone(),
*key,
)
.await,
Processor::new(name(i), NetworkId::Bitcoin, &outer_ops, handles.clone(), *key).await,
);
}

View File

@@ -57,14 +57,24 @@ pub(crate) async fn new_test(test_body: impl TestBody) {
let (coord_key, message_queue_keys, message_queue_composition) = message_queue_instance();
let (bitcoin_composition, bitcoin_port) = network_instance(NetworkId::Bitcoin);
let mut bitcoin_processor_composition =
processor_instance(NetworkId::Bitcoin, bitcoin_port, message_queue_keys[&NetworkId::Bitcoin]);
let mut bitcoin_processor_composition = processor_instance(
name,
NetworkId::Bitcoin,
bitcoin_port,
message_queue_keys[&NetworkId::Bitcoin],
)
.0;
assert_eq!(bitcoin_processor_composition.len(), 1);
let bitcoin_processor_composition = bitcoin_processor_composition.swap_remove(0);
let (monero_composition, monero_port) = network_instance(NetworkId::Monero);
let mut monero_processor_composition =
processor_instance(NetworkId::Monero, monero_port, message_queue_keys[&NetworkId::Monero]);
let mut monero_processor_composition = processor_instance(
name,
NetworkId::Monero,
monero_port,
message_queue_keys[&NetworkId::Monero],
)
.0;
assert_eq!(monero_processor_composition.len(), 1);
let monero_processor_composition = monero_processor_composition.swap_remove(0);

View File

@@ -3,18 +3,14 @@
use std::sync::{OnceLock, Mutex};
use zeroize::Zeroizing;
use rand_core::OsRng;
use ciphersuite::{
group::{
ff::{Field, PrimeField},
GroupEncoding,
},
group::{ff::PrimeField, GroupEncoding},
Ciphersuite, Secp256k1, Ed25519, Ristretto,
};
use dkg::evrf::*;
use serai_client::primitives::NetworkId;
use serai_client::primitives::{NetworkId, insecure_arbitrary_key_from_name};
use messages::{ProcessorMessage, CoordinatorMessage};
use serai_message_queue::{Service, Metadata, client::MessageQueue};
@@ -39,12 +35,13 @@ pub struct EvrfPublicKeys {
}
pub fn processor_instance(
name: &str,
network: NetworkId,
port: u32,
message_queue_key: <Ristretto as Ciphersuite>::F,
) -> (Vec<TestBodySpecification>, EvrfPublicKeys) {
let substrate_evrf_key =
<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng);
insecure_arbitrary_key_from_name::<<Ristretto as EvrfCurve>::EmbeddedCurve>(name);
let substrate_evrf_pub_key =
(<Ristretto as EvrfCurve>::EmbeddedCurve::generator() * substrate_evrf_key).to_bytes();
let substrate_evrf_key = substrate_evrf_key.to_repr();
@@ -53,13 +50,14 @@ pub fn processor_instance(
NetworkId::Serai => panic!("starting a processor for Serai"),
NetworkId::Bitcoin | NetworkId::Ethereum => {
let evrf_key =
<<Secp256k1 as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng);
insecure_arbitrary_key_from_name::<<Secp256k1 as EvrfCurve>::EmbeddedCurve>(name);
let pub_key =
(<Secp256k1 as EvrfCurve>::EmbeddedCurve::generator() * evrf_key).to_bytes().to_vec();
(evrf_key.to_repr(), pub_key)
}
NetworkId::Monero => {
let evrf_key = <<Ed25519 as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng);
let evrf_key =
insecure_arbitrary_key_from_name::<<Ed25519 as EvrfCurve>::EmbeddedCurve>(name);
let pub_key =
(<Ed25519 as EvrfCurve>::EmbeddedCurve::generator() * evrf_key).to_bytes().to_vec();
(evrf_key.to_repr(), pub_key)
@@ -120,6 +118,7 @@ pub struct ProcessorKeys {
pub type Handles = (String, String, String, String);
pub fn processor_stack(
name: &str,
network: NetworkId,
network_hostname_override: Option<String>,
) -> (Handles, ProcessorKeys, Vec<TestBodySpecification>) {
@@ -129,7 +128,7 @@ pub fn processor_stack(
serai_message_queue_tests::instance();
let (mut processor_compositions, evrf_keys) =
processor_instance(network, network_rpc_port, message_queue_keys[&network]);
processor_instance(name, network, network_rpc_port, message_queue_keys[&network]);
// Give every item in this stack a unique ID
// Uses a Mutex as we can't generate a 8-byte random ID without hitting hostname length limits

View File

@@ -3,7 +3,7 @@ use std::{
time::{SystemTime, Duration},
};
use rand_core::RngCore;
use rand_core::{RngCore, OsRng};
use dkg::{Participant, tests::clone_without};

View File

@@ -19,8 +19,11 @@ 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, keys, compositions) = processor_stack(network, eth_handle.clone());
for i in 0 .. COORDINATORS {
// Uses the counter `i` as this has no relation to any other system, and while Substrate has
// hard-coded names for itself, these tests down't spawn any Substrate node
let (handles, keys, compositions) =
processor_stack(&i.to_string(), 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 {

View File

@@ -3,7 +3,7 @@ use std::{
time::{SystemTime, Duration},
};
use rand_core::RngCore;
use rand_core::{RngCore, OsRng};
use dkg::{Participant, tests::clone_without};