mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
Compare commits
6 Commits
2ae2883106
...
8c50a31633
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c50a31633 | ||
|
|
d943e037e5 | ||
|
|
3042697243 | ||
|
|
8de696f169 | ||
|
|
b8912e4b7b | ||
|
|
89fc88b283 |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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 => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)>>,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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()),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{
|
||||
time::{SystemTime, Duration},
|
||||
};
|
||||
|
||||
use rand_core::RngCore;
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use dkg::{Participant, tests::clone_without};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{
|
||||
time::{SystemTime, Duration},
|
||||
};
|
||||
|
||||
use rand_core::RngCore;
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use dkg::{Participant, tests::clone_without};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user