Have set_keys take signature_participants, not removed_participants

Now no one is removed from the DKG. Only `t` people publish the key however.

Uses a BitVec for an efficient encoding of the participants.
This commit is contained in:
Luke Parker
2024-08-04 01:14:30 -04:00
parent 5ed355902b
commit 58a435d4e9
8 changed files with 33 additions and 53 deletions

View File

@@ -16,8 +16,10 @@ rustdoc-args = ["--cfg", "docsrs"]
workspace = true workspace = true
[dependencies] [dependencies]
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } bitvec = { version = "1", default-features = false, features = ["alloc", "serde"] }
scale-info = { version = "2", default-features = false, features = ["derive"] }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "bit-vec"] }
scale-info = { version = "2", default-features = false, features = ["derive", "bit-vec"] }
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true } borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true }
serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true } serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true }
@@ -39,6 +41,8 @@ serai-signals-primitives = { path = "../signals/primitives", version = "0.1", de
[features] [features]
std = [ std = [
"bitvec/std",
"scale/std", "scale/std",
"scale-info/std", "scale-info/std",

View File

@@ -11,8 +11,8 @@ use serai_validator_sets_primitives::*;
pub enum Call { pub enum Call {
set_keys { set_keys {
network: NetworkId, network: NetworkId,
removed_participants: BoundedVec<SeraiAddress, ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
key_pair: KeyPair, key_pair: KeyPair,
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
signature: Signature, signature: Signature,
}, },
set_embedded_elliptic_curve_key { set_embedded_elliptic_curve_key {

View File

@@ -20,6 +20,8 @@ workspace = true
zeroize = "^1.5" zeroize = "^1.5"
thiserror = { version = "1", optional = true } thiserror = { version = "1", optional = true }
bitvec = { version = "1", default-features = false, features = ["alloc", "serde"] }
hex = "0.4" hex = "0.4"
scale = { package = "parity-scale-codec", version = "3" } scale = { package = "parity-scale-codec", version = "3" }
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }

View File

@@ -181,17 +181,14 @@ impl<'a> SeraiValidatorSets<'a> {
pub fn set_keys( pub fn set_keys(
network: NetworkId, network: NetworkId,
removed_participants: sp_runtime::BoundedVec<
SeraiAddress,
sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET / 3 }>,
>,
key_pair: KeyPair, key_pair: KeyPair,
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
signature: Signature, signature: Signature,
) -> Transaction { ) -> Transaction {
Serai::unsigned(serai_abi::Call::ValidatorSets(serai_abi::validator_sets::Call::set_keys { Serai::unsigned(serai_abi::Call::ValidatorSets(serai_abi::validator_sets::Call::set_keys {
network, network,
removed_participants,
key_pair, key_pair,
signature_participants,
signature, signature,
})) }))
} }

View File

@@ -103,16 +103,13 @@ impl From<Call> for RuntimeCall {
Call::ValidatorSets(vs) => match vs { Call::ValidatorSets(vs) => match vs {
serai_abi::validator_sets::Call::set_keys { serai_abi::validator_sets::Call::set_keys {
network, network,
removed_participants,
key_pair, key_pair,
signature_participants,
signature, signature,
} => RuntimeCall::ValidatorSets(validator_sets::Call::set_keys { } => RuntimeCall::ValidatorSets(validator_sets::Call::set_keys {
network, network,
removed_participants: <_>::try_from(
removed_participants.into_iter().map(PublicKey::from).collect::<Vec<_>>(),
)
.unwrap(),
key_pair, key_pair,
signature_participants,
signature, signature,
}), }),
serai_abi::validator_sets::Call::set_embedded_elliptic_curve_key { serai_abi::validator_sets::Call::set_embedded_elliptic_curve_key {
@@ -289,14 +286,11 @@ impl TryInto<Call> for RuntimeCall {
_ => Err(())?, _ => Err(())?,
}), }),
RuntimeCall::ValidatorSets(call) => Call::ValidatorSets(match call { RuntimeCall::ValidatorSets(call) => Call::ValidatorSets(match call {
validator_sets::Call::set_keys { network, removed_participants, key_pair, signature } => { validator_sets::Call::set_keys { network, key_pair, signature_participants, signature } => {
serai_abi::validator_sets::Call::set_keys { serai_abi::validator_sets::Call::set_keys {
network, network,
removed_participants: <_>::try_from(
removed_participants.into_iter().map(SeraiAddress::from).collect::<Vec<_>>(),
)
.unwrap(),
key_pair, key_pair,
signature_participants,
signature, signature,
} }
} }

View File

@@ -19,10 +19,11 @@ ignored = ["scale", "scale-info"]
workspace = true workspace = true
[dependencies] [dependencies]
bitvec = { version = "1", default-features = false, features = ["alloc", "serde"] }
hashbrown = { version = "0.14", default-features = false, features = ["ahash", "inline-more"] } hashbrown = { version = "0.14", default-features = false, features = ["ahash", "inline-more"] }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "bit-vec"] }
scale-info = { version = "2", default-features = false, features = ["derive"] } scale-info = { version = "2", default-features = false, features = ["derive", "bit-vec"] }
serde = { version = "1", default-features = false, features = ["derive", "alloc"] } serde = { version = "1", default-features = false, features = ["derive", "alloc"] }
@@ -48,6 +49,8 @@ dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-
[features] [features]
std = [ std = [
"bitvec/std",
"scale/std", "scale/std",
"scale-info/std", "scale-info/std",

View File

@@ -927,14 +927,15 @@ pub mod pallet {
pub fn set_keys( pub fn set_keys(
origin: OriginFor<T>, origin: OriginFor<T>,
network: NetworkId, network: NetworkId,
removed_participants: BoundedVec<Public, ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
key_pair: KeyPair, key_pair: KeyPair,
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
signature: Signature, signature: Signature,
) -> DispatchResult { ) -> DispatchResult {
ensure_none(origin)?; ensure_none(origin)?;
// signature isn't checked as this is an unsigned transaction, and validate_unsigned // signature isn't checked as this is an unsigned transaction, and validate_unsigned
// (called by pre_dispatch) checks it // (called by pre_dispatch) checks it
let _ = signature_participants;
let _ = signature; let _ = signature;
let session = Self::session(network).unwrap(); let session = Self::session(network).unwrap();
@@ -949,15 +950,6 @@ pub mod pallet {
Self::set_total_allocated_stake(network); Self::set_total_allocated_stake(network);
} }
// This does not remove from TotalAllocatedStake or InSet in order to:
// 1) Not decrease the stake present in this set. This means removed participants are
// still liable for the economic security of the external network. This prevents
// a decided set, which is economically secure, from falling below the threshold.
// 2) Not allow parties removed to immediately deallocate, per commentary on deallocation
// scheduling (https://github.com/serai-dex/serai/issues/394).
for removed in removed_participants {
Self::deposit_event(Event::ParticipantRemoved { set, removed });
}
Self::deposit_event(Event::KeyGen { set, key_pair }); Self::deposit_event(Event::KeyGen { set, key_pair });
Ok(()) Ok(())
@@ -1070,7 +1062,7 @@ pub mod pallet {
fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity { fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity {
// Match to be exhaustive // Match to be exhaustive
match call { match call {
Call::set_keys { network, ref removed_participants, ref key_pair, ref signature } => { Call::set_keys { network, ref key_pair, ref signature_participants, ref signature } => {
let network = *network; let network = *network;
// Don't allow the Serai set to set_keys, as they have no reason to do so // Don't allow the Serai set to set_keys, as they have no reason to do so
@@ -1094,30 +1086,24 @@ pub mod pallet {
// session on this assumption // session on this assumption
assert_eq!(Pallet::<T>::latest_decided_session(network), Some(current_session)); assert_eq!(Pallet::<T>::latest_decided_session(network), Some(current_session));
// This does not slash the removed participants as that'll be done at the end of the
// set's lifetime
let mut removed = hashbrown::HashSet::new();
for participant in removed_participants {
// Confirm this wasn't duplicated
if removed.contains(&participant.0) {
Err(InvalidTransaction::Custom(2))?;
}
removed.insert(participant.0);
}
let participants = let participants =
Participants::<T>::get(network).expect("session existed without participants"); Participants::<T>::get(network).expect("session existed without participants");
// Check the bitvec is of the proper length
if participants.len() != signature_participants.len() {
Err(InvalidTransaction::Custom(2))?;
}
let mut all_key_shares = 0; let mut all_key_shares = 0;
let mut signers = vec![]; let mut signers = vec![];
let mut signing_key_shares = 0; let mut signing_key_shares = 0;
for participant in participants { for (participant, in_use) in participants.into_iter().zip(signature_participants) {
let participant = participant.0; let participant = participant.0;
let shares = InSet::<T>::get(network, participant) let shares = InSet::<T>::get(network, participant)
.expect("participant from Participants wasn't InSet"); .expect("participant from Participants wasn't InSet");
all_key_shares += shares; all_key_shares += shares;
if removed.contains(&participant.0) { if !in_use {
continue; continue;
} }
@@ -1135,9 +1121,7 @@ pub mod pallet {
// Verify the signature with the MuSig key of the signers // Verify the signature with the MuSig key of the signers
// We theoretically don't need set_keys_message to bind to removed_participants, as the // We theoretically don't need set_keys_message to bind to removed_participants, as the
// key we're signing with effectively already does so, yet there's no reason not to // key we're signing with effectively already does so, yet there's no reason not to
if !musig_key(set, &signers) if !musig_key(set, &signers).verify(&set_keys_message(&set, key_pair), signature) {
.verify(&set_keys_message(&set, removed_participants, key_pair), signature)
{
Err(InvalidTransaction::BadProof)?; Err(InvalidTransaction::BadProof)?;
} }

View File

@@ -99,12 +99,8 @@ pub fn musig_key(set: ValidatorSet, set_keys: &[Public]) -> Public {
} }
/// The message for the set_keys signature. /// The message for the set_keys signature.
pub fn set_keys_message( pub fn set_keys_message(set: &ValidatorSet, key_pair: &KeyPair) -> Vec<u8> {
set: &ValidatorSet, (b"ValidatorSets-set_keys", set, key_pair).encode()
removed_participants: &[Public],
key_pair: &KeyPair,
) -> Vec<u8> {
(b"ValidatorSets-set_keys", set, removed_participants, key_pair).encode()
} }
pub fn report_slashes_message(set: &ValidatorSet, slashes: &[(Public, u32)]) -> Vec<u8> { pub fn report_slashes_message(set: &ValidatorSet, slashes: &[(Public, u32)]) -> Vec<u8> {