mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Add cosign signature verification
This commit is contained in:
@@ -18,14 +18,10 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std", "derive"] }
|
|
||||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
|
||||||
|
|
||||||
blake2 = { version = "0.10", default-features = false, features = ["std"] }
|
blake2 = { version = "0.10", default-features = false, features = ["std"] }
|
||||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
|
schnorrkel = { version = "0.11", default-features = false, features = ["std"] }
|
||||||
schnorr = { package = "schnorr-signatures", path = "../../crypto/schnorr", default-features = false, features = ["std"] }
|
|
||||||
|
|
||||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false, features = ["std"] }
|
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai", "borsh"] }
|
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai", "borsh"] }
|
||||||
|
|
||||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
|
|||||||
@@ -114,8 +114,8 @@ asynchronous network or 11.33% of non-Serai validator sets' stake.
|
|||||||
### TODO
|
### TODO
|
||||||
|
|
||||||
The Serai node no longer responding to RPC requests upon detecting any
|
The Serai node no longer responding to RPC requests upon detecting any
|
||||||
equivocation, and the fallback protocol where validators individually produce
|
equivocation, the delayed acknowledgement of cosigns, and the fallback protocol
|
||||||
signatures, are not implemented at this time. The former means the detection of
|
where validators individually produce signatures, are not implemented at this
|
||||||
equivocating cosigns not redundant and the latter makes 5.67% of non-Serai
|
time. The former means the detection of equivocating cosigns not redundant and
|
||||||
validator sets' stake the DoS threshold, even without control of an
|
the latter makes 5.67% of non-Serai validator sets' stake the DoS threshold,
|
||||||
asynchronous network.
|
even without control of an asynchronous network.
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D,
|
|||||||
// Check if we have the cosign from this set
|
// Check if we have the cosign from this set
|
||||||
if cosigns_for_block
|
if cosigns_for_block
|
||||||
.iter()
|
.iter()
|
||||||
.any(|cosign| cosign.cosigner == Cosigner::ValidatorSet(set.network))
|
.any(|cosign| cosign.cosign.cosigner == Cosigner::ValidatorSet(set.network))
|
||||||
{
|
{
|
||||||
// Since have this cosign, add the set's weight to the weight which has cosigned
|
// Since have this cosign, add the set's weight to the weight which has cosigned
|
||||||
weight_cosigned += stake;
|
weight_cosigned += stake;
|
||||||
@@ -139,14 +139,14 @@ impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D,
|
|||||||
let Some(cosign) = NetworksLatestCosignedBlock::get(&txn, set.network) else {
|
let Some(cosign) = NetworksLatestCosignedBlock::get(&txn, set.network) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if cosign.block_number >= block_number {
|
if cosign.cosign.block_number >= block_number {
|
||||||
weight_cosigned += total_weight
|
weight_cosigned += total_weight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the lowest block common to all of these cosigns
|
// Update the lowest block common to all of these cosigns
|
||||||
lowest_common_block = lowest_common_block
|
lowest_common_block = lowest_common_block
|
||||||
.map(|existing| existing.min(cosign.block_number))
|
.map(|existing| existing.min(cosign.cosign.block_number))
|
||||||
.or(Some(cosign.block_number));
|
.or(Some(cosign.cosign.block_number));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the sum weight doesn't cross the required threshold
|
// Check if the sum weight doesn't cross the required threshold
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ mod intend;
|
|||||||
mod evaluator;
|
mod evaluator;
|
||||||
use evaluator::LatestCosignedBlockNumber;
|
use evaluator::LatestCosignedBlockNumber;
|
||||||
|
|
||||||
|
/// The schnorrkel context to used when signing a cosign.
|
||||||
|
pub const COSIGN_CONTEXT: &[u8] = b"serai-cosign";
|
||||||
|
|
||||||
/// A 'global session', defined as all validator sets used for cosigning at a given moment.
|
/// A 'global session', defined as all validator sets used for cosigning at a given moment.
|
||||||
///
|
///
|
||||||
/// We evaluate cosign faults within a global session. This ensures even if cosigners cosign
|
/// We evaluate cosign faults within a global session. This ensures even if cosigners cosign
|
||||||
@@ -63,14 +66,14 @@ create_db! {
|
|||||||
//
|
//
|
||||||
// This will only be populated with cosigns predating or during the most recent global session
|
// This will only be populated with cosigns predating or during the most recent global session
|
||||||
// to have its start cosigned.
|
// to have its start cosigned.
|
||||||
Cosigns: (block_number: u64) -> Vec<Cosign>,
|
Cosigns: (block_number: u64) -> Vec<SignedCosign>,
|
||||||
// The latest cosigned block for each network.
|
// The latest cosigned block for each network.
|
||||||
//
|
//
|
||||||
// This will only be populated with cosigns predating or during the most recent global session
|
// This will only be populated with cosigns predating or during the most recent global session
|
||||||
// to have its start cosigned.
|
// to have its start cosigned.
|
||||||
NetworksLatestCosignedBlock: (network: NetworkId) -> Cosign,
|
NetworksLatestCosignedBlock: (network: NetworkId) -> SignedCosign,
|
||||||
// Cosigns received for blocks not locally recognized as finalized.
|
// Cosigns received for blocks not locally recognized as finalized.
|
||||||
Faults: (global_session: [u8; 32]) -> Vec<Cosign>,
|
Faults: (global_session: [u8; 32]) -> Vec<SignedCosign>,
|
||||||
// The global session which faulted.
|
// The global session which faulted.
|
||||||
FaultedSession: () -> [u8; 32],
|
FaultedSession: () -> [u8; 32],
|
||||||
}
|
}
|
||||||
@@ -105,7 +108,7 @@ struct CosignIntent {
|
|||||||
|
|
||||||
/// The identification of a cosigner.
|
/// The identification of a cosigner.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
enum Cosigner {
|
pub enum Cosigner {
|
||||||
/// The network which produced this cosign.
|
/// The network which produced this cosign.
|
||||||
ValidatorSet(NetworkId),
|
ValidatorSet(NetworkId),
|
||||||
/// The individual validator which produced this cosign.
|
/// The individual validator which produced this cosign.
|
||||||
@@ -113,16 +116,34 @@ enum Cosigner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A cosign.
|
/// A cosign.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
struct Cosign {
|
pub struct Cosign {
|
||||||
/// The global session this cosign is being performed under.
|
/// The global session this cosign is being performed under.
|
||||||
global_session: [u8; 32],
|
pub global_session: [u8; 32],
|
||||||
/// The number of the block to cosign.
|
/// The number of the block to cosign.
|
||||||
block_number: u64,
|
pub block_number: u64,
|
||||||
/// The hash of the block to cosign.
|
/// The hash of the block to cosign.
|
||||||
block_hash: [u8; 32],
|
pub block_hash: [u8; 32],
|
||||||
/// The actual cosigner.
|
/// The actual cosigner.
|
||||||
cosigner: Cosigner,
|
pub cosigner: Cosigner,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A signed cosign.
|
||||||
|
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
|
pub struct SignedCosign {
|
||||||
|
/// The cosign.
|
||||||
|
pub cosign: Cosign,
|
||||||
|
/// The signature for the cosign.
|
||||||
|
pub signature: [u8; 64],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignedCosign {
|
||||||
|
fn verify_signature(&self, signer: serai_client::Public) -> bool {
|
||||||
|
let Ok(signer) = schnorrkel::PublicKey::from_bytes(&signer.0) else { return false };
|
||||||
|
let Ok(signature) = schnorrkel::Signature::from_bytes(&self.signature) else { return false };
|
||||||
|
|
||||||
|
signer.verify_simple(COSIGN_CONTEXT, &borsh::to_vec(&self.cosign).unwrap(), &signature).is_ok()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a `TemporalSerai` bound to the time used for cosigning this block.
|
/// Construct a `TemporalSerai` bound to the time used for cosigning this block.
|
||||||
@@ -258,7 +279,7 @@ impl<D: Db> Cosigning<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch the notable cosigns for a global session in order to respond to requests.
|
/// Fetch the notable cosigns for a global session in order to respond to requests.
|
||||||
pub fn notable_cosigns(&self, global_session: [u8; 32]) -> Vec<Cosign> {
|
pub fn notable_cosigns(&self, global_session: [u8; 32]) -> Vec<SignedCosign> {
|
||||||
todo!("TODO")
|
todo!("TODO")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,14 +287,14 @@ impl<D: Db> Cosigning<D> {
|
|||||||
///
|
///
|
||||||
/// This will be the most recent cosigns, in case the initial broadcast failed, or the faulty
|
/// This will be the most recent cosigns, in case the initial broadcast failed, or the faulty
|
||||||
/// cosigns, in case of a fault, to induce identification of the fault by others.
|
/// cosigns, in case of a fault, to induce identification of the fault by others.
|
||||||
pub fn cosigns_to_rebroadcast(&self) -> Vec<Cosign> {
|
pub fn cosigns_to_rebroadcast(&self) -> Vec<SignedCosign> {
|
||||||
if let Some(faulted) = FaultedSession::get(&self.db) {
|
if let Some(faulted) = FaultedSession::get(&self.db) {
|
||||||
let mut cosigns = Faults::get(&self.db, faulted).unwrap();
|
let mut cosigns = Faults::get(&self.db, faulted).unwrap();
|
||||||
// Also include all of our recognized-as-honest cosigns in an attempt to induce fault
|
// Also include all of our recognized-as-honest cosigns in an attempt to induce fault
|
||||||
// identification in those who see the faulty cosigns as honest
|
// identification in those who see the faulty cosigns as honest
|
||||||
for network in serai_client::primitives::NETWORKS {
|
for network in serai_client::primitives::NETWORKS {
|
||||||
if let Some(cosign) = NetworksLatestCosignedBlock::get(&self.db, network) {
|
if let Some(cosign) = NetworksLatestCosignedBlock::get(&self.db, network) {
|
||||||
if cosign.global_session == faulted {
|
if cosign.cosign.global_session == faulted {
|
||||||
cosigns.push(cosign);
|
cosigns.push(cosign);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,12 +324,14 @@ impl<D: Db> Cosigning<D> {
|
|||||||
// more relevant, cosign) again.
|
// more relevant, cosign) again.
|
||||||
//
|
//
|
||||||
// Takes `&mut self` as this should only be called once at any given moment.
|
// Takes `&mut self` as this should only be called once at any given moment.
|
||||||
pub async fn intake_cosign(&mut self, cosign: Cosign) -> Result<bool, String> {
|
pub async fn intake_cosign(&mut self, signed_cosign: SignedCosign) -> Result<bool, String> {
|
||||||
|
let cosign = &signed_cosign.cosign;
|
||||||
|
|
||||||
// Check if we've prior handled this cosign
|
// Check if we've prior handled this cosign
|
||||||
let mut txn = self.db.txn();
|
let mut txn = self.db.txn();
|
||||||
let mut cosigns_for_this_block_position =
|
let mut cosigns_for_this_block_position =
|
||||||
Cosigns::get(&txn, cosign.block_number).unwrap_or(vec![]);
|
Cosigns::get(&txn, cosign.block_number).unwrap_or(vec![]);
|
||||||
if cosigns_for_this_block_position.iter().any(|existing| *existing == cosign) {
|
if cosigns_for_this_block_position.iter().any(|existing| existing.cosign == *cosign) {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +352,9 @@ impl<D: Db> Cosigning<D> {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
todo!("TODO");
|
if !signed_cosign.verify_signature(keys.0) {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
network
|
network
|
||||||
}
|
}
|
||||||
@@ -348,7 +373,7 @@ impl<D: Db> Cosigning<D> {
|
|||||||
// cosign
|
// cosign
|
||||||
|
|
||||||
// Save the cosign to the database
|
// Save the cosign to the database
|
||||||
cosigns_for_this_block_position.push(cosign);
|
cosigns_for_this_block_position.push(signed_cosign.clone());
|
||||||
Cosigns::set(&mut txn, cosign.block_number, &cosigns_for_this_block_position);
|
Cosigns::set(&mut txn, cosign.block_number, &cosigns_for_this_block_position);
|
||||||
|
|
||||||
let our_block_hash = self
|
let our_block_hash = self
|
||||||
@@ -359,23 +384,23 @@ impl<D: Db> Cosigning<D> {
|
|||||||
.expect("requested hash of a finalized block yet received None");
|
.expect("requested hash of a finalized block yet received None");
|
||||||
if our_block_hash == cosign.block_hash {
|
if our_block_hash == cosign.block_hash {
|
||||||
// If this is for a future global session, we don't acknowledge this cosign at this time
|
// If this is for a future global session, we don't acknowledge this cosign at this time
|
||||||
if global_session_start_block_number > LatestCosignedBlockNumber::get(&self.db).unwrap_or(0) {
|
if global_session_start_block_number > LatestCosignedBlockNumber::get(&txn).unwrap_or(0) {
|
||||||
drop(txn);
|
drop(txn);
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if NetworksLatestCosignedBlock::get(&txn, network)
|
if NetworksLatestCosignedBlock::get(&txn, network)
|
||||||
.map(|cosign| cosign.block_number)
|
.map(|cosign| cosign.cosign.block_number)
|
||||||
.unwrap_or(0) <
|
.unwrap_or(0) <
|
||||||
cosign.block_number
|
cosign.block_number
|
||||||
{
|
{
|
||||||
NetworksLatestCosignedBlock::set(&mut txn, network, &cosign);
|
NetworksLatestCosignedBlock::set(&mut txn, network, &signed_cosign);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut faults = Faults::get(&txn, cosign.global_session).unwrap_or(vec![]);
|
let mut faults = Faults::get(&txn, cosign.global_session).unwrap_or(vec![]);
|
||||||
// Only handle this as a fault if this set wasn't prior faulty
|
// Only handle this as a fault if this set wasn't prior faulty
|
||||||
if !faults.iter().any(|cosign| cosign.cosigner == Cosigner::ValidatorSet(network)) {
|
if !faults.iter().any(|cosign| cosign.cosign.cosigner == Cosigner::ValidatorSet(network)) {
|
||||||
faults.push(cosign);
|
faults.push(signed_cosign.clone());
|
||||||
Faults::set(&mut txn, cosign.global_session, &faults);
|
Faults::set(&mut txn, cosign.global_session, &faults);
|
||||||
|
|
||||||
let mut weight_cosigned = 0;
|
let mut weight_cosigned = 0;
|
||||||
@@ -394,7 +419,10 @@ impl<D: Db> Cosigning<D> {
|
|||||||
total_weight += stake;
|
total_weight += stake;
|
||||||
|
|
||||||
// Check if this set cosigned this block or not
|
// Check if this set cosigned this block or not
|
||||||
if faults.iter().any(|cosign| cosign.cosigner == Cosigner::ValidatorSet(set.network)) {
|
if faults
|
||||||
|
.iter()
|
||||||
|
.any(|cosign| cosign.cosign.cosigner == Cosigner::ValidatorSet(set.network))
|
||||||
|
{
|
||||||
weight_cosigned += total_weight
|
weight_cosigned += total_weight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user