mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Use a MuSig signature to publish validator set key pairs to Serai
The processor/coordinator flow still has to be rewritten.
This commit is contained in:
@@ -13,6 +13,7 @@ all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
zeroize = "^1.5"
|
||||
thiserror = { version = "1", optional = true }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3" }
|
||||
@@ -28,6 +29,17 @@ bitcoin = { version = "0.30", optional = true }
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", version = "0.3", optional = true }
|
||||
monero-serai = { path = "../../coins/monero", version = "0.1.4-alpha", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1"
|
||||
|
||||
rand_core = "0.6"
|
||||
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", features = ["ristretto"] }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", features = ["tests"] }
|
||||
schnorrkel = { path = "../../crypto/schnorrkel", package = "frost-schnorrkel" }
|
||||
|
||||
tokio = "1"
|
||||
|
||||
[features]
|
||||
serai = ["thiserror", "scale-info", "subxt"]
|
||||
|
||||
@@ -38,10 +50,3 @@ monero = ["coins", "ciphersuite/ed25519", "monero-serai"]
|
||||
# Assumes the default usage is to use Serai as a DEX, which doesn't actually
|
||||
# require connecting to a Serai node
|
||||
default = ["bitcoin", "monero"]
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1"
|
||||
|
||||
rand_core = "0.6"
|
||||
|
||||
tokio = "1"
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Serai {
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn execute_batch(&self, batch: SignedBatch) -> Encoded {
|
||||
self.unsigned::<InInstructions, _>(&in_instructions::Call::<Runtime>::execute_batch { batch })
|
||||
pub fn execute_batch(batch: SignedBatch) -> Encoded {
|
||||
Self::unsigned::<InInstructions, _>(&in_instructions::Call::<Runtime>::execute_batch { batch })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ impl Serai {
|
||||
.map_err(SeraiError::RpcError)
|
||||
}
|
||||
|
||||
fn unsigned<P: 'static, C: Encode>(&self, call: &C) -> Encoded {
|
||||
fn unsigned<P: 'static, C: Encode>(call: &C) -> Encoded {
|
||||
// TODO: Should Serai purge the old transaction code AND set this to 0/1?
|
||||
const TRANSACTION_VERSION: u8 = 4;
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use sp_core::sr25519::Signature;
|
||||
|
||||
use serai_runtime::{validator_sets, ValidatorSets, Runtime};
|
||||
pub use validator_sets::primitives;
|
||||
use primitives::{ValidatorSet, ValidatorSetData, KeyPair};
|
||||
|
||||
use subxt::tx::Payload;
|
||||
use subxt::utils::Encoded;
|
||||
|
||||
use crate::{primitives::NetworkId, Serai, SeraiError, Composite, scale_value, scale_composite};
|
||||
use crate::{primitives::NetworkId, Serai, SeraiError, scale_value};
|
||||
|
||||
const PALLET: &str = "ValidatorSets";
|
||||
|
||||
@@ -20,15 +22,6 @@ impl Serai {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_vote_events(
|
||||
&self,
|
||||
block: [u8; 32],
|
||||
) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
||||
self
|
||||
.events::<ValidatorSets, _>(block, |event| matches!(event, ValidatorSetsEvent::Vote { .. }))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_key_gen_events(
|
||||
&self,
|
||||
block: [u8; 32],
|
||||
@@ -52,17 +45,35 @@ impl Serai {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_validator_set_musig_key(
|
||||
&self,
|
||||
set: ValidatorSet,
|
||||
) -> Result<Option<[u8; 32]>, SeraiError> {
|
||||
self
|
||||
.storage(
|
||||
PALLET,
|
||||
"MuSigKeys",
|
||||
Some(vec![scale_value(set)]),
|
||||
self.get_latest_block_hash().await?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_keys(&self, set: ValidatorSet) -> Result<Option<KeyPair>, SeraiError> {
|
||||
self
|
||||
.storage(PALLET, "Keys", Some(vec![scale_value(set)]), self.get_latest_block_hash().await?)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn vote(network: NetworkId, key_pair: KeyPair) -> Payload<Composite<()>> {
|
||||
Payload::new(
|
||||
PALLET,
|
||||
"vote",
|
||||
scale_composite(validator_sets::Call::<Runtime>::vote { network, key_pair }),
|
||||
)
|
||||
pub fn set_validator_set_keys(
|
||||
network: NetworkId,
|
||||
key_pair: KeyPair,
|
||||
signature: Signature,
|
||||
) -> Encoded {
|
||||
Self::unsigned::<ValidatorSets, _>(&validator_sets::Call::<Runtime>::set_keys {
|
||||
network,
|
||||
key_pair,
|
||||
signature,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ use serai_client::{
|
||||
primitives::{Batch, SignedBatch},
|
||||
InInstructionsEvent,
|
||||
},
|
||||
Serai,
|
||||
};
|
||||
|
||||
use crate::common::{serai, tx::publish_tx, validator_sets::vote_in_keys};
|
||||
use crate::common::{serai, tx::publish_tx, validator_sets::set_validator_set_keys};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn provide_batch(batch: Batch) -> [u8; 32] {
|
||||
@@ -24,15 +25,15 @@ pub async fn provide_batch(batch: Batch) -> [u8; 32] {
|
||||
keys
|
||||
} else {
|
||||
let keys = (pair.public(), vec![].try_into().unwrap());
|
||||
vote_in_keys(set, keys.clone()).await;
|
||||
set_validator_set_keys(set, keys.clone()).await;
|
||||
keys
|
||||
};
|
||||
assert_eq!(keys.0, pair.public());
|
||||
|
||||
let block = publish_tx(
|
||||
&serai
|
||||
.execute_batch(SignedBatch { batch: batch.clone(), signature: pair.sign(&batch.encode()) }),
|
||||
)
|
||||
let block = publish_tx(&Serai::execute_batch(SignedBatch {
|
||||
batch: batch.clone(),
|
||||
signature: pair.sign(&batch.encode()),
|
||||
}))
|
||||
.await;
|
||||
|
||||
let batches = serai.get_batch_events(block).await.unwrap();
|
||||
|
||||
@@ -1,42 +1,68 @@
|
||||
use sp_core::Pair;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use scale::Encode;
|
||||
|
||||
use sp_core::{Pair, sr25519::Signature};
|
||||
|
||||
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||
use frost::dkg::musig::*;
|
||||
use schnorrkel::Schnorrkel;
|
||||
|
||||
use serai_client::{
|
||||
subxt::config::extrinsic_params::BaseExtrinsicParamsBuilder,
|
||||
primitives::{SeraiAddress, insecure_pair_from_name},
|
||||
primitives::insecure_pair_from_name,
|
||||
validator_sets::{
|
||||
primitives::{ValidatorSet, KeyPair},
|
||||
ValidatorSetsEvent,
|
||||
},
|
||||
PairSigner, Serai,
|
||||
Serai,
|
||||
};
|
||||
|
||||
use crate::common::{serai, tx::publish_tx};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn vote_in_keys(set: ValidatorSet, key_pair: KeyPair) -> [u8; 32] {
|
||||
pub async fn set_validator_set_keys(set: ValidatorSet, key_pair: KeyPair) -> [u8; 32] {
|
||||
let pair = insecure_pair_from_name("Alice");
|
||||
let public = pair.public();
|
||||
|
||||
let serai = serai().await;
|
||||
let public_key = <Ristretto as Ciphersuite>::read_G::<&[u8]>(&mut public.0.as_ref()).unwrap();
|
||||
assert_eq!(
|
||||
serai.get_validator_set_musig_key(set).await.unwrap().unwrap(),
|
||||
musig_key::<Ristretto>(&[public_key]).unwrap().to_bytes()
|
||||
);
|
||||
|
||||
let secret_key = <Ristretto as Ciphersuite>::read_F::<&[u8]>(
|
||||
&mut pair.as_ref().secret.to_bytes()[.. 32].as_ref(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(Ristretto::generator() * secret_key, public_key);
|
||||
let threshold_keys = musig::<Ristretto>(&Zeroizing::new(secret_key), &[public_key]).unwrap();
|
||||
assert_eq!(
|
||||
serai.get_validator_set_musig_key(set).await.unwrap().unwrap(),
|
||||
threshold_keys.group_key().to_bytes()
|
||||
);
|
||||
|
||||
let sig = frost::tests::sign_without_caching(
|
||||
&mut OsRng,
|
||||
frost::tests::algorithm_machines(
|
||||
&mut OsRng,
|
||||
Schnorrkel::new(b"substrate"),
|
||||
&HashMap::from([(threshold_keys.params().i(), threshold_keys.into())]),
|
||||
),
|
||||
&key_pair.encode(),
|
||||
);
|
||||
|
||||
// Vote in a key pair
|
||||
let address = SeraiAddress::from(pair.public());
|
||||
let block = publish_tx(
|
||||
&serai
|
||||
.sign(
|
||||
&PairSigner::new(pair),
|
||||
&Serai::vote(set.network, key_pair.clone()),
|
||||
serai.get_nonce(&address).await.unwrap(),
|
||||
BaseExtrinsicParamsBuilder::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
let block = publish_tx(&Serai::set_validator_set_keys(
|
||||
set.network,
|
||||
key_pair.clone(),
|
||||
Signature(sig.to_bytes()),
|
||||
))
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
serai.get_vote_events(block).await.unwrap(),
|
||||
vec![ValidatorSetsEvent::Vote { voter: public, set, key_pair: key_pair.clone(), votes: 1 }]
|
||||
);
|
||||
assert_eq!(
|
||||
serai.get_key_gen_events(block).await.unwrap(),
|
||||
vec![ValidatorSetsEvent::KeyGen { set, key_pair: key_pair.clone() }]
|
||||
|
||||
@@ -2,6 +2,9 @@ use rand_core::{RngCore, OsRng};
|
||||
|
||||
use sp_core::{sr25519::Public, Pair};
|
||||
|
||||
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||
use frost::dkg::musig::musig_key;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{NETWORKS, NetworkId, insecure_pair_from_name},
|
||||
validator_sets::{
|
||||
@@ -12,10 +15,10 @@ use serai_client::{
|
||||
};
|
||||
|
||||
mod common;
|
||||
use common::{serai, validator_sets::vote_in_keys};
|
||||
use common::{serai, validator_sets::set_validator_set_keys};
|
||||
|
||||
serai_test!(
|
||||
async fn vote_keys() {
|
||||
async fn set_validator_set_keys_test() {
|
||||
let network = NetworkId::Bitcoin;
|
||||
let set = ValidatorSet { session: Session(0), network };
|
||||
|
||||
@@ -51,14 +54,20 @@ serai_test!(
|
||||
assert_eq!(set_data.network, NETWORKS[&NetworkId::Bitcoin]);
|
||||
let participants_ref: &[_] = set_data.participants.as_ref();
|
||||
assert_eq!(participants_ref, [(public, set_data.bond)].as_ref());
|
||||
|
||||
let block = vote_in_keys(set, key_pair.clone()).await;
|
||||
|
||||
// While the vote_in_keys function should handle this, it's beneficial to independently test it
|
||||
assert_eq!(
|
||||
serai.get_vote_events(block).await.unwrap(),
|
||||
vec![ValidatorSetsEvent::Vote { voter: public, set, key_pair: key_pair.clone(), votes: 1 }]
|
||||
serai.get_validator_set_musig_key(set).await.unwrap().unwrap(),
|
||||
musig_key::<Ristretto>(&[<Ristretto as Ciphersuite>::read_G::<&[u8]>(
|
||||
&mut public.0.as_ref()
|
||||
)
|
||||
.unwrap()])
|
||||
.unwrap()
|
||||
.to_bytes()
|
||||
);
|
||||
|
||||
let block = set_validator_set_keys(set, key_pair.clone()).await;
|
||||
|
||||
// While the set_validator_set_keys function should handle this, it's beneficial to
|
||||
// independently test it
|
||||
assert_eq!(
|
||||
serai.get_key_gen_events(block).await.unwrap(),
|
||||
vec![ValidatorSetsEvent::KeyGen { set, key_pair: key_pair.clone() }]
|
||||
|
||||
Reference in New Issue
Block a user