From 2f29c91d3068c411d74859b33af2f0284dd69118 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 16 Aug 2024 17:01:45 -0400 Subject: [PATCH] Smash key-gen out of processor Resolves some bad assumptions made regarding keys being unique or not. --- Cargo.lock | 21 +- processor/Cargo.toml | 1 - processor/key-gen/Cargo.toml | 80 ++----- processor/key-gen/README.md | 8 + processor/key-gen/src/db.rs | 144 ++++++++++++ processor/key-gen/src/generators.rs | 38 +++ processor/key-gen/src/lib.rs | 351 +++++++++------------------- processor/src/lib.rs | 2 +- processor/src/main.rs | 2 +- 9 files changed, 335 insertions(+), 312 deletions(-) create mode 100644 processor/key-gen/README.md create mode 100644 processor/key-gen/src/db.rs create mode 100644 processor/key-gen/src/generators.rs diff --git a/Cargo.lock b/Cargo.lock index ff21fe66..62952da0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8564,7 +8564,6 @@ version = "0.1.0" dependencies = [ "async-trait", "bitcoin-serai", - "blake2", "borsh", "ciphersuite", "const-hex", @@ -8600,6 +8599,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "serai-processor-key-gen" +version = "0.1.0" +dependencies = [ + "blake2", + "borsh", + "ciphersuite", + "dkg", + "ec-divisors", + "flexible-transcript", + "log", + "parity-scale-codec", + "rand_chacha", + "rand_core", + "serai-db", + "serai-processor-messages", + "serai-validator-sets-primitives", + "zeroize", +] + [[package]] name = "serai-processor-messages" version = "0.1.0" diff --git a/processor/Cargo.toml b/processor/Cargo.toml index fa2f643c..2d386f2d 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -36,7 +36,6 @@ serde_json = { version = "1", default-features = false, features = ["std"] } # Cryptography ciphersuite = { path = "../crypto/ciphersuite", default-features = false, features = ["std", "ristretto"] } -blake2 = { version = "0.10", default-features = false, features = ["std"] } transcript = { package = "flexible-transcript", path = "../crypto/transcript", default-features = false, features = ["std"] } ec-divisors = { package = "ec-divisors", path = "../crypto/evrf/divisors", default-features = false } dkg = { package = "dkg", path = "../crypto/dkg", default-features = false, features = ["std", "evrf-ristretto"] } diff --git a/processor/key-gen/Cargo.toml b/processor/key-gen/Cargo.toml index ed6e7383..f1f00564 100644 --- a/processor/key-gen/Cargo.toml +++ b/processor/key-gen/Cargo.toml @@ -13,85 +13,35 @@ publish = false all-features = true rustdoc-args = ["--cfg", "docsrs"] +[package.metadata.cargo-machete] +ignored = ["scale"] + [lints] workspace = true [dependencies] # Macros -async-trait = { version = "0.1", default-features = false } zeroize = { version = "1", default-features = false, features = ["std"] } -thiserror = { version = "1", default-features = false } # Libs rand_core = { version = "0.6", default-features = false, features = ["std", "getrandom"] } rand_chacha = { version = "0.3", default-features = false, features = ["std"] } +# Cryptography +blake2 = { version = "0.10", default-features = false, features = ["std"] } +transcript = { package = "flexible-transcript", path = "../../crypto/transcript", default-features = false, features = ["std"] } +ec-divisors = { package = "ec-divisors", path = "../../crypto/evrf/divisors", default-features = false } +ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "ristretto"] } +dkg = { package = "dkg", path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-ristretto"] } + +# Substrate +serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] } + # Encoders -const-hex = { version = "1", default-features = false } -hex = { version = "0.4", default-features = false, features = ["std"] } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] } borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] } -serde_json = { version = "1", default-features = false, features = ["std"] } - -# Cryptography -ciphersuite = { path = "../crypto/ciphersuite", default-features = false, features = ["std", "ristretto"] } - -blake2 = { version = "0.10", default-features = false, features = ["std"] } -transcript = { package = "flexible-transcript", path = "../crypto/transcript", default-features = false, features = ["std"] } -ec-divisors = { package = "ec-divisors", path = "../crypto/evrf/divisors", default-features = false } -dkg = { package = "dkg", path = "../crypto/dkg", default-features = false, features = ["std", "evrf-ristretto"] } -frost = { package = "modular-frost", path = "../crypto/frost", default-features = false, features = ["ristretto"] } -frost-schnorrkel = { path = "../crypto/schnorrkel", default-features = false } - -# Bitcoin/Ethereum -k256 = { version = "^0.13.1", default-features = false, features = ["std"], optional = true } - -# Bitcoin -secp256k1 = { version = "0.29", default-features = false, features = ["std", "global-context", "rand-std"], optional = true } -bitcoin-serai = { path = "../networks/bitcoin", default-features = false, features = ["std"], optional = true } - -# Ethereum -ethereum-serai = { path = "../networks/ethereum", default-features = false, optional = true } - -# Monero -dalek-ff-group = { path = "../crypto/dalek-ff-group", default-features = false, features = ["std"], optional = true } -monero-simple-request-rpc = { path = "../networks/monero/rpc/simple-request", default-features = false, optional = true } -monero-wallet = { path = "../networks/monero/wallet", default-features = false, features = ["std", "multisig", "compile-time-generators"], optional = true } # Application log = { version = "0.4", default-features = false, features = ["std"] } -env_logger = { version = "0.10", default-features = false, features = ["humantime"], optional = true } -tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "sync", "time", "macros"] } - -zalloc = { path = "../common/zalloc" } -serai-db = { path = "../common/db" } -serai-env = { path = "../common/env", optional = true } -# TODO: Replace with direct usage of primitives -serai-client = { path = "../substrate/client", default-features = false, features = ["serai"] } - -messages = { package = "serai-processor-messages", path = "./messages" } - -message-queue = { package = "serai-message-queue", path = "../message-queue", optional = true } - -[dev-dependencies] -frost = { package = "modular-frost", path = "../crypto/frost", features = ["tests"] } - -sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false, features = ["std"] } - -ethereum-serai = { path = "../networks/ethereum", default-features = false, features = ["tests"] } - -dockertest = "0.4" -serai-docker-tests = { path = "../tests/docker" } - -[features] -secp256k1 = ["k256", "dkg/evrf-secp256k1", "frost/secp256k1"] -bitcoin = ["dep:secp256k1", "secp256k1", "bitcoin-serai", "serai-client/bitcoin"] - -ethereum = ["secp256k1", "ethereum-serai/tests"] - -ed25519 = ["dalek-ff-group", "dkg/evrf-ed25519", "frost/ed25519"] -monero = ["ed25519", "monero-simple-request-rpc", "monero-wallet", "serai-client/monero"] - -binaries = ["env_logger", "serai-env", "message-queue"] -parity-db = ["serai-db/parity-db"] -rocksdb = ["serai-db/rocksdb"] +serai-db = { path = "../../common/db" } +messages = { package = "serai-processor-messages", path = "../messages" } diff --git a/processor/key-gen/README.md b/processor/key-gen/README.md new file mode 100644 index 00000000..c28357ba --- /dev/null +++ b/processor/key-gen/README.md @@ -0,0 +1,8 @@ +# Key Generation + +This library implements the Distributed Key Generation (DKG) for the Serai +protocol. Two invocations of the eVRF-based DKG are performed, one for Ristretto +(to have a key to oraclize values onto the Serai blockchain with) and one for +the external network's curve. + +This library is interacted with via the `serai-processor-messages::key_gen` API. diff --git a/processor/key-gen/src/db.rs b/processor/key-gen/src/db.rs new file mode 100644 index 00000000..d597cb7e --- /dev/null +++ b/processor/key-gen/src/db.rs @@ -0,0 +1,144 @@ +use core::marker::PhantomData; +use std::collections::HashMap; + +use zeroize::Zeroizing; + +use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto}; +use dkg::{Participant, ThresholdCore, ThresholdKeys, evrf::EvrfCurve}; + +use serai_validator_sets_primitives::Session; + +use borsh::{BorshSerialize, BorshDeserialize}; +use serai_db::{Get, DbTxn, create_db}; + +use crate::KeyGenParams; + +pub(crate) struct Params { + pub(crate) t: u16, + pub(crate) n: u16, + pub(crate) substrate_evrf_public_keys: + Vec<<::EmbeddedCurve as Ciphersuite>::G>, + pub(crate) network_evrf_public_keys: + Vec<<::EmbeddedCurve as Ciphersuite>::G>, +} + +#[derive(BorshSerialize, BorshDeserialize)] +struct RawParams { + t: u16, + substrate_evrf_public_keys: Vec<[u8; 32]>, + network_evrf_public_keys: Vec>, +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub(crate) struct Participations { + pub(crate) substrate_participations: HashMap>, + pub(crate) network_participations: HashMap>, +} + +create_db!( + KeyGenDb { + ParamsDb: (session: &Session) -> RawParams, + ParticipationsDb: (session: &Session) -> Participations, + KeySharesDb: (session: &Session) -> Vec, + } +); + +pub(crate) struct KeyGenDb(PhantomData

); +impl KeyGenDb

{ + pub(crate) fn set_params(txn: &mut impl DbTxn, session: Session, params: Params

) { + assert_eq!(params.substrate_evrf_public_keys.len(), params.network_evrf_public_keys.len()); + + ParamsDb::set( + txn, + &session, + &RawParams { + t: params.t, + substrate_evrf_public_keys: params + .substrate_evrf_public_keys + .into_iter() + .map(|key| key.to_bytes()) + .collect(), + network_evrf_public_keys: params + .network_evrf_public_keys + .into_iter() + .map(|key| key.to_bytes().as_ref().to_vec()) + .collect(), + }, + ) + } + + pub(crate) fn params(getter: &impl Get, session: Session) -> Option> { + ParamsDb::get(getter, &session).map(|params| Params { + t: params.t, + n: params + .network_evrf_public_keys + .len() + .try_into() + .expect("amount of keys exceeded the amount allowed during a DKG"), + substrate_evrf_public_keys: params + .substrate_evrf_public_keys + .into_iter() + .map(|key| { + <::EmbeddedCurve as Ciphersuite>::read_G(&mut key.as_slice()) + .unwrap() + }) + .collect(), + network_evrf_public_keys: params + .network_evrf_public_keys + .into_iter() + .map(|key| { + <::EmbeddedCurve as Ciphersuite>::read_G::<&[u8]>( + &mut key.as_ref(), + ) + .unwrap() + }) + .collect(), + }) + } + + pub(crate) fn set_participations( + txn: &mut impl DbTxn, + session: Session, + participations: &Participations, + ) { + ParticipationsDb::set(txn, &session, participations) + } + pub(crate) fn participations(getter: &impl Get, session: Session) -> Option { + ParticipationsDb::get(getter, &session) + } + + pub(crate) fn set_key_shares( + txn: &mut impl DbTxn, + session: Session, + substrate_keys: &[ThresholdKeys], + network_keys: &[ThresholdKeys], + ) { + assert_eq!(substrate_keys.len(), network_keys.len()); + + let mut keys = Zeroizing::new(vec![]); + for (substrate_keys, network_keys) in substrate_keys.iter().zip(network_keys) { + keys.extend(substrate_keys.serialize().as_slice()); + keys.extend(network_keys.serialize().as_slice()); + } + KeySharesDb::set(txn, &session, &keys); + } + + #[allow(clippy::type_complexity)] + pub(crate) fn key_shares( + getter: &impl Get, + session: Session, + ) -> Option<(Vec>, Vec>)> { + let keys = KeySharesDb::get(getter, &session)?; + let mut keys: &[u8] = keys.as_ref(); + + let mut substrate_keys = vec![]; + let mut network_keys = vec![]; + while !keys.is_empty() { + substrate_keys.push(ThresholdKeys::new(ThresholdCore::read(&mut keys).unwrap())); + let mut these_network_keys = ThresholdKeys::new(ThresholdCore::read(&mut keys).unwrap()); + P::tweak_keys(&mut these_network_keys); + network_keys.push(these_network_keys); + } + Some((substrate_keys, network_keys)) + } +} diff --git a/processor/key-gen/src/generators.rs b/processor/key-gen/src/generators.rs new file mode 100644 index 00000000..3570ca6e --- /dev/null +++ b/processor/key-gen/src/generators.rs @@ -0,0 +1,38 @@ +use core::any::{TypeId, Any}; +use std::{ + sync::{LazyLock, Mutex}, + collections::HashMap, +}; + +use dkg::evrf::*; + +use serai_validator_sets_primitives::MAX_KEY_SHARES_PER_SET; + +/// A cache of the generators used by the eVRF DKG. +/// +/// This performs a lookup of the Ciphersuite to its generators. Since the Ciphersuite is a +/// generic, this takes advantage of `Any`. This static is isolated in a module to ensure +/// correctness can be evaluated solely by reviewing these few lines of code. +/// +/// This is arguably over-engineered as of right now, as we only need generators for Ristretto +/// and N::Curve. By having this HashMap, we enable de-duplication of the Ristretto == N::Curve +/// case, and we automatically support the n-curve case (rather than hard-coding to the 2-curve +/// case). +static GENERATORS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + +pub(crate) fn generators() -> &'static EvrfGenerators { + GENERATORS + .lock() + .unwrap() + .entry(TypeId::of::()) + .or_insert_with(|| { + // If we haven't prior needed generators for this Ciphersuite, generate new ones + Box::leak(Box::new(EvrfGenerators::::new( + ((MAX_KEY_SHARES_PER_SET * 2 / 3) + 1).try_into().unwrap(), + MAX_KEY_SHARES_PER_SET.try_into().unwrap(), + ))) + }) + .downcast_ref() + .unwrap() +} diff --git a/processor/key-gen/src/lib.rs b/processor/key-gen/src/lib.rs index a059c350..8d4e911f 100644 --- a/processor/key-gen/src/lib.rs +++ b/processor/key-gen/src/lib.rs @@ -1,7 +1,8 @@ -use std::{ - io, - collections::{HashSet, HashMap}, -}; +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![deny(missing_docs)] + +use std::{io, collections::HashMap}; use zeroize::Zeroizing; @@ -14,156 +15,41 @@ use ciphersuite::{ group::{Group, GroupEncoding}, Ciphersuite, Ristretto, }; -use dkg::{Participant, ThresholdCore, ThresholdKeys, evrf::*}; +use dkg::{Participant, ThresholdKeys, evrf::*}; use log::info; -use serai_client::validator_sets::primitives::{Session, KeyPair}; +use serai_validator_sets_primitives::Session; use messages::key_gen::*; -use crate::{Get, DbTxn, Db, create_db, networks::Network}; +use serai_db::{DbTxn, Db}; -mod generators { - use core::any::{TypeId, Any}; - use std::{ - sync::{LazyLock, Mutex}, - collections::HashMap, - }; - - use frost::dkg::evrf::*; - - use serai_client::validator_sets::primitives::MAX_KEY_SHARES_PER_SET; - - /// A cache of the generators used by the eVRF DKG. - /// - /// This performs a lookup of the Ciphersuite to its generators. Since the Ciphersuite is a - /// generic, this takes advantage of `Any`. This static is isolated in a module to ensure - /// correctness can be evaluated solely by reviewing these few lines of code. - /// - /// This is arguably over-engineered as of right now, as we only need generators for Ristretto - /// and N::Curve. By having this HashMap, we enable de-duplication of the Ristretto == N::Curve - /// case, and we automatically support the n-curve case (rather than hard-coding to the 2-curve - /// case). - static GENERATORS: LazyLock>> = - LazyLock::new(|| Mutex::new(HashMap::new())); - - pub(crate) fn generators() -> &'static EvrfGenerators { - GENERATORS - .lock() - .unwrap() - .entry(TypeId::of::()) - .or_insert_with(|| { - // If we haven't prior needed generators for this Ciphersuite, generate new ones - Box::leak(Box::new(EvrfGenerators::::new( - ((MAX_KEY_SHARES_PER_SET * 2 / 3) + 1).try_into().unwrap(), - MAX_KEY_SHARES_PER_SET.try_into().unwrap(), - ))) - }) - .downcast_ref() - .unwrap() - } -} +mod generators; use generators::generators; -#[derive(Debug)] -pub struct KeyConfirmed { - pub substrate_keys: Vec>, - pub network_keys: Vec>, -} +mod db; +use db::{Params, Participations, KeyGenDb}; -create_db!( - KeyGenDb { - ParamsDb: (session: &Session) -> (u16, Vec<[u8; 32]>, Vec>), - ParticipationDb: (session: &Session) -> ( - HashMap>, - HashMap>, - ), - // GeneratedKeysDb, KeysDb use `()` for their value as we manually serialize their values - // TODO: Don't do that - GeneratedKeysDb: (session: &Session) -> (), - // These do assume a key is only used once across sets, which holds true if the threshold is - // honest - // TODO: Remove this assumption - KeysDb: (network_key: &[u8]) -> (), - SessionDb: (network_key: &[u8]) -> Session, - NetworkKeyDb: (session: Session) -> Vec, - } -); +/// Parameters for a key generation. +pub trait KeyGenParams { + /// The ID for this instantiation. + const ID: &'static str; -impl GeneratedKeysDb { - #[allow(clippy::type_complexity)] - fn read_keys( - getter: &impl Get, - key: &[u8], - ) -> Option<(Vec, (Vec>, Vec>))> { - let keys_vec = getter.get(key)?; - let mut keys_ref: &[u8] = keys_vec.as_ref(); + /// The curve used for the external network. + type ExternalNetworkCurve: EvrfCurve< + EmbeddedCurve: Ciphersuite< + G: ec_divisors::DivisorCurve::F>, + >, + >; - let mut substrate_keys = vec![]; - let mut network_keys = vec![]; - while !keys_ref.is_empty() { - substrate_keys.push(ThresholdKeys::new(ThresholdCore::read(&mut keys_ref).unwrap())); - let mut these_network_keys = ThresholdKeys::new(ThresholdCore::read(&mut keys_ref).unwrap()); - N::tweak_keys(&mut these_network_keys); - network_keys.push(these_network_keys); - } - Some((keys_vec, (substrate_keys, network_keys))) - } + /// Tweaks keys as necessary/beneficial. + fn tweak_keys(keys: &mut ThresholdKeys); - fn save_keys( - txn: &mut impl DbTxn, - session: &Session, - substrate_keys: &[ThresholdKeys], - network_keys: &[ThresholdKeys], - ) { - let mut keys = Zeroizing::new(vec![]); - for (substrate_keys, network_keys) in substrate_keys.iter().zip(network_keys) { - keys.extend(substrate_keys.serialize().as_slice()); - keys.extend(network_keys.serialize().as_slice()); - } - txn.put(Self::key(session), keys); - } -} - -impl KeysDb { - fn confirm_keys( - txn: &mut impl DbTxn, - session: Session, - key_pair: &KeyPair, - ) -> (Vec>, Vec>) { - let (keys_vec, keys) = - GeneratedKeysDb::read_keys::(txn, &GeneratedKeysDb::key(&session)).unwrap(); - assert_eq!(key_pair.0 .0, keys.0[0].group_key().to_bytes()); - assert_eq!( - { - let network_key: &[u8] = key_pair.1.as_ref(); - network_key - }, - keys.1[0].group_key().to_bytes().as_ref(), - ); - txn.put(Self::key(key_pair.1.as_ref()), keys_vec); - NetworkKeyDb::set(txn, session, &key_pair.1.clone().into_inner()); - SessionDb::set(txn, key_pair.1.as_ref(), &session); - keys - } - - #[allow(clippy::type_complexity)] - fn keys( - getter: &impl Get, - network_key: &::G, - ) -> Option<(Session, (Vec>, Vec>))> { - let res = - GeneratedKeysDb::read_keys::(getter, &Self::key(network_key.to_bytes().as_ref()))?.1; - assert_eq!(&res.1[0].group_key(), network_key); - Some((SessionDb::get(getter, network_key.to_bytes().as_ref()).unwrap(), res)) - } - - pub fn substrate_keys_by_session( - getter: &impl Get, - session: Session, - ) -> Option>> { - let network_key = NetworkKeyDb::get(getter, session)?; - Some(GeneratedKeysDb::read_keys::(getter, &Self::key(&network_key))?.1 .0) + /// Encode keys as optimal. + /// + /// A default implementation is provided which calls the traditional `to_bytes`. + fn encode_key(key: ::G) -> Vec { + key.to_bytes().as_ref().to_vec() } } @@ -242,49 +128,44 @@ fn coerce_keys( (keys, faulty) } +/// An instance of the Serai key generation protocol. #[derive(Debug)] -pub struct KeyGen { +pub struct KeyGen { db: D, substrate_evrf_private_key: Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, - network_evrf_private_key: Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, + network_evrf_private_key: + Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, } -impl KeyGen { +impl KeyGen { + /// Create a new key generation instance. #[allow(clippy::new_ret_no_self)] pub fn new( db: D, substrate_evrf_private_key: Zeroizing< <::EmbeddedCurve as Ciphersuite>::F, >, - network_evrf_private_key: Zeroizing<<::EmbeddedCurve as Ciphersuite>::F>, - ) -> KeyGen { + network_evrf_private_key: Zeroizing< + <::EmbeddedCurve as Ciphersuite>::F, + >, + ) -> KeyGen { KeyGen { db, substrate_evrf_private_key, network_evrf_private_key } } - pub fn in_set(&self, session: &Session) -> bool { - // We determine if we're in set using if we have the parameters for a session's key generation - // We only have these if we were told to generate a key for this session - ParamsDb::get(&self.db, session).is_some() - } - + /// Fetch the key shares for a specific session. #[allow(clippy::type_complexity)] - pub fn keys( - &self, - key: &::G, - ) -> Option<(Session, (Vec>, Vec>))> { - // This is safe, despite not having a txn, since it's a static value - // It doesn't change over time/in relation to other operations - KeysDb::keys::(&self.db, key) - } - - pub fn substrate_keys_by_session( + pub fn key_shares( &self, session: Session, - ) -> Option>> { - KeysDb::substrate_keys_by_session::(&self.db, session) + ) -> Option<(Vec>, Vec>)> { + // This is safe, despite not having a txn, since it's a static value + // It doesn't change over time/in relation to other operations + // It is solely set or unset + KeyGenDb::

::key_shares(&self.db, session) } + /// Handle a message from the coordinator. pub fn handle( &mut self, txn: &mut D::Transaction<'_>, @@ -292,10 +173,10 @@ impl KeyGen { ) -> Vec { const SUBSTRATE_KEY_CONTEXT: &[u8] = b"substrate"; const NETWORK_KEY_CONTEXT: &[u8] = b"network"; - fn context(session: Session, key_context: &[u8]) -> [u8; 32] { + fn context(session: Session, key_context: &[u8]) -> [u8; 32] { // TODO2: Also embed the chain ID/genesis block let mut transcript = RecommendedTranscript::new(b"Serai eVRF Key Gen"); - transcript.append_message(b"network", N::ID); + transcript.append_message(b"network", P::ID.as_bytes()); transcript.append_message(b"session", session.0.to_le_bytes()); transcript.append_message(b"key", key_context); (&(&transcript.challenge(b"context"))[.. 32]).try_into().unwrap() @@ -308,64 +189,68 @@ impl KeyGen { // Unzip the vector of eVRF keys let substrate_evrf_public_keys = evrf_public_keys.iter().map(|(key, _)| *key).collect::>(); + let (substrate_evrf_public_keys, mut faulty) = + coerce_keys::(&substrate_evrf_public_keys); + let network_evrf_public_keys = evrf_public_keys.into_iter().map(|(_, key)| key).collect::>(); - - let mut participation = Vec::with_capacity(2048); - let mut faulty = HashSet::new(); + let (network_evrf_public_keys, additional_faulty) = + coerce_keys::(&network_evrf_public_keys); + faulty.extend(additional_faulty); // Participate for both Substrate and the network fn participate( context: [u8; 32], threshold: u16, - evrf_public_keys: &[impl AsRef<[u8]>], + evrf_public_keys: &[::G], evrf_private_key: &Zeroizing<::F>, - faulty: &mut HashSet, output: &mut impl io::Write, ) { - let (coerced_keys, faulty_is) = coerce_keys::(evrf_public_keys); - for faulty_i in faulty_is { - faulty.insert(faulty_i); - } let participation = EvrfDkg::::participate( &mut OsRng, generators(), context, threshold, - &coerced_keys, + evrf_public_keys, evrf_private_key, ); participation.unwrap().write(output).unwrap(); } + + let mut participation = Vec::with_capacity(2048); participate::( - context::(session, SUBSTRATE_KEY_CONTEXT), + context::

(session, SUBSTRATE_KEY_CONTEXT), threshold, &substrate_evrf_public_keys, &self.substrate_evrf_private_key, - &mut faulty, &mut participation, ); - participate::( - context::(session, NETWORK_KEY_CONTEXT), + participate::( + context::

(session, NETWORK_KEY_CONTEXT), threshold, &network_evrf_public_keys, &self.network_evrf_private_key, - &mut faulty, &mut participation, ); // Save the params - ParamsDb::set( + KeyGenDb::

::set_params( txn, - &session, - &(threshold, substrate_evrf_public_keys, network_evrf_public_keys), + session, + Params { + t: threshold, + n: substrate_evrf_public_keys + .len() + .try_into() + .expect("amount of keys exceeded the amount allowed during a DKG"), + substrate_evrf_public_keys, + network_evrf_public_keys, + }, ); // Send back our Participation and all faulty parties - let mut faulty = faulty.into_iter().collect::>(); - faulty.sort(); - let mut res = Vec::with_capacity(faulty.len() + 1); + faulty.sort_unstable(); for faulty in faulty { res.push(ProcessorMessage::Blame { session, participant: faulty }); } @@ -377,13 +262,8 @@ impl KeyGen { CoordinatorMessage::Participation { session, participant, participation } => { info!("received participation from {:?} for {:?}", participant, session); - let (threshold, substrate_evrf_public_keys, network_evrf_public_keys) = - ParamsDb::get(txn, &session).unwrap(); - - let n = substrate_evrf_public_keys - .len() - .try_into() - .expect("performing a key gen with more than u16::MAX participants"); + let Params { t: threshold, n, substrate_evrf_public_keys, network_evrf_public_keys } = + KeyGenDb::

::params(txn, session).unwrap(); // Read these `Participation`s // If they fail basic sanity checks, fail fast @@ -399,7 +279,8 @@ impl KeyGen { return blame; }; let len_at_network_participation_start_pos = participation.len(); - let Ok(network_participation) = Participation::::read(&mut participation, n) + let Ok(network_participation) = + Participation::::read(&mut participation, n) else { return blame; }; @@ -413,16 +294,15 @@ impl KeyGen { // If we've already generated these keys, we don't actually need to save these // participations and continue. We solely have to verify them, as to identify malicious // participants and prevent DoSs, before returning - if txn.get(GeneratedKeysDb::key(&session)).is_some() { + if self.key_shares(session).is_some() { info!("already finished generating a key for {:?}", session); match EvrfDkg::::verify( &mut OsRng, generators(), - context::(session, SUBSTRATE_KEY_CONTEXT), + context::

(session, SUBSTRATE_KEY_CONTEXT), threshold, - // Ignores the list of participants who were faulty, as they were prior blamed - &coerce_keys::(&substrate_evrf_public_keys).0, + &substrate_evrf_public_keys, &HashMap::from([(participant, substrate_participation)]), ) .unwrap() @@ -434,13 +314,12 @@ impl KeyGen { } } - match EvrfDkg::::verify( + match EvrfDkg::::verify( &mut OsRng, generators(), - context::(session, NETWORK_KEY_CONTEXT), + context::

(session, NETWORK_KEY_CONTEXT), threshold, - // Ignores the list of participants who were faulty, as they were prior blamed - &coerce_keys::(&network_evrf_public_keys).0, + &network_evrf_public_keys, &HashMap::from([(participant, network_participation)]), ) .unwrap() @@ -467,17 +346,22 @@ impl KeyGen { // Since these are valid `Participation`s, save them let (mut substrate_participations, mut network_participations) = - ParticipationDb::get(txn, &session) - .unwrap_or((HashMap::with_capacity(1), HashMap::with_capacity(1))); + KeyGenDb::

::participations(txn, session).map_or_else( + || (HashMap::with_capacity(1), HashMap::with_capacity(1)), + |p| (p.substrate_participations, p.network_participations), + ); assert!( substrate_participations.insert(participant, substrate_participation).is_none() && network_participations.insert(participant, network_participation).is_none(), "received participation for someone multiple times" ); - ParticipationDb::set( + KeyGenDb::

::set_participations( txn, - &session, - &(substrate_participations.clone(), network_participations.clone()), + session, + &Participations { + substrate_participations: substrate_participations.clone(), + network_participations: network_participations.clone(), + }, ); // This block is taken from the eVRF DKG itself to evaluate the amount participating @@ -510,12 +394,12 @@ impl KeyGen { } // If we now have the threshold participating, verify their `Participation`s - fn verify_dkg( + fn verify_dkg( txn: &mut impl DbTxn, session: Session, true_if_substrate_false_if_network: bool, threshold: u16, - evrf_public_keys: &[impl AsRef<[u8]>], + evrf_public_keys: &[::G], substrate_participations: &mut HashMap>, network_participations: &mut HashMap>, ) -> Result, Vec> { @@ -542,7 +426,7 @@ impl KeyGen { match EvrfDkg::::verify( &mut OsRng, generators(), - context::( + context::

( session, if true_if_substrate_false_if_network { SUBSTRATE_KEY_CONTEXT @@ -551,8 +435,7 @@ impl KeyGen { }, ), threshold, - // Ignores the list of participants who were faulty, as they were prior blamed - &coerce_keys::(evrf_public_keys).0, + evrf_public_keys, &participations, ) .unwrap() @@ -570,10 +453,13 @@ impl KeyGen { blames.push(ProcessorMessage::Blame { session, participant }); } // Since we removed `Participation`s, write the updated versions to the database - ParticipationDb::set( + KeyGenDb::

::set_participations( txn, - &session, - &(substrate_participations.clone(), network_participations.clone()), + session, + &Participations { + substrate_participations: substrate_participations.clone(), + network_participations: network_participations.clone(), + }, ); Err(blames)? } @@ -586,7 +472,7 @@ impl KeyGen { } } - let substrate_dkg = match verify_dkg::( + let substrate_dkg = match verify_dkg::( txn, session, true, @@ -601,7 +487,7 @@ impl KeyGen { Err(blames) => return blames, }; - let network_dkg = match verify_dkg::( + let network_dkg = match verify_dkg::( txn, session, false, @@ -623,38 +509,17 @@ impl KeyGen { let mut network_keys = network_dkg.keys(&self.network_evrf_private_key); // Tweak the keys for the network for network_keys in &mut network_keys { - N::tweak_keys(network_keys); + P::tweak_keys(network_keys); } - GeneratedKeysDb::save_keys::(txn, &session, &substrate_keys, &network_keys); + KeyGenDb::

::set_key_shares(txn, session, &substrate_keys, &network_keys); // Since no one we verified was invalid, and we had the threshold, yield the new keys vec![ProcessorMessage::GeneratedKeyPair { session, substrate_key: substrate_keys[0].group_key().to_bytes(), - // TODO: This can be made more efficient since tweaked keys may be a subset of keys - network_key: network_keys[0].group_key().to_bytes().as_ref().to_vec(), + network_key: P::encode_key(network_keys[0].group_key()), }] } } } - - // This should only be called if we're participating, hence taking our instance - #[allow(clippy::unused_self)] - pub fn confirm( - &mut self, - txn: &mut D::Transaction<'_>, - session: Session, - key_pair: &KeyPair, - ) -> KeyConfirmed { - info!( - "Confirmed key pair {} {} for {:?}", - hex::encode(key_pair.0), - hex::encode(&key_pair.1), - session, - ); - - let (substrate_keys, network_keys) = KeysDb::confirm_keys::(txn, session, key_pair); - - KeyConfirmed { substrate_keys, network_keys } - } } diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 19f67508..bbff33f6 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -6,7 +6,7 @@ pub use plan::*; mod db; pub(crate) use db::*; -mod key_gen; +use serai_processor_key_gen as key_gen; pub mod networks; pub(crate) mod multisigs; diff --git a/processor/src/main.rs b/processor/src/main.rs index 2d05ad4d..49406aaf 100644 --- a/processor/src/main.rs +++ b/processor/src/main.rs @@ -48,7 +48,7 @@ pub use db::*; mod coordinator; pub use coordinator::*; -mod key_gen; +use serai_processor_key_gen as key_gen; use key_gen::{SessionDb, KeyConfirmed, KeyGen}; mod signer;