mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 12:09:37 +00:00
Smash key-gen out of processor
Resolves some bad assumptions made regarding keys being unique or not.
This commit is contained in:
parent
f3b91bd44f
commit
2f29c91d30
9 changed files with 335 additions and 312 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -8564,7 +8564,6 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bitcoin-serai",
|
"bitcoin-serai",
|
||||||
"blake2",
|
|
||||||
"borsh",
|
"borsh",
|
||||||
"ciphersuite",
|
"ciphersuite",
|
||||||
"const-hex",
|
"const-hex",
|
||||||
|
@ -8600,6 +8599,26 @@ dependencies = [
|
||||||
"zeroize",
|
"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]]
|
[[package]]
|
||||||
name = "serai-processor-messages"
|
name = "serai-processor-messages"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -36,7 +36,6 @@ serde_json = { version = "1", default-features = false, features = ["std"] }
|
||||||
# Cryptography
|
# Cryptography
|
||||||
ciphersuite = { path = "../crypto/ciphersuite", default-features = false, features = ["std", "ristretto"] }
|
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"] }
|
transcript = { package = "flexible-transcript", path = "../crypto/transcript", default-features = false, features = ["std"] }
|
||||||
ec-divisors = { package = "ec-divisors", path = "../crypto/evrf/divisors", default-features = false }
|
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"] }
|
dkg = { package = "dkg", path = "../crypto/dkg", default-features = false, features = ["std", "evrf-ristretto"] }
|
||||||
|
|
|
@ -13,85 +13,35 @@ publish = false
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[package.metadata.cargo-machete]
|
||||||
|
ignored = ["scale"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Macros
|
# Macros
|
||||||
async-trait = { version = "0.1", default-features = false }
|
|
||||||
zeroize = { version = "1", default-features = false, features = ["std"] }
|
zeroize = { version = "1", default-features = false, features = ["std"] }
|
||||||
thiserror = { version = "1", default-features = false }
|
|
||||||
|
|
||||||
# Libs
|
# Libs
|
||||||
rand_core = { version = "0.6", default-features = false, features = ["std", "getrandom"] }
|
rand_core = { version = "0.6", default-features = false, features = ["std", "getrandom"] }
|
||||||
rand_chacha = { version = "0.3", default-features = false, features = ["std"] }
|
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
|
# 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"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
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
|
# Application
|
||||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
env_logger = { version = "0.10", default-features = false, features = ["humantime"], optional = true }
|
serai-db = { path = "../../common/db" }
|
||||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "sync", "time", "macros"] }
|
messages = { package = "serai-processor-messages", path = "../messages" }
|
||||||
|
|
||||||
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"]
|
|
||||||
|
|
8
processor/key-gen/README.md
Normal file
8
processor/key-gen/README.md
Normal file
|
@ -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.
|
144
processor/key-gen/src/db.rs
Normal file
144
processor/key-gen/src/db.rs
Normal file
|
@ -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<P: KeyGenParams> {
|
||||||
|
pub(crate) t: u16,
|
||||||
|
pub(crate) n: u16,
|
||||||
|
pub(crate) substrate_evrf_public_keys:
|
||||||
|
Vec<<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G>,
|
||||||
|
pub(crate) network_evrf_public_keys:
|
||||||
|
Vec<<<P::ExternalNetworkCurve as EvrfCurve>::EmbeddedCurve as Ciphersuite>::G>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)]
|
||||||
|
struct RawParams {
|
||||||
|
t: u16,
|
||||||
|
substrate_evrf_public_keys: Vec<[u8; 32]>,
|
||||||
|
network_evrf_public_keys: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)]
|
||||||
|
pub(crate) struct Participations {
|
||||||
|
pub(crate) substrate_participations: HashMap<Participant, Vec<u8>>,
|
||||||
|
pub(crate) network_participations: HashMap<Participant, Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
create_db!(
|
||||||
|
KeyGenDb {
|
||||||
|
ParamsDb: (session: &Session) -> RawParams,
|
||||||
|
ParticipationsDb: (session: &Session) -> Participations,
|
||||||
|
KeySharesDb: (session: &Session) -> Vec<u8>,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
pub(crate) struct KeyGenDb<P: KeyGenParams>(PhantomData<P>);
|
||||||
|
impl<P: KeyGenParams> KeyGenDb<P> {
|
||||||
|
pub(crate) fn set_params(txn: &mut impl DbTxn, session: Session, params: Params<P>) {
|
||||||
|
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<Params<P>> {
|
||||||
|
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| {
|
||||||
|
<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::read_G(&mut key.as_slice())
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
network_evrf_public_keys: params
|
||||||
|
.network_evrf_public_keys
|
||||||
|
.into_iter()
|
||||||
|
.map(|key| {
|
||||||
|
<<P::ExternalNetworkCurve as EvrfCurve>::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<Participations> {
|
||||||
|
ParticipationsDb::get(getter, &session)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_key_shares(
|
||||||
|
txn: &mut impl DbTxn,
|
||||||
|
session: Session,
|
||||||
|
substrate_keys: &[ThresholdKeys<Ristretto>],
|
||||||
|
network_keys: &[ThresholdKeys<P::ExternalNetworkCurve>],
|
||||||
|
) {
|
||||||
|
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<ThresholdKeys<Ristretto>>, Vec<ThresholdKeys<P::ExternalNetworkCurve>>)> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
38
processor/key-gen/src/generators.rs
Normal file
38
processor/key-gen/src/generators.rs
Normal file
|
@ -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<Mutex<HashMap<TypeId, &'static (dyn Send + Sync + Any)>>> =
|
||||||
|
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
|
pub(crate) fn generators<C: EvrfCurve>() -> &'static EvrfGenerators<C> {
|
||||||
|
GENERATORS
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.entry(TypeId::of::<C>())
|
||||||
|
.or_insert_with(|| {
|
||||||
|
// If we haven't prior needed generators for this Ciphersuite, generate new ones
|
||||||
|
Box::leak(Box::new(EvrfGenerators::<C>::new(
|
||||||
|
((MAX_KEY_SHARES_PER_SET * 2 / 3) + 1).try_into().unwrap(),
|
||||||
|
MAX_KEY_SHARES_PER_SET.try_into().unwrap(),
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
.downcast_ref()
|
||||||
|
.unwrap()
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
use std::{
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
io,
|
#![doc = include_str!("../README.md")]
|
||||||
collections::{HashSet, HashMap},
|
#![deny(missing_docs)]
|
||||||
};
|
|
||||||
|
use std::{io, collections::HashMap};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
|
||||||
|
@ -14,156 +15,41 @@ use ciphersuite::{
|
||||||
group::{Group, GroupEncoding},
|
group::{Group, GroupEncoding},
|
||||||
Ciphersuite, Ristretto,
|
Ciphersuite, Ristretto,
|
||||||
};
|
};
|
||||||
use dkg::{Participant, ThresholdCore, ThresholdKeys, evrf::*};
|
use dkg::{Participant, ThresholdKeys, evrf::*};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use serai_client::validator_sets::primitives::{Session, KeyPair};
|
use serai_validator_sets_primitives::Session;
|
||||||
use messages::key_gen::*;
|
use messages::key_gen::*;
|
||||||
|
|
||||||
use crate::{Get, DbTxn, Db, create_db, networks::Network};
|
use serai_db::{DbTxn, Db};
|
||||||
|
|
||||||
mod generators {
|
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<Mutex<HashMap<TypeId, &'static (dyn Send + Sync + Any)>>> =
|
|
||||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
|
||||||
|
|
||||||
pub(crate) fn generators<C: EvrfCurve>() -> &'static EvrfGenerators<C> {
|
|
||||||
GENERATORS
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.entry(TypeId::of::<C>())
|
|
||||||
.or_insert_with(|| {
|
|
||||||
// If we haven't prior needed generators for this Ciphersuite, generate new ones
|
|
||||||
Box::leak(Box::new(EvrfGenerators::<C>::new(
|
|
||||||
((MAX_KEY_SHARES_PER_SET * 2 / 3) + 1).try_into().unwrap(),
|
|
||||||
MAX_KEY_SHARES_PER_SET.try_into().unwrap(),
|
|
||||||
)))
|
|
||||||
})
|
|
||||||
.downcast_ref()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
use generators::generators;
|
use generators::generators;
|
||||||
|
|
||||||
#[derive(Debug)]
|
mod db;
|
||||||
pub struct KeyConfirmed<C: Ciphersuite> {
|
use db::{Params, Participations, KeyGenDb};
|
||||||
pub substrate_keys: Vec<ThresholdKeys<Ristretto>>,
|
|
||||||
pub network_keys: Vec<ThresholdKeys<C>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
create_db!(
|
/// Parameters for a key generation.
|
||||||
KeyGenDb {
|
pub trait KeyGenParams {
|
||||||
ParamsDb: (session: &Session) -> (u16, Vec<[u8; 32]>, Vec<Vec<u8>>),
|
/// The ID for this instantiation.
|
||||||
ParticipationDb: (session: &Session) -> (
|
const ID: &'static str;
|
||||||
HashMap<Participant, Vec<u8>>,
|
|
||||||
HashMap<Participant, Vec<u8>>,
|
|
||||||
),
|
|
||||||
// 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<u8>,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
impl GeneratedKeysDb {
|
/// The curve used for the external network.
|
||||||
#[allow(clippy::type_complexity)]
|
type ExternalNetworkCurve: EvrfCurve<
|
||||||
fn read_keys<N: Network>(
|
EmbeddedCurve: Ciphersuite<
|
||||||
getter: &impl Get,
|
G: ec_divisors::DivisorCurve<FieldElement = <Self::ExternalNetworkCurve as Ciphersuite>::F>,
|
||||||
key: &[u8],
|
>,
|
||||||
) -> Option<(Vec<u8>, (Vec<ThresholdKeys<Ristretto>>, Vec<ThresholdKeys<N::Curve>>))> {
|
>;
|
||||||
let keys_vec = getter.get(key)?;
|
|
||||||
let mut keys_ref: &[u8] = keys_vec.as_ref();
|
|
||||||
|
|
||||||
let mut substrate_keys = vec![];
|
/// Tweaks keys as necessary/beneficial.
|
||||||
let mut network_keys = vec![];
|
fn tweak_keys(keys: &mut ThresholdKeys<Self::ExternalNetworkCurve>);
|
||||||
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)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_keys<N: Network>(
|
/// Encode keys as optimal.
|
||||||
txn: &mut impl DbTxn,
|
///
|
||||||
session: &Session,
|
/// A default implementation is provided which calls the traditional `to_bytes`.
|
||||||
substrate_keys: &[ThresholdKeys<Ristretto>],
|
fn encode_key(key: <Self::ExternalNetworkCurve as Ciphersuite>::G) -> Vec<u8> {
|
||||||
network_keys: &[ThresholdKeys<N::Curve>],
|
key.to_bytes().as_ref().to_vec()
|
||||||
) {
|
|
||||||
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<N: Network>(
|
|
||||||
txn: &mut impl DbTxn,
|
|
||||||
session: Session,
|
|
||||||
key_pair: &KeyPair,
|
|
||||||
) -> (Vec<ThresholdKeys<Ristretto>>, Vec<ThresholdKeys<N::Curve>>) {
|
|
||||||
let (keys_vec, keys) =
|
|
||||||
GeneratedKeysDb::read_keys::<N>(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<N: Network>(
|
|
||||||
getter: &impl Get,
|
|
||||||
network_key: &<N::Curve as Ciphersuite>::G,
|
|
||||||
) -> Option<(Session, (Vec<ThresholdKeys<Ristretto>>, Vec<ThresholdKeys<N::Curve>>))> {
|
|
||||||
let res =
|
|
||||||
GeneratedKeysDb::read_keys::<N>(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<N: Network>(
|
|
||||||
getter: &impl Get,
|
|
||||||
session: Session,
|
|
||||||
) -> Option<Vec<ThresholdKeys<Ristretto>>> {
|
|
||||||
let network_key = NetworkKeyDb::get(getter, session)?;
|
|
||||||
Some(GeneratedKeysDb::read_keys::<N>(getter, &Self::key(&network_key))?.1 .0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,49 +128,44 @@ fn coerce_keys<C: EvrfCurve>(
|
||||||
(keys, faulty)
|
(keys, faulty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An instance of the Serai key generation protocol.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct KeyGen<N: Network, D: Db> {
|
pub struct KeyGen<P: KeyGenParams, D: Db> {
|
||||||
db: D,
|
db: D,
|
||||||
substrate_evrf_private_key:
|
substrate_evrf_private_key:
|
||||||
Zeroizing<<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
|
Zeroizing<<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
|
||||||
network_evrf_private_key: Zeroizing<<<N::Curve as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
|
network_evrf_private_key:
|
||||||
|
Zeroizing<<<P::ExternalNetworkCurve as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: Network, D: Db> KeyGen<N, D> {
|
impl<P: KeyGenParams, D: Db> KeyGen<P, D> {
|
||||||
|
/// Create a new key generation instance.
|
||||||
#[allow(clippy::new_ret_no_self)]
|
#[allow(clippy::new_ret_no_self)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
db: D,
|
db: D,
|
||||||
substrate_evrf_private_key: Zeroizing<
|
substrate_evrf_private_key: Zeroizing<
|
||||||
<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F,
|
<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F,
|
||||||
>,
|
>,
|
||||||
network_evrf_private_key: Zeroizing<<<N::Curve as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F>,
|
network_evrf_private_key: Zeroizing<
|
||||||
) -> KeyGen<N, D> {
|
<<P::ExternalNetworkCurve as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F,
|
||||||
|
>,
|
||||||
|
) -> KeyGen<P, D> {
|
||||||
KeyGen { db, substrate_evrf_private_key, network_evrf_private_key }
|
KeyGen { db, substrate_evrf_private_key, network_evrf_private_key }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn in_set(&self, session: &Session) -> bool {
|
/// Fetch the key shares for a specific session.
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
pub fn keys(
|
pub fn key_shares(
|
||||||
&self,
|
|
||||||
key: &<N::Curve as Ciphersuite>::G,
|
|
||||||
) -> Option<(Session, (Vec<ThresholdKeys<Ristretto>>, Vec<ThresholdKeys<N::Curve>>))> {
|
|
||||||
// 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::<N>(&self.db, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn substrate_keys_by_session(
|
|
||||||
&self,
|
&self,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> Option<Vec<ThresholdKeys<Ristretto>>> {
|
) -> Option<(Vec<ThresholdKeys<Ristretto>>, Vec<ThresholdKeys<P::ExternalNetworkCurve>>)> {
|
||||||
KeysDb::substrate_keys_by_session::<N>(&self.db, session)
|
// 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::<P>::key_shares(&self.db, session)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle a message from the coordinator.
|
||||||
pub fn handle(
|
pub fn handle(
|
||||||
&mut self,
|
&mut self,
|
||||||
txn: &mut D::Transaction<'_>,
|
txn: &mut D::Transaction<'_>,
|
||||||
|
@ -292,10 +173,10 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
) -> Vec<ProcessorMessage> {
|
) -> Vec<ProcessorMessage> {
|
||||||
const SUBSTRATE_KEY_CONTEXT: &[u8] = b"substrate";
|
const SUBSTRATE_KEY_CONTEXT: &[u8] = b"substrate";
|
||||||
const NETWORK_KEY_CONTEXT: &[u8] = b"network";
|
const NETWORK_KEY_CONTEXT: &[u8] = b"network";
|
||||||
fn context<N: Network>(session: Session, key_context: &[u8]) -> [u8; 32] {
|
fn context<P: KeyGenParams>(session: Session, key_context: &[u8]) -> [u8; 32] {
|
||||||
// TODO2: Also embed the chain ID/genesis block
|
// TODO2: Also embed the chain ID/genesis block
|
||||||
let mut transcript = RecommendedTranscript::new(b"Serai eVRF Key Gen");
|
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"session", session.0.to_le_bytes());
|
||||||
transcript.append_message(b"key", key_context);
|
transcript.append_message(b"key", key_context);
|
||||||
(&(&transcript.challenge(b"context"))[.. 32]).try_into().unwrap()
|
(&(&transcript.challenge(b"context"))[.. 32]).try_into().unwrap()
|
||||||
|
@ -308,64 +189,68 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
// Unzip the vector of eVRF keys
|
// Unzip the vector of eVRF keys
|
||||||
let substrate_evrf_public_keys =
|
let substrate_evrf_public_keys =
|
||||||
evrf_public_keys.iter().map(|(key, _)| *key).collect::<Vec<_>>();
|
evrf_public_keys.iter().map(|(key, _)| *key).collect::<Vec<_>>();
|
||||||
|
let (substrate_evrf_public_keys, mut faulty) =
|
||||||
|
coerce_keys::<Ristretto>(&substrate_evrf_public_keys);
|
||||||
|
|
||||||
let network_evrf_public_keys =
|
let network_evrf_public_keys =
|
||||||
evrf_public_keys.into_iter().map(|(_, key)| key).collect::<Vec<_>>();
|
evrf_public_keys.into_iter().map(|(_, key)| key).collect::<Vec<_>>();
|
||||||
|
let (network_evrf_public_keys, additional_faulty) =
|
||||||
let mut participation = Vec::with_capacity(2048);
|
coerce_keys::<P::ExternalNetworkCurve>(&network_evrf_public_keys);
|
||||||
let mut faulty = HashSet::new();
|
faulty.extend(additional_faulty);
|
||||||
|
|
||||||
// Participate for both Substrate and the network
|
// Participate for both Substrate and the network
|
||||||
fn participate<C: EvrfCurve>(
|
fn participate<C: EvrfCurve>(
|
||||||
context: [u8; 32],
|
context: [u8; 32],
|
||||||
threshold: u16,
|
threshold: u16,
|
||||||
evrf_public_keys: &[impl AsRef<[u8]>],
|
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
|
||||||
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
|
evrf_private_key: &Zeroizing<<C::EmbeddedCurve as Ciphersuite>::F>,
|
||||||
faulty: &mut HashSet<Participant>,
|
|
||||||
output: &mut impl io::Write,
|
output: &mut impl io::Write,
|
||||||
) {
|
) {
|
||||||
let (coerced_keys, faulty_is) = coerce_keys::<C>(evrf_public_keys);
|
|
||||||
for faulty_i in faulty_is {
|
|
||||||
faulty.insert(faulty_i);
|
|
||||||
}
|
|
||||||
let participation = EvrfDkg::<C>::participate(
|
let participation = EvrfDkg::<C>::participate(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
generators(),
|
generators(),
|
||||||
context,
|
context,
|
||||||
threshold,
|
threshold,
|
||||||
&coerced_keys,
|
evrf_public_keys,
|
||||||
evrf_private_key,
|
evrf_private_key,
|
||||||
);
|
);
|
||||||
participation.unwrap().write(output).unwrap();
|
participation.unwrap().write(output).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut participation = Vec::with_capacity(2048);
|
||||||
participate::<Ristretto>(
|
participate::<Ristretto>(
|
||||||
context::<N>(session, SUBSTRATE_KEY_CONTEXT),
|
context::<P>(session, SUBSTRATE_KEY_CONTEXT),
|
||||||
threshold,
|
threshold,
|
||||||
&substrate_evrf_public_keys,
|
&substrate_evrf_public_keys,
|
||||||
&self.substrate_evrf_private_key,
|
&self.substrate_evrf_private_key,
|
||||||
&mut faulty,
|
|
||||||
&mut participation,
|
&mut participation,
|
||||||
);
|
);
|
||||||
participate::<N::Curve>(
|
participate::<P::ExternalNetworkCurve>(
|
||||||
context::<N>(session, NETWORK_KEY_CONTEXT),
|
context::<P>(session, NETWORK_KEY_CONTEXT),
|
||||||
threshold,
|
threshold,
|
||||||
&network_evrf_public_keys,
|
&network_evrf_public_keys,
|
||||||
&self.network_evrf_private_key,
|
&self.network_evrf_private_key,
|
||||||
&mut faulty,
|
|
||||||
&mut participation,
|
&mut participation,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save the params
|
// Save the params
|
||||||
ParamsDb::set(
|
KeyGenDb::<P>::set_params(
|
||||||
txn,
|
txn,
|
||||||
&session,
|
session,
|
||||||
&(threshold, substrate_evrf_public_keys, network_evrf_public_keys),
|
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
|
// Send back our Participation and all faulty parties
|
||||||
let mut faulty = faulty.into_iter().collect::<Vec<_>>();
|
|
||||||
faulty.sort();
|
|
||||||
|
|
||||||
let mut res = Vec::with_capacity(faulty.len() + 1);
|
let mut res = Vec::with_capacity(faulty.len() + 1);
|
||||||
|
faulty.sort_unstable();
|
||||||
for faulty in faulty {
|
for faulty in faulty {
|
||||||
res.push(ProcessorMessage::Blame { session, participant: faulty });
|
res.push(ProcessorMessage::Blame { session, participant: faulty });
|
||||||
}
|
}
|
||||||
|
@ -377,13 +262,8 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
CoordinatorMessage::Participation { session, participant, participation } => {
|
CoordinatorMessage::Participation { session, participant, participation } => {
|
||||||
info!("received participation from {:?} for {:?}", participant, session);
|
info!("received participation from {:?} for {:?}", participant, session);
|
||||||
|
|
||||||
let (threshold, substrate_evrf_public_keys, network_evrf_public_keys) =
|
let Params { t: threshold, n, substrate_evrf_public_keys, network_evrf_public_keys } =
|
||||||
ParamsDb::get(txn, &session).unwrap();
|
KeyGenDb::<P>::params(txn, session).unwrap();
|
||||||
|
|
||||||
let n = substrate_evrf_public_keys
|
|
||||||
.len()
|
|
||||||
.try_into()
|
|
||||||
.expect("performing a key gen with more than u16::MAX participants");
|
|
||||||
|
|
||||||
// Read these `Participation`s
|
// Read these `Participation`s
|
||||||
// If they fail basic sanity checks, fail fast
|
// If they fail basic sanity checks, fail fast
|
||||||
|
@ -399,7 +279,8 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
return blame;
|
return blame;
|
||||||
};
|
};
|
||||||
let len_at_network_participation_start_pos = participation.len();
|
let len_at_network_participation_start_pos = participation.len();
|
||||||
let Ok(network_participation) = Participation::<N::Curve>::read(&mut participation, n)
|
let Ok(network_participation) =
|
||||||
|
Participation::<P::ExternalNetworkCurve>::read(&mut participation, n)
|
||||||
else {
|
else {
|
||||||
return blame;
|
return blame;
|
||||||
};
|
};
|
||||||
|
@ -413,16 +294,15 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
// If we've already generated these keys, we don't actually need to save these
|
// 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
|
// participations and continue. We solely have to verify them, as to identify malicious
|
||||||
// participants and prevent DoSs, before returning
|
// 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);
|
info!("already finished generating a key for {:?}", session);
|
||||||
|
|
||||||
match EvrfDkg::<Ristretto>::verify(
|
match EvrfDkg::<Ristretto>::verify(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
generators(),
|
generators(),
|
||||||
context::<N>(session, SUBSTRATE_KEY_CONTEXT),
|
context::<P>(session, SUBSTRATE_KEY_CONTEXT),
|
||||||
threshold,
|
threshold,
|
||||||
// Ignores the list of participants who were faulty, as they were prior blamed
|
&substrate_evrf_public_keys,
|
||||||
&coerce_keys::<Ristretto>(&substrate_evrf_public_keys).0,
|
|
||||||
&HashMap::from([(participant, substrate_participation)]),
|
&HashMap::from([(participant, substrate_participation)]),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -434,13 +314,12 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match EvrfDkg::<N::Curve>::verify(
|
match EvrfDkg::<P::ExternalNetworkCurve>::verify(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
generators(),
|
generators(),
|
||||||
context::<N>(session, NETWORK_KEY_CONTEXT),
|
context::<P>(session, NETWORK_KEY_CONTEXT),
|
||||||
threshold,
|
threshold,
|
||||||
// Ignores the list of participants who were faulty, as they were prior blamed
|
&network_evrf_public_keys,
|
||||||
&coerce_keys::<N::Curve>(&network_evrf_public_keys).0,
|
|
||||||
&HashMap::from([(participant, network_participation)]),
|
&HashMap::from([(participant, network_participation)]),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -467,17 +346,22 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
|
|
||||||
// Since these are valid `Participation`s, save them
|
// Since these are valid `Participation`s, save them
|
||||||
let (mut substrate_participations, mut network_participations) =
|
let (mut substrate_participations, mut network_participations) =
|
||||||
ParticipationDb::get(txn, &session)
|
KeyGenDb::<P>::participations(txn, session).map_or_else(
|
||||||
.unwrap_or((HashMap::with_capacity(1), HashMap::with_capacity(1)));
|
|| (HashMap::with_capacity(1), HashMap::with_capacity(1)),
|
||||||
|
|p| (p.substrate_participations, p.network_participations),
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
substrate_participations.insert(participant, substrate_participation).is_none() &&
|
substrate_participations.insert(participant, substrate_participation).is_none() &&
|
||||||
network_participations.insert(participant, network_participation).is_none(),
|
network_participations.insert(participant, network_participation).is_none(),
|
||||||
"received participation for someone multiple times"
|
"received participation for someone multiple times"
|
||||||
);
|
);
|
||||||
ParticipationDb::set(
|
KeyGenDb::<P>::set_participations(
|
||||||
txn,
|
txn,
|
||||||
&session,
|
session,
|
||||||
&(substrate_participations.clone(), network_participations.clone()),
|
&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
|
// This block is taken from the eVRF DKG itself to evaluate the amount participating
|
||||||
|
@ -510,12 +394,12 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we now have the threshold participating, verify their `Participation`s
|
// If we now have the threshold participating, verify their `Participation`s
|
||||||
fn verify_dkg<N: Network, C: EvrfCurve>(
|
fn verify_dkg<P: KeyGenParams, C: EvrfCurve>(
|
||||||
txn: &mut impl DbTxn,
|
txn: &mut impl DbTxn,
|
||||||
session: Session,
|
session: Session,
|
||||||
true_if_substrate_false_if_network: bool,
|
true_if_substrate_false_if_network: bool,
|
||||||
threshold: u16,
|
threshold: u16,
|
||||||
evrf_public_keys: &[impl AsRef<[u8]>],
|
evrf_public_keys: &[<C::EmbeddedCurve as Ciphersuite>::G],
|
||||||
substrate_participations: &mut HashMap<Participant, Vec<u8>>,
|
substrate_participations: &mut HashMap<Participant, Vec<u8>>,
|
||||||
network_participations: &mut HashMap<Participant, Vec<u8>>,
|
network_participations: &mut HashMap<Participant, Vec<u8>>,
|
||||||
) -> Result<EvrfDkg<C>, Vec<ProcessorMessage>> {
|
) -> Result<EvrfDkg<C>, Vec<ProcessorMessage>> {
|
||||||
|
@ -542,7 +426,7 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
match EvrfDkg::<C>::verify(
|
match EvrfDkg::<C>::verify(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
generators(),
|
generators(),
|
||||||
context::<N>(
|
context::<P>(
|
||||||
session,
|
session,
|
||||||
if true_if_substrate_false_if_network {
|
if true_if_substrate_false_if_network {
|
||||||
SUBSTRATE_KEY_CONTEXT
|
SUBSTRATE_KEY_CONTEXT
|
||||||
|
@ -551,8 +435,7 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
threshold,
|
threshold,
|
||||||
// Ignores the list of participants who were faulty, as they were prior blamed
|
evrf_public_keys,
|
||||||
&coerce_keys::<C>(evrf_public_keys).0,
|
|
||||||
&participations,
|
&participations,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -570,10 +453,13 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
blames.push(ProcessorMessage::Blame { session, participant });
|
blames.push(ProcessorMessage::Blame { session, participant });
|
||||||
}
|
}
|
||||||
// Since we removed `Participation`s, write the updated versions to the database
|
// Since we removed `Participation`s, write the updated versions to the database
|
||||||
ParticipationDb::set(
|
KeyGenDb::<P>::set_participations(
|
||||||
txn,
|
txn,
|
||||||
&session,
|
session,
|
||||||
&(substrate_participations.clone(), network_participations.clone()),
|
&Participations {
|
||||||
|
substrate_participations: substrate_participations.clone(),
|
||||||
|
network_participations: network_participations.clone(),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
Err(blames)?
|
Err(blames)?
|
||||||
}
|
}
|
||||||
|
@ -586,7 +472,7 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let substrate_dkg = match verify_dkg::<N, Ristretto>(
|
let substrate_dkg = match verify_dkg::<P, Ristretto>(
|
||||||
txn,
|
txn,
|
||||||
session,
|
session,
|
||||||
true,
|
true,
|
||||||
|
@ -601,7 +487,7 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
Err(blames) => return blames,
|
Err(blames) => return blames,
|
||||||
};
|
};
|
||||||
|
|
||||||
let network_dkg = match verify_dkg::<N, N::Curve>(
|
let network_dkg = match verify_dkg::<P, P::ExternalNetworkCurve>(
|
||||||
txn,
|
txn,
|
||||||
session,
|
session,
|
||||||
false,
|
false,
|
||||||
|
@ -623,38 +509,17 @@ impl<N: Network, D: Db> KeyGen<N, D> {
|
||||||
let mut network_keys = network_dkg.keys(&self.network_evrf_private_key);
|
let mut network_keys = network_dkg.keys(&self.network_evrf_private_key);
|
||||||
// Tweak the keys for the network
|
// Tweak the keys for the network
|
||||||
for network_keys in &mut network_keys {
|
for network_keys in &mut network_keys {
|
||||||
N::tweak_keys(network_keys);
|
P::tweak_keys(network_keys);
|
||||||
}
|
}
|
||||||
GeneratedKeysDb::save_keys::<N>(txn, &session, &substrate_keys, &network_keys);
|
KeyGenDb::<P>::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
|
// Since no one we verified was invalid, and we had the threshold, yield the new keys
|
||||||
vec![ProcessorMessage::GeneratedKeyPair {
|
vec![ProcessorMessage::GeneratedKeyPair {
|
||||||
session,
|
session,
|
||||||
substrate_key: substrate_keys[0].group_key().to_bytes(),
|
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: P::encode_key(network_keys[0].group_key()),
|
||||||
network_key: network_keys[0].group_key().to_bytes().as_ref().to_vec(),
|
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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<N::Curve> {
|
|
||||||
info!(
|
|
||||||
"Confirmed key pair {} {} for {:?}",
|
|
||||||
hex::encode(key_pair.0),
|
|
||||||
hex::encode(&key_pair.1),
|
|
||||||
session,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (substrate_keys, network_keys) = KeysDb::confirm_keys::<N>(txn, session, key_pair);
|
|
||||||
|
|
||||||
KeyConfirmed { substrate_keys, network_keys }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ pub use plan::*;
|
||||||
mod db;
|
mod db;
|
||||||
pub(crate) use db::*;
|
pub(crate) use db::*;
|
||||||
|
|
||||||
mod key_gen;
|
use serai_processor_key_gen as key_gen;
|
||||||
|
|
||||||
pub mod networks;
|
pub mod networks;
|
||||||
pub(crate) mod multisigs;
|
pub(crate) mod multisigs;
|
||||||
|
|
|
@ -48,7 +48,7 @@ pub use db::*;
|
||||||
mod coordinator;
|
mod coordinator;
|
||||||
pub use coordinator::*;
|
pub use coordinator::*;
|
||||||
|
|
||||||
mod key_gen;
|
use serai_processor_key_gen as key_gen;
|
||||||
use key_gen::{SessionDb, KeyConfirmed, KeyGen};
|
use key_gen::{SessionDb, KeyConfirmed, KeyGen};
|
||||||
|
|
||||||
mod signer;
|
mod signer;
|
||||||
|
|
Loading…
Reference in a new issue