mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
394 lines
14 KiB
Rust
394 lines
14 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use zeroize::Zeroize;
|
|
|
|
use scale::{Encode, Decode};
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
use dkg::{Participant, ThresholdParams};
|
|
|
|
use serai_primitives::{BlockHash, NetworkId};
|
|
use in_instructions_primitives::{Batch, SignedBatch};
|
|
use coins_primitives::OutInstructionWithBalance;
|
|
use validator_sets_primitives::{ValidatorSet, KeyPair};
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
|
pub struct SubstrateContext {
|
|
pub serai_time: u64,
|
|
pub network_latest_finalized_block: BlockHash,
|
|
}
|
|
|
|
pub mod key_gen {
|
|
use super::*;
|
|
|
|
#[derive(
|
|
Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize, Encode, Decode, Serialize, Deserialize,
|
|
)]
|
|
pub struct KeyGenId {
|
|
pub set: ValidatorSet,
|
|
pub attempt: u32,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
pub enum CoordinatorMessage {
|
|
// Instructs the Processor to begin the key generation process.
|
|
// TODO: Should this be moved under Substrate?
|
|
GenerateKey { id: KeyGenId, params: ThresholdParams, shares: u16 },
|
|
// Received commitments for the specified key generation protocol.
|
|
Commitments { id: KeyGenId, commitments: HashMap<Participant, Vec<u8>> },
|
|
// Received shares for the specified key generation protocol.
|
|
Shares { id: KeyGenId, shares: Vec<HashMap<Participant, Vec<u8>>> },
|
|
}
|
|
|
|
impl CoordinatorMessage {
|
|
pub fn required_block(&self) -> Option<BlockHash> {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
pub enum ProcessorMessage {
|
|
// Created commitments for the specified key generation protocol.
|
|
Commitments { id: KeyGenId, commitments: Vec<Vec<u8>> },
|
|
// Created shares for the specified key generation protocol.
|
|
Shares { id: KeyGenId, shares: Vec<HashMap<Participant, Vec<u8>>> },
|
|
// Resulting keys from the specified key generation protocol.
|
|
GeneratedKeyPair { id: KeyGenId, substrate_key: [u8; 32], network_key: Vec<u8> },
|
|
}
|
|
}
|
|
|
|
pub mod sign {
|
|
use super::*;
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
|
pub struct SignId {
|
|
pub key: Vec<u8>,
|
|
pub id: [u8; 32],
|
|
pub attempt: u32,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
pub enum CoordinatorMessage {
|
|
// Received preprocesses for the specified signing protocol.
|
|
Preprocesses { id: SignId, preprocesses: HashMap<Participant, Vec<u8>> },
|
|
// Received shares for the specified signing protocol.
|
|
Shares { id: SignId, shares: HashMap<Participant, Vec<u8>> },
|
|
// Re-attempt a signing protocol.
|
|
Reattempt { id: SignId },
|
|
// Completed a signing protocol already.
|
|
Completed { key: Vec<u8>, id: [u8; 32], tx: Vec<u8> },
|
|
}
|
|
|
|
impl CoordinatorMessage {
|
|
pub fn required_block(&self) -> Option<BlockHash> {
|
|
None
|
|
}
|
|
|
|
pub fn key(&self) -> &[u8] {
|
|
match self {
|
|
CoordinatorMessage::Preprocesses { id, .. } => &id.key,
|
|
CoordinatorMessage::Shares { id, .. } => &id.key,
|
|
CoordinatorMessage::Reattempt { id } => &id.key,
|
|
CoordinatorMessage::Completed { key, .. } => key,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
|
pub enum ProcessorMessage {
|
|
// Created preprocess for the specified signing protocol.
|
|
Preprocess { id: SignId, preprocesses: Vec<Vec<u8>> },
|
|
// Signed share for the specified signing protocol.
|
|
Share { id: SignId, shares: Vec<Vec<u8>> },
|
|
// Completed a signing protocol already.
|
|
Completed { key: Vec<u8>, id: [u8; 32], tx: Vec<u8> },
|
|
}
|
|
}
|
|
|
|
pub mod coordinator {
|
|
use super::*;
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
|
pub struct BatchSignId {
|
|
pub key: [u8; 32],
|
|
pub id: [u8; 5],
|
|
pub attempt: u32,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
pub enum CoordinatorMessage {
|
|
// Uses Vec<u8> instead of [u8; 64] since serde Deserialize isn't implemented for [u8; 64]
|
|
BatchPreprocesses { id: BatchSignId, preprocesses: HashMap<Participant, Vec<u8>> },
|
|
BatchShares { id: BatchSignId, shares: HashMap<Participant, [u8; 32]> },
|
|
// Re-attempt a batch signing protocol.
|
|
BatchReattempt { id: BatchSignId },
|
|
}
|
|
|
|
impl CoordinatorMessage {
|
|
// The Coordinator will only send Batch messages once the Batch ID has been recognized
|
|
// The ID will only be recognized when the block is acknowledged by a super-majority of the
|
|
// network *and the local node*
|
|
// This synchrony obtained lets us ignore the synchrony requirement offered here
|
|
pub fn required_block(&self) -> Option<BlockHash> {
|
|
match self {
|
|
CoordinatorMessage::BatchPreprocesses { .. } => None,
|
|
CoordinatorMessage::BatchShares { .. } => None,
|
|
CoordinatorMessage::BatchReattempt { .. } => None,
|
|
}
|
|
}
|
|
|
|
pub fn key(&self) -> &[u8] {
|
|
match self {
|
|
CoordinatorMessage::BatchPreprocesses { id, .. } => &id.key,
|
|
CoordinatorMessage::BatchShares { id, .. } => &id.key,
|
|
CoordinatorMessage::BatchReattempt { id } => &id.key,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
|
pub struct PlanMeta {
|
|
pub key: Vec<u8>,
|
|
pub id: [u8; 32],
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
|
pub enum ProcessorMessage {
|
|
SubstrateBlockAck { network: NetworkId, block: u64, plans: Vec<PlanMeta> },
|
|
BatchPreprocess { id: BatchSignId, block: BlockHash, preprocesses: Vec<Vec<u8>> },
|
|
BatchShare { id: BatchSignId, shares: Vec<[u8; 32]> },
|
|
}
|
|
}
|
|
|
|
pub mod substrate {
|
|
use super::*;
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, Serialize, Deserialize)]
|
|
pub enum CoordinatorMessage {
|
|
ConfirmKeyPair {
|
|
context: SubstrateContext,
|
|
set: ValidatorSet,
|
|
key_pair: KeyPair,
|
|
},
|
|
SubstrateBlock {
|
|
context: SubstrateContext,
|
|
network: NetworkId,
|
|
block: u64,
|
|
burns: Vec<OutInstructionWithBalance>,
|
|
batches: Vec<u32>,
|
|
},
|
|
}
|
|
|
|
impl CoordinatorMessage {
|
|
pub fn required_block(&self) -> Option<BlockHash> {
|
|
let context = match self {
|
|
CoordinatorMessage::ConfirmKeyPair { context, .. } => context,
|
|
CoordinatorMessage::SubstrateBlock { context, .. } => context,
|
|
};
|
|
Some(context.network_latest_finalized_block)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
|
pub enum ProcessorMessage {
|
|
Batch { batch: Batch },
|
|
SignedBatch { batch: SignedBatch },
|
|
}
|
|
}
|
|
|
|
macro_rules! impl_from {
|
|
($from: ident, $to: ident, $via: ident) => {
|
|
impl From<$from::$to> for $to {
|
|
fn from(msg: $from::$to) -> $to {
|
|
$to::$via(msg)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
pub enum CoordinatorMessage {
|
|
KeyGen(key_gen::CoordinatorMessage),
|
|
Sign(sign::CoordinatorMessage),
|
|
Coordinator(coordinator::CoordinatorMessage),
|
|
Substrate(substrate::CoordinatorMessage),
|
|
}
|
|
|
|
impl_from!(key_gen, CoordinatorMessage, KeyGen);
|
|
impl_from!(sign, CoordinatorMessage, Sign);
|
|
impl_from!(coordinator, CoordinatorMessage, Coordinator);
|
|
impl_from!(substrate, CoordinatorMessage, Substrate);
|
|
|
|
impl CoordinatorMessage {
|
|
pub fn required_block(&self) -> Option<BlockHash> {
|
|
let required = match self {
|
|
CoordinatorMessage::KeyGen(msg) => msg.required_block(),
|
|
CoordinatorMessage::Sign(msg) => msg.required_block(),
|
|
CoordinatorMessage::Coordinator(msg) => msg.required_block(),
|
|
CoordinatorMessage::Substrate(msg) => msg.required_block(),
|
|
};
|
|
|
|
// 0 is used when Serai hasn't acknowledged *any* block for this network, which also means
|
|
// there's no need to wait for the block in question
|
|
if required == Some(BlockHash([0; 32])) {
|
|
return None;
|
|
}
|
|
required
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
pub enum ProcessorMessage {
|
|
KeyGen(key_gen::ProcessorMessage),
|
|
Sign(sign::ProcessorMessage),
|
|
Coordinator(coordinator::ProcessorMessage),
|
|
Substrate(substrate::ProcessorMessage),
|
|
}
|
|
|
|
impl_from!(key_gen, ProcessorMessage, KeyGen);
|
|
impl_from!(sign, ProcessorMessage, Sign);
|
|
impl_from!(coordinator, ProcessorMessage, Coordinator);
|
|
impl_from!(substrate, ProcessorMessage, Substrate);
|
|
|
|
// Intent generation code
|
|
|
|
const COORDINATOR_UID: u8 = 0;
|
|
const PROCESSSOR_UID: u8 = 1;
|
|
|
|
const TYPE_KEY_GEN_UID: u8 = 2;
|
|
const TYPE_SIGN_UID: u8 = 3;
|
|
const TYPE_COORDINATOR_UID: u8 = 4;
|
|
const TYPE_SUBSTRATE_UID: u8 = 5;
|
|
|
|
impl CoordinatorMessage {
|
|
/// The intent for this message, which should be unique across the validator's entire system,
|
|
/// including all of its processors.
|
|
///
|
|
/// This doesn't use H(msg.serialize()) as it's meant to be unique to intent, not unique to
|
|
/// values. While the values should be consistent per intent, that assumption isn't required
|
|
/// here.
|
|
pub fn intent(&self) -> Vec<u8> {
|
|
match self {
|
|
CoordinatorMessage::KeyGen(msg) => {
|
|
// Unique since key gen ID embeds the validator set and attempt
|
|
let (sub, id) = match msg {
|
|
key_gen::CoordinatorMessage::GenerateKey { id, .. } => (0, id),
|
|
key_gen::CoordinatorMessage::Commitments { id, .. } => (1, id),
|
|
key_gen::CoordinatorMessage::Shares { id, .. } => (2, id),
|
|
};
|
|
|
|
let mut res = vec![COORDINATOR_UID, TYPE_KEY_GEN_UID, sub];
|
|
res.extend(&id.encode());
|
|
res
|
|
}
|
|
CoordinatorMessage::Sign(msg) => {
|
|
let (sub, id) = match msg {
|
|
// Unique since SignId includes a hash of the network, and specific transaction info
|
|
sign::CoordinatorMessage::Preprocesses { id, .. } => (0, id.encode()),
|
|
sign::CoordinatorMessage::Shares { id, .. } => (1, id.encode()),
|
|
sign::CoordinatorMessage::Reattempt { id } => (2, id.encode()),
|
|
// The coordinator should report all reported completions to the processor
|
|
// 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,
|
|
// which are possible
|
|
sign::CoordinatorMessage::Completed { id, tx, .. } => (3, (id, tx).encode()),
|
|
};
|
|
|
|
let mut res = vec![COORDINATOR_UID, TYPE_SIGN_UID, sub];
|
|
res.extend(&id);
|
|
res
|
|
}
|
|
CoordinatorMessage::Coordinator(msg) => {
|
|
let (sub, id) = match msg {
|
|
// Unique since this embeds the batch ID (hash of it, including its network) and attempt
|
|
coordinator::CoordinatorMessage::BatchPreprocesses { id, .. } => (0, id.encode()),
|
|
coordinator::CoordinatorMessage::BatchShares { id, .. } => (1, id.encode()),
|
|
coordinator::CoordinatorMessage::BatchReattempt { id, .. } => (2, id.encode()),
|
|
};
|
|
|
|
let mut res = vec![COORDINATOR_UID, TYPE_COORDINATOR_UID, sub];
|
|
res.extend(&id);
|
|
res
|
|
}
|
|
CoordinatorMessage::Substrate(msg) => {
|
|
let (sub, id) = match msg {
|
|
// Unique since there's only one key pair for a set
|
|
substrate::CoordinatorMessage::ConfirmKeyPair { set, .. } => (0, set.encode()),
|
|
substrate::CoordinatorMessage::SubstrateBlock { network, block, .. } => {
|
|
(1, (network, block).encode())
|
|
}
|
|
};
|
|
|
|
let mut res = vec![COORDINATOR_UID, TYPE_SUBSTRATE_UID, sub];
|
|
res.extend(&id);
|
|
res
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ProcessorMessage {
|
|
/// The intent for this message, which should be unique across the validator's entire system,
|
|
/// including all of its processors.
|
|
///
|
|
/// This doesn't use H(msg.serialize()) as it's meant to be unique to intent, not unique to
|
|
/// values. While the values should be consistent per intent, that assumption isn't required
|
|
/// here.
|
|
pub fn intent(&self) -> Vec<u8> {
|
|
match self {
|
|
ProcessorMessage::KeyGen(msg) => {
|
|
let (sub, id) = match msg {
|
|
// Unique since KeyGenId
|
|
key_gen::ProcessorMessage::Commitments { id, .. } => (0, id),
|
|
key_gen::ProcessorMessage::Shares { id, .. } => (1, id),
|
|
key_gen::ProcessorMessage::GeneratedKeyPair { id, .. } => (2, id),
|
|
};
|
|
|
|
let mut res = vec![PROCESSSOR_UID, TYPE_KEY_GEN_UID, sub];
|
|
res.extend(&id.encode());
|
|
res
|
|
}
|
|
ProcessorMessage::Sign(msg) => {
|
|
let (sub, id) = match msg {
|
|
// Unique since SignId
|
|
sign::ProcessorMessage::Preprocess { id, .. } => (0, id.encode()),
|
|
sign::ProcessorMessage::Share { id, .. } => (1, id.encode()),
|
|
// Unique since a processor will only sign a TX once
|
|
sign::ProcessorMessage::Completed { id, .. } => (2, id.to_vec()),
|
|
};
|
|
|
|
let mut res = vec![PROCESSSOR_UID, TYPE_SIGN_UID, sub];
|
|
res.extend(&id);
|
|
res
|
|
}
|
|
ProcessorMessage::Coordinator(msg) => {
|
|
let (sub, id) = match msg {
|
|
coordinator::ProcessorMessage::SubstrateBlockAck { network, block, .. } => {
|
|
(0, (network, block).encode())
|
|
}
|
|
// Unique since BatchSignId
|
|
coordinator::ProcessorMessage::BatchPreprocess { id, .. } => (1, id.encode()),
|
|
coordinator::ProcessorMessage::BatchShare { id, .. } => (2, id.encode()),
|
|
};
|
|
|
|
let mut res = vec![PROCESSSOR_UID, TYPE_COORDINATOR_UID, sub];
|
|
res.extend(&id);
|
|
res
|
|
}
|
|
ProcessorMessage::Substrate(msg) => {
|
|
let (sub, id) = match msg {
|
|
// Unique since network and ID binding
|
|
substrate::ProcessorMessage::Batch { batch } => (0, (batch.network, batch.id).encode()),
|
|
substrate::ProcessorMessage::SignedBatch { batch, .. } => {
|
|
(1, (batch.batch.network, batch.batch.id).encode())
|
|
}
|
|
};
|
|
|
|
let mut res = vec![PROCESSSOR_UID, TYPE_SUBSTRATE_UID, sub];
|
|
res.extend(&id);
|
|
res
|
|
}
|
|
}
|
|
}
|
|
}
|