From 1dc61fb17d97e2afcd445fda95b84fff1797b9fd Mon Sep 17 00:00:00 2001 From: Uncle Stretch Date: Sat, 10 Oct 2026 15:48:50 +0300 Subject: [PATCH] fix GJKR arbitration, avoid invalid kicks, and synchronize round 5 output Signed-off-by: Uncle Stretch --- pallets/exodus/Cargo.toml | 2 +- pallets/exodus/src/benchmarking.rs | 26 +++-- pallets/exodus/src/lib.rs | 136 +++++++++++++++++++------- pallets/exodus/src/tests.rs | 75 +++++++++++++- pallets/exodus/src/types/consensus.rs | 24 +++++ pallets/exodus/src/types/packages.rs | 47 +++++---- 6 files changed, 246 insertions(+), 64 deletions(-) diff --git a/pallets/exodus/Cargo.toml b/pallets/exodus/Cargo.toml index 0901ceb..4c56a27 100644 --- a/pallets/exodus/Cargo.toml +++ b/pallets/exodus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-exodus" -version = "0.0.24" +version = "0.0.25" description = "Threshold signature generation with DKG included" license.workspace = true authors.workspace = true diff --git a/pallets/exodus/src/benchmarking.rs b/pallets/exodus/src/benchmarking.rs index 4bf65a8..437bd1a 100644 --- a/pallets/exodus/src/benchmarking.rs +++ b/pallets/exodus/src/benchmarking.rs @@ -344,12 +344,17 @@ mod benchmarks { let dummy_merkle_root = ExodusHash::repeat_byte(69); let dummy_verifying_key = vec![69u8; network_curve.element_bytes_len()]; + let dummy_key_members = vec![15]; let round5_package = PackageContext::default() .with_network_curve(NetworkCurve::Secp256k1) .with_authority_index(authority_index) .with_dkg_index(dkg_index) - .with_dkg_round5(dummy_verifying_key.clone(), dummy_merkle_root) + .with_dkg_round5( + dummy_verifying_key.clone(), + dummy_key_members.clone(), + dummy_merkle_root, + ) .expect("Round5 package should be valid"); let dkg_package = DkgPackage::Round5(round5_package); @@ -366,7 +371,11 @@ mod benchmarks { dummy_verifying_key, ).expect("Bounded verifying key should be correct"); - let metadata_hashes = [dummy_merkle_root.as_ref(), bounded_verifying_key.as_ref()]; + let metadata_hashes = [ + dummy_merkle_root.as_ref(), + bounded_verifying_key.as_ref(), + dummy_key_members.as_ref(), + ]; let verifying_hash = cumulative_hash::(metadata_hashes); let verification_key = (network_curve, dkg_index, verifying_hash); @@ -374,10 +383,11 @@ mod benchmarks { assert!(participants.contains(authority_index)); let consensus = VerifyingKeyConsensus::::get(&network_curve, dkg_index); - assert!(consensus.participants.contains(authority_index)); - assert_eq!(consensus.participants.count_ones::(), 1); - assert_eq!(consensus.merkle_root, dummy_merkle_root); - assert_eq!(consensus.element_bytes, bounded_verifying_key); + assert!(consensus.state.participants.contains(authority_index)); + assert_eq!(consensus.state.participants.count_ones::(), 1); + assert_eq!(consensus.state.merkle_root, dummy_merkle_root); + assert_eq!(consensus.state.element_bytes, bounded_verifying_key); + assert_eq!(consensus.members.as_ref(), dummy_key_members); Ok(()) } @@ -444,8 +454,8 @@ mod benchmarks { }); VerifyingKeyConsensus::::mutate(&network_curve, &dkg_index, |consensus| { - consensus.participants.insert(authority_index); - consensus.merkle_root = dummy_merkle_root; + consensus.state.participants.insert(authority_index); + consensus.state.merkle_root = dummy_merkle_root; }); let round6_package = PackageContext::default() diff --git a/pallets/exodus/src/lib.rs b/pallets/exodus/src/lib.rs index 22d8c95..b051fef 100644 --- a/pallets/exodus/src/lib.rs +++ b/pallets/exodus/src/lib.rs @@ -411,7 +411,7 @@ pub mod pallet { type MaxAuthorities: Get; #[pallet::constant] - type MaxAuthoritiesChunks: Get; + type MaxAuthoritiesChunks: Get + core::fmt::Debug; #[pallet::constant] type DkgRoundPeriod: Get; @@ -499,6 +499,7 @@ pub mod pallet { BridgeOverflow, TooManyPackages, WrongNetworkType, + MemberNotQualified, InvalidMerkleProof, NetworkDoesNotExist, NoActiveAuthorities, @@ -730,7 +731,10 @@ pub mod pallet { _, Twox64Concat, NetworkCurve, Twox64Concat, DkgIndex, - ConsensusState>, + QualifiedConsensusState< + ParticipantsBitmap, + BoundedVec> + >, ValueQuery, >; @@ -1230,12 +1234,16 @@ pub mod pallet { return Err(Error::::Round5PackageInvalidProof.into()); }; - let metadata_hashes = [round5.merkle_root.as_ref(), round5.verifying_key.as_ref()]; - let verifying_hash = cumulative_hash::(metadata_hashes); - let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_fifth_phase(), Error::::DkgWrongRound); + let metadata_hashes = [ + round5.merkle_root.as_ref(), + round5.verifying_key.as_ref(), + round5.key_members.as_ref(), + ]; + let verifying_hash = cumulative_hash::(metadata_hashes); + let dkg_index = state.get_dkg_index(); let verification_key = (network_curve, dkg_index, verifying_hash); @@ -1257,15 +1265,16 @@ pub mod pallet { Ok(bitmap.clone()) })?; - VerifyingKeyConsensus::::mutate(&network_curve, &dkg_index, |state| { - let state_participants_count = state.participants.count_ones::(); + VerifyingKeyConsensus::::mutate(&network_curve, &dkg_index, |current| { + let state_participants_count = current.state.participants.count_ones::(); let latest_participants_count = latest_participants.count_ones::(); if state_participants_count < latest_participants_count { - *state = ConsensusState::new( + *current = QualifiedConsensusState::new( round5.verifying_key, latest_participants, round5.merkle_root, + round5.key_members, ); } }); @@ -1314,12 +1323,12 @@ pub mod pallet { ensure!(state.is_sixth_phase(), Error::::DkgWrongRound); let dkg_index = state.get_dkg_index(); - let consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); + let qualified_consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); network_curve.verify_merkle_proof( &round6.verifying_share, &round6.merkle_proof, - consensus.merkle_root, + qualified_consensus.state.merkle_root, authority_index, ).map_err(|_| Error::::InvalidMerkleProof)?; @@ -2513,10 +2522,13 @@ impl Pallet { }, |weight| { weight.saturating_accrue(T::DbWeight::get().reads(1)); - let state = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); + let consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); - if state.participants.count_ones::() < min_threshold { + if consensus.state.participants.count_ones::() < min_threshold { dkg_qualification.nullify_indexes(); + } else { + let key_members = ParticipantsBitmap::::from_bitmask(&consensus.members); + dkg_qualification.intersect_indexes(&key_members); } }) } @@ -2548,7 +2560,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(1)); let consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); - let verifying_key_slice = consensus.element_bytes.as_slice(); + let verifying_key_slice = consensus.state.element_bytes.as_slice(); if verifying_key_slice.len() != network_curve.element_bytes_len() { dkg_qualification.nullify_indexes(); @@ -2587,7 +2599,7 @@ impl Pallet { CurrentExodus::::put(exodus_session); weight.saturating_accrue(T::DbWeight::get().writes(1)); - dkg_qualification.set_verifying_key(consensus.element_bytes); + dkg_qualification.set_verifying_key(consensus.state.element_bytes); }) } @@ -2979,6 +2991,10 @@ impl Pallet { dkg_package: &DkgPackage, signature: &::Signature, ) -> Result<(), InvalidTransaction> { + let DkgPackage::Round5(round5) = dkg_package else { + return Err(InvalidTransaction::BadProof); + }; + let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); let package_dkg_index = dkg_package.get_dkg_index(); @@ -3009,6 +3025,22 @@ impl Pallet { return Err(InvalidTransaction::BadProof); } + let has_non_qualified_member = round5.key_members + .iter() + .enumerate() + .any(|(byte_idx, &byte)| { + if byte == 0 { return false; } + (0..8).any(|bit_idx| { + if (byte & (1u8 << bit_idx)) == 0 { return false; } + let global_index = byte_idx * 8 + bit_idx; + !state.contains_index(global_index) + }) + }); + + if has_non_qualified_member { + return Err(InvalidTransaction::BadProof); + } + let authority = QualificationAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() @@ -4328,7 +4360,7 @@ impl Pallet { let verification_found_for_self = VerifyingKeyConsensus::::iter_values() - .any(|state| state.participants.contains(authority_index)); + .any(|consensus| consensus.state.participants.contains(authority_index)); if verification_found_for_self { return Ok(ExodusOk::DkgRoundAlreadyPassed( @@ -4415,6 +4447,30 @@ impl Pallet { DkgStorageEnum::Round5 { secret_share, merkle_proof } )?; + let padded_participants = qualification_state + .highest_bit::() + .map(|bit_pos| bit_pos.saturating_add(1)) + .unwrap_or_default(); + + let bitmask_len = padded_participants.div_ceil(8); + + let mut key_members_bitmask = vec![0u8; bitmask_len]; + + for &member_index in round1_packages.keys() { + let global_index = member_index as usize; + let byte_index = global_index >> 3usize; + let bit_index = global_index & 7usize; + + if let Some(byte) = key_members_bitmask.get_mut(byte_index) { + *byte |= 1u8 << bit_index; + } + } + + let self_global_index = authority_index as usize; + if let Some(byte) = key_members_bitmask.get_mut(self_global_index >> 3) { + *byte |= 1 << (self_global_index & 7); + } + let current_dkg_index = QualificationDkgState::::get(&network_curve) .get_dkg_index(); @@ -4423,7 +4479,11 @@ impl Pallet { current_dkg_index, &mut round_metadata, |context| { - let package = context.with_dkg_round5(verifying_key, merkle_root)?; + let package = context.with_dkg_round5( + verifying_key, + key_members_bitmask, + merkle_root, + )?; Ok(DkgPackage::Round5(package)) }, |dkg_package, signature| { @@ -4573,32 +4633,40 @@ impl Pallet { let round2_package_size = blob_len.saturating_div(padded_participants); + let mut kicked = sp_std::collections::btree_set::BTreeSet::new(); + let mut all_justifications = sp_std::collections::btree_map::BTreeMap::new(); + for (&accuser_index, accused_bitmap) in Complaints::::get(network_curve).iter() { - if !qualification_state.contains_index(accuser_index) { continue; } - if accuser_index == authority_index { continue; } - - let raw_round2_packages: BTreeMap> = - Self::gather_incoming_offchain_packages( - network_curve, - accuser_index, - ROUND_NUMBER_4, - &accused_bitmap, - round2_package_size, - |mut raw_slice| Vec::::decode(&mut raw_slice).ok(), - ); - - let mut kicked = sp_std::collections::btree_set::BTreeSet::new(); + if !qualification_state.contains_index(accuser_index) { + round1_packages.remove(&accuser_index); + round2_packages.remove(&accuser_index); + continue; + } for accused_index in accused_bitmap.iter::() { if !qualification_state.contains_index(accused_index) { continue; } if kicked.contains(&accused_index) { continue; } if accused_index == authority_index { continue; } + let justifications = all_justifications.entry(accused_index) + .or_insert_with(|| { + let key = Self::create_offchain_dkg_key(network_curve, accused_index, ROUND_NUMBER_4); + StorageValueRef::persistent(&key).get::().ok().flatten() + }); + + let maybe_justification = justifications.as_ref().and_then(|bundle| { + Self::locate_package_bounds(bundle, round2_package_size, accuser_index) + .and_then(|(start, end)| { + bundle.blob.get(start..end).and_then(|raw_slice| { + Vec::::decode(&mut &raw_slice[..]).ok() + }) + }) + }); + let maybe_round1_package = round1_packages.get(&accused_index); - let maybe_round2_package = raw_round2_packages.get(&accused_index); let (round1_package, round2_package) = - match (maybe_round1_package, maybe_round2_package) { + match (maybe_round1_package, maybe_justification) { (Some(round1_package), Some(round2_package)) => { (round1_package, round2_package) }, @@ -4613,13 +4681,13 @@ impl Pallet { match network_curve.dkg_verify_private_package( accuser_index, round1_package, - round2_package, + &round2_package, ) { Ok(_) => { if accuser_index != authority_index { continue; } round2_packages.insert( accused_index, - round2_package.clone(), + round2_package, ); }, Err(_) => { diff --git a/pallets/exodus/src/tests.rs b/pallets/exodus/src/tests.rs index 25ee7b6..4c5a99e 100644 --- a/pallets/exodus/src/tests.rs +++ b/pallets/exodus/src/tests.rs @@ -603,7 +603,27 @@ fn run_succesfull_dkg_session_partially( assert_eq!(prev_dkg_index + 1, dkg_index); ext.execute_with(|| { - let dummy_oversized_bitmap = ParticipantsBitmap::::all_ones(1337); + let max_authorities_len = raw_authorities.len(); + + let max_authority_index = raw_authorities.iter().max().cloned().unwrap_or_default() as usize; + let padded_participants = max_authority_index.saturating_add(1); + let bitmask_len = padded_participants.div_ceil(8); + + let mut key_members_raw = vec![0u8; bitmask_len]; + + for &authority in raw_authorities.iter() { + let global_index = authority as usize; + let byte_index = global_index >> 3; + let bit_index = global_index & 7; + if let Some(byte) = key_members_raw.get_mut(byte_index) { + *byte |= 1 << bit_index; + } + } + + let dummy_key_members: BoundedVec> = + key_members_raw.try_into().unwrap(); + + let dummy_oversized_bitmap = ParticipantsBitmap::::all_ones(max_authorities_len); let mut map: BitmapByAuthority = Default::default(); for authority in raw_authorities.iter() { @@ -625,10 +645,11 @@ fn run_succesfull_dkg_session_partially( vec![69u8; ELEMENT_MAX_BYTES as usize], ).unwrap(); - let dummy_consensus_state = ConsensusState::new( + let dummy_consensus_state = QualifiedConsensusState::new( dummy_verifying_key, dummy_oversized_bitmap, DUMMY_HASH, + dummy_key_members, ); VerifyingKeyConsensus::::insert(curve, prev_dkg_index, dummy_consensus_state); @@ -1021,6 +1042,56 @@ fn run_dkg_session( assert_ne!(prev_active_authorities, active_authorities); } +#[test] +fn test_exodus_dkg_distributed_synced_exclusion() { + let (mut ext, tx_pool_state) = new_test_ext(); + let curve = NetworkCurve::Secp256k1; + + let honest_validators = vec![0, 1, 2, 4, 5, 6, 7, 8, 9]; + let malicious_validator = 3; + let all_validators = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + let authorities: Vec = all_validators.iter().map(|&id| id.into()).collect(); + ext.execute_with(|| Exodus::start_dkg_qualification(authorities, NetworkCurve::iter())); + + run_to_next_round(&mut ext, curve); + for &authority in &all_validators { + assert_ok!(execute_round0_happy_path(&mut ext, &tx_pool_state, curve, authority)); + } + + run_to_next_round(&mut ext, curve); + for &authority in &all_validators { + assert_ok!(execute_round1_happy_path(&mut ext, &tx_pool_state, curve, authority)); + } + + let remaining_rounds = [ + execute_round2_happy_path, + execute_round3_happy_path, + execute_round4_happy_path, + execute_round5_happy_path, + execute_round6_happy_path, + ]; + + for dkg_round in remaining_rounds { + run_to_next_round(&mut ext, curve); + for &authority in &honest_validators { + assert_ok!(dkg_round(&mut ext, &tx_pool_state, curve, authority)); + } + } + + run_to_next_round(&mut ext, curve); + + ext.execute_with(|| { + let state = QualificationDkgState::::get(curve); + assert_eq!(state.get_phase(), DkgPhase::Round7); + assert!(!state.contains_index(malicious_validator)); + + for &authority in &honest_validators { + assert!(state.contains_index(authority)); + } + }); +} + #[test] fn test_all_validators_can_execute_initial_dkg() { let curve = NetworkCurve::Secp256k1; diff --git a/pallets/exodus/src/types/consensus.rs b/pallets/exodus/src/types/consensus.rs index 53161e6..66d01cd 100644 --- a/pallets/exodus/src/types/consensus.rs +++ b/pallets/exodus/src/types/consensus.rs @@ -6,6 +6,30 @@ use frame_support::{BoundedVec, traits::ConstU32}; use crate::{ExodusHash, ELEMENT_MAX_BYTES}; +#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct QualifiedConsensusState { + pub state: ConsensusState, + pub members: Bitmask, +} + +impl QualifiedConsensusState { + pub fn new( + element_bytes: BoundedVec>, + participants: Bitmap, + merkle_root: ExodusHash, + members: Bitmask, + ) -> Self { + Self { + members, + state: ConsensusState::new( + element_bytes, + participants, + merkle_root, + ) + } + } +} + #[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct ConsensusState { pub element_bytes: BoundedVec>, diff --git a/pallets/exodus/src/types/packages.rs b/pallets/exodus/src/types/packages.rs index 23e7fb0..72914b3 100644 --- a/pallets/exodus/src/types/packages.rs +++ b/pallets/exodus/src/types/packages.rs @@ -75,7 +75,7 @@ where Round2(Round2Package), Round3(Round3Package), Round4(Round4Package), - Round5(Round5Package), + Round5(Round5Package), Round6(Round6Package), } @@ -101,7 +101,7 @@ where } } -#[derive(Default, RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(Default, RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] pub struct PackageContext { authority_index: AuthIndex, network_curve: NetworkCurve, @@ -140,13 +140,13 @@ where } } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] pub struct BundledPackages { pub blob: Vec, pub bitmask: Vec, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct EncryptionBundledPackages where @@ -180,7 +180,7 @@ impl TryFrom for EncryptionBundledPac } } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct JustificationsBundledPackages where @@ -214,7 +214,7 @@ impl TryFrom for JustificationsBundle } } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct ExodusNonceCommitment { context: PackageContext, @@ -223,7 +223,7 @@ pub struct ExodusNonceCommitment { pub session: ExodusSession, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] pub struct ExodusGroupCommitment { context: PackageContext, pub group_commitment: BoundedVec>, @@ -231,7 +231,7 @@ pub struct ExodusGroupCommitment { pub session: ExodusSession, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct ExodusSignatureShare where @@ -244,13 +244,13 @@ where pub session: ExodusSession, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] pub struct Round0Package { context: PackageContext, pub package_hash: ExodusHash, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct Round1Package where @@ -260,7 +260,7 @@ where pub package: BoundedVec>, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct Round2Package where @@ -270,7 +270,7 @@ where pub bundle: EncryptionBundledPackages, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct Round3Package where @@ -280,7 +280,7 @@ where pub indices: BoundedVec>, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct Round4Package where @@ -290,14 +290,19 @@ where pub bundle: JustificationsBundledPackages, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] -pub struct Round5Package { +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct Round5Package +where + T: crate::pallet::Config, +{ context: PackageContext, + pub key_members: BoundedVec>, pub verifying_key: BoundedVec>, pub merkle_root: ExodusHash, } -#[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct Round6Package where @@ -417,16 +422,20 @@ impl PackageContext { Ok(Round4Package { context: self, bundle }) } - pub fn with_dkg_round5( + pub fn with_dkg_round5( self, verifying_key_vec: Vec, + key_members_vec: Vec, merkle_root: ExodusHash, - ) -> Result, ExodusError> { + ) -> Result, ExodusError> { + let key_members = BoundedVec::>::try_from(key_members_vec) + .map_err(|_| ExodusError::DeserializationError)?; + let verifying_key = BoundedVec::>::try_from(verifying_key_vec) .map_err(|_| ExodusError::DeserializationError)?; - Ok(Round5Package { context: self, verifying_key, merkle_root }) + Ok(Round5Package { context: self, key_members, verifying_key, merkle_root }) } pub fn with_dkg_round6(