mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 12:09:37 +00:00
Smash out the signer
Abstract, to be done for the transactions, the batches, the cosigns, the slash reports, everything. It has a minimal API itself, intending to be as clear as possible.
This commit is contained in:
parent
2f29c91d30
commit
1e8a9ec5bd
12 changed files with 442 additions and 39 deletions
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
|
@ -40,6 +40,7 @@ jobs:
|
||||||
-p serai-message-queue \
|
-p serai-message-queue \
|
||||||
-p serai-processor-messages \
|
-p serai-processor-messages \
|
||||||
-p serai-processor-key-gen \
|
-p serai-processor-key-gen \
|
||||||
|
-p serai-processor-frost-attempt-manager \
|
||||||
-p serai-processor \
|
-p serai-processor \
|
||||||
-p tendermint-machine \
|
-p tendermint-machine \
|
||||||
-p tributary-chain \
|
-p tributary-chain \
|
||||||
|
|
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -8599,6 +8599,19 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-processor-frost-attempt-manager"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"hex",
|
||||||
|
"log",
|
||||||
|
"modular-frost",
|
||||||
|
"rand_core",
|
||||||
|
"serai-db",
|
||||||
|
"serai-processor-messages",
|
||||||
|
"serai-validator-sets-primitives",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-processor-key-gen"
|
name = "serai-processor-key-gen"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -71,6 +71,7 @@ members = [
|
||||||
|
|
||||||
"processor/messages",
|
"processor/messages",
|
||||||
"processor/key-gen",
|
"processor/key-gen",
|
||||||
|
"processor/frost-attempt-manager",
|
||||||
"processor",
|
"processor",
|
||||||
|
|
||||||
"coordinator/tributary/tendermint",
|
"coordinator/tributary/tendermint",
|
||||||
|
|
|
@ -47,6 +47,7 @@ exceptions = [
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-messages" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-messages" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-key-gen" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-key-gen" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-processor-frost-attempt-manager" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "tributary-chain" },
|
{ allow = ["AGPL-3.0"], name = "tributary-chain" },
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
AGPL-3.0-only license
|
AGPL-3.0-only license
|
||||||
|
|
||||||
Copyright (c) 2022-2023 Luke Parker
|
Copyright (c) 2022-2024 Luke Parker
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License Version 3 as
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
|
29
processor/frost-attempt-manager/Cargo.toml
Normal file
29
processor/frost-attempt-manager/Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[package]
|
||||||
|
name = "serai-processor-frost-attempt-manager"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A manager of multiple attempts of FROST signing protocols"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/processor/frost-attempt-manager"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
keywords = ["frost", "multisig", "threshold"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.79"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand_core = { version = "0.6", default-features = false, features = ["std", "getrandom"] }
|
||||||
|
|
||||||
|
frost = { package = "modular-frost", path = "../../crypto/frost", version = "^0.8.1", default-features = false }
|
||||||
|
|
||||||
|
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
hex = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
|
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
|
serai-db = { path = "../../common/db" }
|
||||||
|
messages = { package = "serai-processor-messages", path = "../messages" }
|
15
processor/frost-attempt-manager/LICENSE
Normal file
15
processor/frost-attempt-manager/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2022-2024 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
6
processor/frost-attempt-manager/README.md
Normal file
6
processor/frost-attempt-manager/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# FROST Attempt Manager
|
||||||
|
|
||||||
|
A library for helper structures to manage various attempts of a FROST signing
|
||||||
|
protocol.
|
||||||
|
|
||||||
|
This library is interacted with via the `serai-processor-messages::sign` API.
|
251
processor/frost-attempt-manager/src/individual.rs
Normal file
251
processor/frost-attempt-manager/src/individual.rs
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use frost::{
|
||||||
|
Participant, FrostError,
|
||||||
|
sign::{Writable, PreprocessMachine, SignMachine, SignatureMachine},
|
||||||
|
};
|
||||||
|
|
||||||
|
use serai_validator_sets_primitives::Session;
|
||||||
|
|
||||||
|
use messages::sign::{SignId, ProcessorMessage};
|
||||||
|
|
||||||
|
/// An instance of a signing protocol with re-attempts handled internally.
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub(crate) struct SigningProtocol<M: Clone + PreprocessMachine> {
|
||||||
|
// The session this signing protocol is being conducted by.
|
||||||
|
session: Session,
|
||||||
|
// The `i` of our first, or starting, set of key shares we will be signing with.
|
||||||
|
// The key shares we sign with are expected to be continguous from this position.
|
||||||
|
start_i: Participant,
|
||||||
|
// The ID of this signing protocol.
|
||||||
|
id: [u8; 32],
|
||||||
|
// This accepts a vector of `root` machines in order to support signing with multiple key shares.
|
||||||
|
root: Vec<M>,
|
||||||
|
preprocessed: HashMap<u32, (Vec<M::SignMachine>, HashMap<Participant, Vec<u8>>)>,
|
||||||
|
// Here, we drop to a single machine as we only need one to complete the signature.
|
||||||
|
shared: HashMap<
|
||||||
|
u32,
|
||||||
|
(
|
||||||
|
<M::SignMachine as SignMachine<M::Signature>>::SignatureMachine,
|
||||||
|
HashMap<Participant, Vec<u8>>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Clone + PreprocessMachine> SigningProtocol<M> {
|
||||||
|
/// Create a new signing protocol.
|
||||||
|
pub(crate) fn new(session: Session, start_i: Participant, id: [u8; 32], root: Vec<M>) -> Self {
|
||||||
|
log::info!("starting signing protocol {}", hex::encode(id));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
session,
|
||||||
|
start_i,
|
||||||
|
id,
|
||||||
|
root,
|
||||||
|
preprocessed: HashMap::with_capacity(1),
|
||||||
|
shared: HashMap::with_capacity(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a new attempt of the signing protocol.
|
||||||
|
///
|
||||||
|
/// Returns the (serialized) preprocesses for the attempt.
|
||||||
|
pub(crate) fn attempt(&mut self, attempt: u32) -> Vec<ProcessorMessage> {
|
||||||
|
/*
|
||||||
|
We'd get slashed as malicious if we:
|
||||||
|
1) Preprocessed
|
||||||
|
2) Rebooted
|
||||||
|
3) On reboot, preprocessed again, sending new preprocesses which would be deduplicated by
|
||||||
|
the message-queue
|
||||||
|
4) Got sent preprocesses
|
||||||
|
5) Sent a share based on our new preprocesses, yet with everyone else expecting it to be
|
||||||
|
based on our old preprocesses
|
||||||
|
|
||||||
|
We avoid this by saving to the DB we preprocessed before sending our preprocessed, and only
|
||||||
|
keeping our preprocesses for this instance of the processor. Accordingly, on reboot, we will
|
||||||
|
flag the prior preprocess and not send new preprocesses.
|
||||||
|
|
||||||
|
We also won't send the share we were supposed to, unfortunately, yet caching/reloading the
|
||||||
|
preprocess has enough safety issues it isn't worth the headache.
|
||||||
|
*/
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
log::debug!("attemting a new instance of signing protocol {}", hex::encode(self.id));
|
||||||
|
|
||||||
|
let mut our_preprocesses = HashMap::with_capacity(self.root.len());
|
||||||
|
let mut preprocessed = Vec::with_capacity(self.root.len());
|
||||||
|
let mut preprocesses = Vec::with_capacity(self.root.len());
|
||||||
|
for (i, machine) in self.root.iter().enumerate() {
|
||||||
|
let (machine, preprocess) = machine.clone().preprocess(&mut OsRng);
|
||||||
|
preprocessed.push(machine);
|
||||||
|
|
||||||
|
let mut this_preprocess = Vec::with_capacity(64);
|
||||||
|
preprocess.write(&mut this_preprocess).unwrap();
|
||||||
|
|
||||||
|
our_preprocesses.insert(
|
||||||
|
Participant::new(
|
||||||
|
u16::from(self.start_i) + u16::try_from(i).expect("signing with 2**16 machines"),
|
||||||
|
)
|
||||||
|
.expect("start_i + i exceeded the valid indexes for a Participant"),
|
||||||
|
this_preprocess.clone(),
|
||||||
|
);
|
||||||
|
preprocesses.push(this_preprocess);
|
||||||
|
}
|
||||||
|
assert!(self.preprocessed.insert(attempt, (preprocessed, our_preprocesses)).is_none());
|
||||||
|
|
||||||
|
vec![ProcessorMessage::Preprocesses {
|
||||||
|
id: SignId { session: self.session, id: self.id, attempt },
|
||||||
|
preprocesses,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle preprocesses for the signing protocol.
|
||||||
|
///
|
||||||
|
/// Returns the (serialized) shares for the attempt.
|
||||||
|
pub(crate) fn preprocesses(
|
||||||
|
&mut self,
|
||||||
|
attempt: u32,
|
||||||
|
serialized_preprocesses: HashMap<Participant, Vec<u8>>,
|
||||||
|
) -> Vec<ProcessorMessage> {
|
||||||
|
log::debug!("handling preprocesses for signing protocol {}", hex::encode(self.id));
|
||||||
|
|
||||||
|
let Some((machines, our_serialized_preprocesses)) = self.preprocessed.remove(&attempt) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut msgs = Vec::with_capacity(1);
|
||||||
|
|
||||||
|
let mut preprocesses =
|
||||||
|
HashMap::with_capacity(serialized_preprocesses.len() + our_serialized_preprocesses.len());
|
||||||
|
for (i, serialized_preprocess) in
|
||||||
|
serialized_preprocesses.into_iter().chain(our_serialized_preprocesses)
|
||||||
|
{
|
||||||
|
let mut serialized_preprocess = serialized_preprocess.as_slice();
|
||||||
|
let Ok(preprocess) = machines[0].read_preprocess(&mut serialized_preprocess) else {
|
||||||
|
msgs.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !serialized_preprocess.is_empty() {
|
||||||
|
msgs.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
preprocesses.insert(i, preprocess);
|
||||||
|
}
|
||||||
|
// We throw out our preprocessed machines here, despite the fact they haven't been invalidated
|
||||||
|
// We could reuse them with a new set of valid preprocesses
|
||||||
|
// https://github.com/serai-dex/serai/issues/588
|
||||||
|
if !msgs.is_empty() {
|
||||||
|
return msgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut our_shares = HashMap::with_capacity(self.root.len());
|
||||||
|
let mut shared = Vec::with_capacity(machines.len());
|
||||||
|
let mut shares = Vec::with_capacity(machines.len());
|
||||||
|
for (i, machine) in machines.into_iter().enumerate() {
|
||||||
|
let i = Participant::new(
|
||||||
|
u16::from(self.start_i) + u16::try_from(i).expect("signing with 2**16 machines"),
|
||||||
|
)
|
||||||
|
.expect("start_i + i exceeded the valid indexes for a Participant");
|
||||||
|
|
||||||
|
let mut preprocesses = preprocesses.clone();
|
||||||
|
assert!(preprocesses.remove(&i).is_some());
|
||||||
|
|
||||||
|
// TODO: Replace this with `()`, which requires making the message type part of the trait
|
||||||
|
let (machine, share) = match machine.sign(preprocesses, &[]) {
|
||||||
|
Ok((machine, share)) => (machine, share),
|
||||||
|
Err(e) => match e {
|
||||||
|
FrostError::InternalError(_) |
|
||||||
|
FrostError::InvalidParticipant(_, _) |
|
||||||
|
FrostError::InvalidSigningSet(_) |
|
||||||
|
FrostError::InvalidParticipantQuantity(_, _) |
|
||||||
|
FrostError::DuplicatedParticipant(_) |
|
||||||
|
FrostError::MissingParticipant(_) |
|
||||||
|
FrostError::InvalidShare(_) => {
|
||||||
|
panic!("FROST had an error which shouldn't be reachable: {e:?}");
|
||||||
|
}
|
||||||
|
FrostError::InvalidPreprocess(i) => {
|
||||||
|
msgs
|
||||||
|
.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
|
||||||
|
return msgs;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
shared.push(machine);
|
||||||
|
|
||||||
|
let mut this_share = Vec::with_capacity(32);
|
||||||
|
share.write(&mut this_share).unwrap();
|
||||||
|
|
||||||
|
our_shares.insert(i, this_share.clone());
|
||||||
|
shares.push(this_share);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(self.shared.insert(attempt, (shared.swap_remove(0), our_shares)).is_none());
|
||||||
|
log::debug!(
|
||||||
|
"successfully handled preprocesses for signing protocol {}, sending shares",
|
||||||
|
hex::encode(self.id)
|
||||||
|
);
|
||||||
|
msgs.push(ProcessorMessage::Shares {
|
||||||
|
id: SignId { session: self.session, id: self.id, attempt },
|
||||||
|
shares,
|
||||||
|
});
|
||||||
|
msgs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process shares for the signing protocol.
|
||||||
|
///
|
||||||
|
/// Returns the signature produced by the protocol.
|
||||||
|
pub(crate) fn shares(
|
||||||
|
&mut self,
|
||||||
|
attempt: u32,
|
||||||
|
serialized_shares: HashMap<Participant, Vec<u8>>,
|
||||||
|
) -> Result<M::Signature, Vec<ProcessorMessage>> {
|
||||||
|
log::debug!("handling shares for signing protocol {}", hex::encode(self.id));
|
||||||
|
|
||||||
|
let Some((machine, our_serialized_shares)) = self.shared.remove(&attempt) else { Err(vec![])? };
|
||||||
|
|
||||||
|
let mut msgs = Vec::with_capacity(1);
|
||||||
|
|
||||||
|
let mut shares = HashMap::with_capacity(serialized_shares.len() + our_serialized_shares.len());
|
||||||
|
for (i, serialized_share) in our_serialized_shares.into_iter().chain(serialized_shares) {
|
||||||
|
let mut serialized_share = serialized_share.as_slice();
|
||||||
|
let Ok(share) = machine.read_share(&mut serialized_share) else {
|
||||||
|
msgs.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !serialized_share.is_empty() {
|
||||||
|
msgs.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
shares.insert(i, share);
|
||||||
|
}
|
||||||
|
if !msgs.is_empty() {
|
||||||
|
Err(msgs)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(shares.remove(&self.start_i).is_some());
|
||||||
|
|
||||||
|
let signature = match machine.complete(shares) {
|
||||||
|
Ok(signature) => signature,
|
||||||
|
Err(e) => match e {
|
||||||
|
FrostError::InternalError(_) |
|
||||||
|
FrostError::InvalidParticipant(_, _) |
|
||||||
|
FrostError::InvalidSigningSet(_) |
|
||||||
|
FrostError::InvalidParticipantQuantity(_, _) |
|
||||||
|
FrostError::DuplicatedParticipant(_) |
|
||||||
|
FrostError::MissingParticipant(_) |
|
||||||
|
FrostError::InvalidPreprocess(_) => {
|
||||||
|
panic!("FROST had an error which shouldn't be reachable: {e:?}");
|
||||||
|
}
|
||||||
|
FrostError::InvalidShare(i) => {
|
||||||
|
Err(vec![ProcessorMessage::InvalidParticipant { session: self.session, participant: i }])?
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("finished signing for protocol {}", hex::encode(self.id));
|
||||||
|
|
||||||
|
Ok(signature)
|
||||||
|
}
|
||||||
|
}
|
92
processor/frost-attempt-manager/src/lib.rs
Normal file
92
processor/frost-attempt-manager/src/lib.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use frost::{Participant, sign::PreprocessMachine};
|
||||||
|
|
||||||
|
use serai_validator_sets_primitives::Session;
|
||||||
|
|
||||||
|
use messages::sign::{ProcessorMessage, CoordinatorMessage};
|
||||||
|
|
||||||
|
mod individual;
|
||||||
|
use individual::SigningProtocol;
|
||||||
|
|
||||||
|
/// A response to handling a message from the coordinator.
|
||||||
|
pub enum Response<M: PreprocessMachine> {
|
||||||
|
/// Messages to send to the coordinator.
|
||||||
|
Messages(Vec<ProcessorMessage>),
|
||||||
|
/// A produced signature.
|
||||||
|
Signature(M::Signature),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A manager of attempts for a variety of signing protocols.
|
||||||
|
pub struct AttemptManager<M: Clone + PreprocessMachine> {
|
||||||
|
session: Session,
|
||||||
|
start_i: Participant,
|
||||||
|
active: HashMap<[u8; 32], SigningProtocol<M>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Clone + PreprocessMachine> AttemptManager<M> {
|
||||||
|
/// Create a new attempt manager.
|
||||||
|
pub fn new(session: Session, start_i: Participant) -> Self {
|
||||||
|
AttemptManager { session, start_i, active: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a signing protocol to attempt.
|
||||||
|
pub fn register(&mut self, id: [u8; 32], machines: Vec<M>) {
|
||||||
|
self.active.insert(id, SigningProtocol::new(self.session, self.start_i, id, machines));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retire a signing protocol.
|
||||||
|
///
|
||||||
|
/// This frees all memory used for it and means no further messages will be handled for it.
|
||||||
|
/// This does not stop the protocol from being re-registered and further worked on (with
|
||||||
|
/// undefined behavior) then. The higher-level context must never call `register` again with this
|
||||||
|
/// ID.
|
||||||
|
// TODO: Also have the DB for this SigningProtocol cleaned up here.
|
||||||
|
pub fn retire(&mut self, id: [u8; 32]) {
|
||||||
|
log::info!("retiring signing protocol {}", hex::encode(id));
|
||||||
|
self.active.remove(&id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle a message for a signing protocol.
|
||||||
|
pub fn handle(&mut self, msg: CoordinatorMessage) -> Response<M> {
|
||||||
|
match msg {
|
||||||
|
CoordinatorMessage::Preprocesses { id, preprocesses } => {
|
||||||
|
let Some(protocol) = self.active.get_mut(&id.id) else {
|
||||||
|
log::trace!(
|
||||||
|
"handling preprocesses for signing protocol {}, which we're not actively running",
|
||||||
|
hex::encode(id.id)
|
||||||
|
);
|
||||||
|
return Response::Messages(vec![]);
|
||||||
|
};
|
||||||
|
Response::Messages(protocol.preprocesses(id.attempt, preprocesses))
|
||||||
|
}
|
||||||
|
CoordinatorMessage::Shares { id, shares } => {
|
||||||
|
let Some(protocol) = self.active.get_mut(&id.id) else {
|
||||||
|
log::trace!(
|
||||||
|
"handling shares for signing protocol {}, which we're not actively running",
|
||||||
|
hex::encode(id.id)
|
||||||
|
);
|
||||||
|
return Response::Messages(vec![]);
|
||||||
|
};
|
||||||
|
match protocol.shares(id.attempt, shares) {
|
||||||
|
Ok(signature) => Response::Signature(signature),
|
||||||
|
Err(messages) => Response::Messages(messages),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CoordinatorMessage::Reattempt { id } => {
|
||||||
|
let Some(protocol) = self.active.get_mut(&id.id) else {
|
||||||
|
log::trace!(
|
||||||
|
"reattempting signing protocol {}, which we're not actively running",
|
||||||
|
hex::encode(id.id)
|
||||||
|
);
|
||||||
|
return Response::Messages(vec![]);
|
||||||
|
};
|
||||||
|
Response::Messages(protocol.attempt(id.attempt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,8 +17,6 @@ use ciphersuite::{
|
||||||
};
|
};
|
||||||
use dkg::{Participant, ThresholdKeys, evrf::*};
|
use dkg::{Participant, ThresholdKeys, evrf::*};
|
||||||
|
|
||||||
use log::info;
|
|
||||||
|
|
||||||
use serai_validator_sets_primitives::Session;
|
use serai_validator_sets_primitives::Session;
|
||||||
use messages::key_gen::*;
|
use messages::key_gen::*;
|
||||||
|
|
||||||
|
@ -184,7 +182,7 @@ impl<P: KeyGenParams, D: Db> KeyGen<P, D> {
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
CoordinatorMessage::GenerateKey { session, threshold, evrf_public_keys } => {
|
CoordinatorMessage::GenerateKey { session, threshold, evrf_public_keys } => {
|
||||||
info!("Generating new key. Session: {session:?}");
|
log::info!("Generating new key. Session: {session:?}");
|
||||||
|
|
||||||
// Unzip the vector of eVRF keys
|
// Unzip the vector of eVRF keys
|
||||||
let substrate_evrf_public_keys =
|
let substrate_evrf_public_keys =
|
||||||
|
@ -260,7 +258,7 @@ impl<P: KeyGenParams, D: Db> KeyGen<P, D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
CoordinatorMessage::Participation { session, participant, participation } => {
|
CoordinatorMessage::Participation { session, participant, participation } => {
|
||||||
info!("received participation from {:?} for {:?}", participant, session);
|
log::info!("received participation from {:?} for {:?}", participant, session);
|
||||||
|
|
||||||
let Params { t: threshold, n, substrate_evrf_public_keys, network_evrf_public_keys } =
|
let Params { t: threshold, n, substrate_evrf_public_keys, network_evrf_public_keys } =
|
||||||
KeyGenDb::<P>::params(txn, session).unwrap();
|
KeyGenDb::<P>::params(txn, session).unwrap();
|
||||||
|
@ -295,7 +293,7 @@ impl<P: KeyGenParams, D: Db> KeyGen<P, D> {
|
||||||
// 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 self.key_shares(session).is_some() {
|
if self.key_shares(session).is_some() {
|
||||||
info!("already finished generating a key for {:?}", session);
|
log::info!("already finished generating a key for {:?}", session);
|
||||||
|
|
||||||
match EvrfDkg::<Ristretto>::verify(
|
match EvrfDkg::<Ristretto>::verify(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
|
|
|
@ -22,7 +22,6 @@ pub mod key_gen {
|
||||||
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum CoordinatorMessage {
|
pub enum CoordinatorMessage {
|
||||||
// Instructs the Processor to begin the key generation process.
|
// Instructs the Processor to begin the key generation process.
|
||||||
// TODO: Should this be moved under Substrate?
|
|
||||||
GenerateKey { session: Session, threshold: u16, evrf_public_keys: Vec<([u8; 32], Vec<u8>)> },
|
GenerateKey { session: Session, threshold: u16, evrf_public_keys: Vec<([u8; 32], Vec<u8>)> },
|
||||||
// Received participations for the specified key generation protocol.
|
// Received participations for the specified key generation protocol.
|
||||||
Participation { session: Session, participant: Participant, participation: Vec<u8> },
|
Participation { session: Session, participant: Participant, participation: Vec<u8> },
|
||||||
|
@ -93,6 +92,8 @@ pub mod sign {
|
||||||
pub attempt: u32,
|
pub attempt: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make this generic to the ID once we introduce topics into the message-queue and remove
|
||||||
|
// the global ProcessorMessage/CoordinatorMessage
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum CoordinatorMessage {
|
pub enum CoordinatorMessage {
|
||||||
// Received preprocesses for the specified signing protocol.
|
// Received preprocesses for the specified signing protocol.
|
||||||
|
@ -101,8 +102,10 @@ pub mod sign {
|
||||||
Shares { id: SignId, shares: HashMap<Participant, Vec<u8>> },
|
Shares { id: SignId, shares: HashMap<Participant, Vec<u8>> },
|
||||||
// Re-attempt a signing protocol.
|
// Re-attempt a signing protocol.
|
||||||
Reattempt { id: SignId },
|
Reattempt { id: SignId },
|
||||||
|
/* TODO
|
||||||
// Completed a signing protocol already.
|
// Completed a signing protocol already.
|
||||||
Completed { session: Session, id: [u8; 32], tx: Vec<u8> },
|
Completed { session: Session, id: [u8; 32], tx: Vec<u8> },
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoordinatorMessage {
|
impl CoordinatorMessage {
|
||||||
|
@ -114,8 +117,8 @@ pub mod sign {
|
||||||
match self {
|
match self {
|
||||||
CoordinatorMessage::Preprocesses { id, .. } |
|
CoordinatorMessage::Preprocesses { id, .. } |
|
||||||
CoordinatorMessage::Shares { id, .. } |
|
CoordinatorMessage::Shares { id, .. } |
|
||||||
CoordinatorMessage::Reattempt { id } => id.session,
|
CoordinatorMessage::Reattempt { id, .. } => id.session,
|
||||||
CoordinatorMessage::Completed { session, .. } => *session,
|
// TODO CoordinatorMessage::Completed { session, .. } => *session,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,13 +126,13 @@ pub mod sign {
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum ProcessorMessage {
|
pub enum ProcessorMessage {
|
||||||
// Participant sent an invalid message during the sign protocol.
|
// Participant sent an invalid message during the sign protocol.
|
||||||
InvalidParticipant { id: SignId, participant: Participant },
|
InvalidParticipant { session: Session, participant: Participant },
|
||||||
// Created preprocess for the specified signing protocol.
|
// Created preprocesses for the specified signing protocol.
|
||||||
Preprocess { id: SignId, preprocesses: Vec<Vec<u8>> },
|
Preprocesses { id: SignId, preprocesses: Vec<Vec<u8>> },
|
||||||
// Signed share for the specified signing protocol.
|
// Signed shares for the specified signing protocol.
|
||||||
Share { id: SignId, shares: Vec<Vec<u8>> },
|
Shares { id: SignId, shares: Vec<Vec<u8>> },
|
||||||
// Completed a signing protocol already.
|
// Completed a signing protocol already.
|
||||||
Completed { session: Session, id: [u8; 32], tx: Vec<u8> },
|
// TODO Completed { session: Session, id: [u8; 32], tx: Vec<u8> },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,10 +168,6 @@ pub mod coordinator {
|
||||||
pub enum CoordinatorMessage {
|
pub enum CoordinatorMessage {
|
||||||
CosignSubstrateBlock { id: SubstrateSignId, block_number: u64 },
|
CosignSubstrateBlock { id: SubstrateSignId, block_number: u64 },
|
||||||
SignSlashReport { id: SubstrateSignId, report: Vec<([u8; 32], u32)> },
|
SignSlashReport { id: SubstrateSignId, report: Vec<([u8; 32], u32)> },
|
||||||
SubstratePreprocesses { id: SubstrateSignId, preprocesses: HashMap<Participant, [u8; 64]> },
|
|
||||||
SubstrateShares { id: SubstrateSignId, shares: HashMap<Participant, [u8; 32]> },
|
|
||||||
// Re-attempt a batch signing protocol.
|
|
||||||
BatchReattempt { id: SubstrateSignId },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoordinatorMessage {
|
impl CoordinatorMessage {
|
||||||
|
@ -192,9 +191,9 @@ pub mod coordinator {
|
||||||
SubstrateBlockAck { block: u64, plans: Vec<PlanMeta> },
|
SubstrateBlockAck { block: u64, plans: Vec<PlanMeta> },
|
||||||
InvalidParticipant { id: SubstrateSignId, participant: Participant },
|
InvalidParticipant { id: SubstrateSignId, participant: Participant },
|
||||||
CosignPreprocess { id: SubstrateSignId, preprocesses: Vec<[u8; 64]> },
|
CosignPreprocess { id: SubstrateSignId, preprocesses: Vec<[u8; 64]> },
|
||||||
|
// TODO: Remove BatchPreprocess? Why does this take a BlockHash here and not in its
|
||||||
|
// SubstrateSignId?
|
||||||
BatchPreprocess { id: SubstrateSignId, block: BlockHash, preprocesses: Vec<[u8; 64]> },
|
BatchPreprocess { id: SubstrateSignId, block: BlockHash, preprocesses: Vec<[u8; 64]> },
|
||||||
SlashReportPreprocess { id: SubstrateSignId, preprocesses: Vec<[u8; 64]> },
|
|
||||||
SubstrateShare { id: SubstrateSignId, shares: Vec<[u8; 32]> },
|
|
||||||
// TODO: Make these signatures [u8; 64]?
|
// TODO: Make these signatures [u8; 64]?
|
||||||
CosignedBlock { block_number: u64, block: [u8; 32], signature: Vec<u8> },
|
CosignedBlock { block_number: u64, block: [u8; 32], signature: Vec<u8> },
|
||||||
SignedSlashReport { session: Session, signature: Vec<u8> },
|
SignedSlashReport { session: Session, signature: Vec<u8> },
|
||||||
|
@ -327,19 +326,19 @@ impl CoordinatorMessage {
|
||||||
}
|
}
|
||||||
CoordinatorMessage::Sign(msg) => {
|
CoordinatorMessage::Sign(msg) => {
|
||||||
let (sub, id) = match msg {
|
let (sub, id) = match msg {
|
||||||
// Unique since SignId includes a hash of the network, and specific transaction info
|
// Unique since SignId
|
||||||
sign::CoordinatorMessage::Preprocesses { id, .. } => (0, id.encode()),
|
sign::CoordinatorMessage::Preprocesses { id, .. } => (0, id),
|
||||||
sign::CoordinatorMessage::Shares { id, .. } => (1, id.encode()),
|
sign::CoordinatorMessage::Shares { id, .. } => (1, id),
|
||||||
sign::CoordinatorMessage::Reattempt { id } => (2, id.encode()),
|
sign::CoordinatorMessage::Reattempt { id, .. } => (2, id),
|
||||||
// The coordinator should report all reported completions to the processor
|
// The coordinator should report all reported completions to the processor
|
||||||
// Accordingly, the intent is a combination of plan ID and actual TX
|
// Accordingly, the intent is a combination of plan ID and actual TX
|
||||||
// While transaction alone may suffice, that doesn't cover cross-chain TX ID conflicts,
|
// While transaction alone may suffice, that doesn't cover cross-chain TX ID conflicts,
|
||||||
// which are possible
|
// which are possible
|
||||||
sign::CoordinatorMessage::Completed { id, tx, .. } => (3, (id, tx).encode()),
|
// TODO sign::CoordinatorMessage::Completed { id, tx, .. } => (3, (id, tx).encode()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = vec![COORDINATOR_UID, TYPE_SIGN_UID, sub];
|
let mut res = vec![COORDINATOR_UID, TYPE_SIGN_UID, sub];
|
||||||
res.extend(&id);
|
res.extend(id.encode());
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
CoordinatorMessage::Coordinator(msg) => {
|
CoordinatorMessage::Coordinator(msg) => {
|
||||||
|
@ -349,10 +348,6 @@ impl CoordinatorMessage {
|
||||||
// Unique since there's only one of these per session/attempt, and ID is inclusive to
|
// Unique since there's only one of these per session/attempt, and ID is inclusive to
|
||||||
// both
|
// both
|
||||||
coordinator::CoordinatorMessage::SignSlashReport { id, .. } => (1, id.encode()),
|
coordinator::CoordinatorMessage::SignSlashReport { id, .. } => (1, id.encode()),
|
||||||
// Unique since this embeds the batch ID (including its network) and attempt
|
|
||||||
coordinator::CoordinatorMessage::SubstratePreprocesses { id, .. } => (2, id.encode()),
|
|
||||||
coordinator::CoordinatorMessage::SubstrateShares { id, .. } => (3, id.encode()),
|
|
||||||
coordinator::CoordinatorMessage::BatchReattempt { id, .. } => (4, id.encode()),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = vec![COORDINATOR_UID, TYPE_COORDINATOR_UID, sub];
|
let mut res = vec![COORDINATOR_UID, TYPE_COORDINATOR_UID, sub];
|
||||||
|
@ -404,12 +399,15 @@ impl ProcessorMessage {
|
||||||
}
|
}
|
||||||
ProcessorMessage::Sign(msg) => {
|
ProcessorMessage::Sign(msg) => {
|
||||||
let (sub, id) = match msg {
|
let (sub, id) = match msg {
|
||||||
|
// Unique since we'll only fatally slash a a participant once
|
||||||
|
sign::ProcessorMessage::InvalidParticipant { session, participant } => {
|
||||||
|
(0, (session, u16::from(*participant)).encode())
|
||||||
|
}
|
||||||
// Unique since SignId
|
// Unique since SignId
|
||||||
sign::ProcessorMessage::InvalidParticipant { id, .. } => (0, id.encode()),
|
sign::ProcessorMessage::Preprocesses { id, .. } => (1, id.encode()),
|
||||||
sign::ProcessorMessage::Preprocess { id, .. } => (1, id.encode()),
|
sign::ProcessorMessage::Shares { id, .. } => (2, id.encode()),
|
||||||
sign::ProcessorMessage::Share { id, .. } => (2, id.encode()),
|
|
||||||
// Unique since a processor will only sign a TX once
|
// Unique since a processor will only sign a TX once
|
||||||
sign::ProcessorMessage::Completed { id, .. } => (3, id.to_vec()),
|
// TODO sign::ProcessorMessage::Completed { id, .. } => (3, id.to_vec()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = vec![PROCESSOR_UID, TYPE_SIGN_UID, sub];
|
let mut res = vec![PROCESSOR_UID, TYPE_SIGN_UID, sub];
|
||||||
|
@ -423,11 +421,9 @@ impl ProcessorMessage {
|
||||||
coordinator::ProcessorMessage::InvalidParticipant { id, .. } => (1, id.encode()),
|
coordinator::ProcessorMessage::InvalidParticipant { id, .. } => (1, id.encode()),
|
||||||
coordinator::ProcessorMessage::CosignPreprocess { id, .. } => (2, id.encode()),
|
coordinator::ProcessorMessage::CosignPreprocess { id, .. } => (2, id.encode()),
|
||||||
coordinator::ProcessorMessage::BatchPreprocess { id, .. } => (3, id.encode()),
|
coordinator::ProcessorMessage::BatchPreprocess { id, .. } => (3, id.encode()),
|
||||||
coordinator::ProcessorMessage::SlashReportPreprocess { id, .. } => (4, id.encode()),
|
|
||||||
coordinator::ProcessorMessage::SubstrateShare { id, .. } => (5, id.encode()),
|
|
||||||
// Unique since only one instance of a signature matters
|
// Unique since only one instance of a signature matters
|
||||||
coordinator::ProcessorMessage::CosignedBlock { block, .. } => (6, block.encode()),
|
coordinator::ProcessorMessage::CosignedBlock { block, .. } => (4, block.encode()),
|
||||||
coordinator::ProcessorMessage::SignedSlashReport { .. } => (7, vec![]),
|
coordinator::ProcessorMessage::SignedSlashReport { .. } => (5, vec![]),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = vec![PROCESSOR_UID, TYPE_COORDINATOR_UID, sub];
|
let mut res = vec![PROCESSOR_UID, TYPE_COORDINATOR_UID, sub];
|
||||||
|
|
Loading…
Reference in a new issue