refactor package storage to use classic bitmap and dense data blob

Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
Uncle Stretch 2026-10-26 21:02:20 +03:00
parent c22925d758
commit d18b8ba11f
Signed by: str3tch
GPG Key ID: 84F3190747EE79AA
11 changed files with 523 additions and 617 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-exodus" name = "ghost-exodus"
version = "0.1.3" version = "0.1.4"
description = "Threshold signature generation with DKG included" description = "Threshold signature generation with DKG included"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true

View File

@ -125,9 +125,9 @@ mod benchmarks {
#[benchmark] #[benchmark]
fn register_encrypted_round2_packages( fn register_encrypted_round2_packages(
c: Linear<1, { T::MaxAuthorities::get() / 8 }> c: Linear<1, { T::MaxAuthorities::get() }>
) -> Result<(), BenchmarkError> { ) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex; let max_signers = c as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) = let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize); prepare_pallet::<T>(max_signers as usize);
@ -149,21 +149,18 @@ mod benchmarks {
QualificationDkgState::<T>::insert(&network_curve, dkg_state); QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitmask_len = padded_participants.div_ceil(8);
let blob_len = round2_encrypted_package_size( let blob_len = round2_encrypted_package_size(
padded_participants, padded_participants - 1,
network_curve.scalar_bytes_len(), network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(), network_curve.header_bytes_len(),
); );
let mut bitmask = vec![0u8; bitmask_len]; let mut bitmask = ParticipantsBitmap::<T>::empty(padded_participants);
let blob = vec![0u8; blob_len]; let blob = vec![0u8; blob_len];
(0..max_signers).for_each(|i| { (0..max_signers).for_each(|i| {
if i != authority_index { if i != authority_index {
let byte_index = (i / 8) as usize; bitmask.insert(i);
let bit_index = (i % 8) as u8;
bitmask[byte_index] |= 1 << bit_index;
} }
}); });
@ -195,9 +192,9 @@ mod benchmarks {
#[benchmark] #[benchmark]
fn register_round3_complaints( fn register_round3_complaints(
c: Linear<1, { T::MaxAuthorities::get() / 8 }> c: Linear<1, { T::MaxAuthorities::get() }>
) -> Result<(), BenchmarkError> { ) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex; let max_signers = c as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) = let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize); prepare_pallet::<T>(max_signers as usize);
@ -215,14 +212,11 @@ mod benchmarks {
QualificationDkgState::<T>::insert(&network_curve, dkg_state); QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitvec_len = (max_signers + 7) / 8; let mut accused_indexes = ParticipantsBitmap::<T>::empty(max_signers);
let mut accused_indexes = vec![0u8; bitvec_len as usize];
(0..max_signers).for_each(|i| { (0..max_signers).for_each(|i| {
if i != authority_index { if i != authority_index {
let byte_index = (i / 8) as usize; accused_indexes.insert(i);
let bit_index = (i % 8) as u8;
accused_indexes[byte_index] |= 1 << bit_index;
} }
}); });
@ -255,9 +249,9 @@ mod benchmarks {
#[benchmark] #[benchmark]
fn register_round4_justifications( fn register_round4_justifications(
c: Linear<1, { T::MaxAuthorities::get() / 8 }> c: Linear<1, { T::MaxAuthorities::get() }>
) -> Result<(), BenchmarkError> { ) -> Result<(), BenchmarkError> {
let max_signers = (c * 8) as AuthIndex; let max_signers = c as AuthIndex;
let (mut dkg_state, network_curve, authority, authority_index) = let (mut dkg_state, network_curve, authority, authority_index) =
prepare_pallet::<T>(max_signers as usize); prepare_pallet::<T>(max_signers as usize);
@ -281,21 +275,18 @@ mod benchmarks {
QualificationDkgState::<T>::insert(&network_curve, dkg_state); QualificationDkgState::<T>::insert(&network_curve, dkg_state);
let bitmask_len = padded_participants.div_ceil(8);
let blob_len = round2_package_size( let blob_len = round2_package_size(
padded_participants, padded_participants - 1,
network_curve.scalar_bytes_len(), network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(), network_curve.header_bytes_len(),
); );
let mut bitmask = vec![0u8; bitmask_len]; let mut bitmask = ParticipantsBitmap::<T>::empty(padded_participants);
let blob = vec![0u8; blob_len]; let blob = vec![0u8; blob_len];
(0..max_signers).for_each(|i| { (0..max_signers).for_each(|i| {
if i != authority_index { if i != authority_index {
let byte_index = (i / 8) as usize; bitmask.insert(i);
let bit_index = (i % 8) as u8;
bitmask[byte_index] |= 1 << bit_index;
} }
}); });
@ -352,7 +343,7 @@ mod benchmarks {
let dummy_merkle_root = ExodusHash::repeat_byte(69); let dummy_merkle_root = ExodusHash::repeat_byte(69);
let dummy_verifying_key = vec![69u8; network_curve.element_bytes_len()]; let dummy_verifying_key = vec![69u8; network_curve.element_bytes_len()];
let dummy_key_members = vec![15]; let dummy_key_members = ParticipantsBitmap::<T>::empty(4);
let current_block = <T as Config>::BlockNumberProvider::current_block_number(); let current_block = <T as Config>::BlockNumberProvider::current_block_number();
let round5_package = PackageContext::default() let round5_package = PackageContext::default()
@ -384,7 +375,7 @@ mod benchmarks {
let metadata_hashes = [ let metadata_hashes = [
dummy_merkle_root.as_ref(), dummy_merkle_root.as_ref(),
bounded_verifying_key.as_ref(), bounded_verifying_key.as_ref(),
dummy_key_members.as_ref(), dummy_key_members.inner_as_ref(),
]; ];
let verifying_hash = cumulative_hash::<SubstrateBlake2Hasher, _>(metadata_hashes); let verifying_hash = cumulative_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
@ -397,7 +388,7 @@ mod benchmarks {
assert_eq!(consensus.state.participants.count_ones::<AuthIndex>(), 1); assert_eq!(consensus.state.participants.count_ones::<AuthIndex>(), 1);
assert_eq!(consensus.state.merkle_root, dummy_merkle_root); assert_eq!(consensus.state.merkle_root, dummy_merkle_root);
assert_eq!(consensus.state.element_bytes, bounded_verifying_key); assert_eq!(consensus.state.element_bytes, bounded_verifying_key);
assert_eq!(consensus.members.as_ref(), dummy_key_members); assert_eq!(consensus.members, dummy_key_members);
Ok(()) Ok(())
} }

View File

@ -92,7 +92,7 @@ pub mod sr25519 {
} }
const MIN_LOCK_GUARD_PERIOD: u64 = 15_000; const MIN_LOCK_GUARD_PERIOD: u64 = 15_000;
const BLOCKS_FOR_DKG_RESET: u64 = 5; const BLOCKS_FOR_DKG_RESET : u64 = 5;
const DB_STORAGE_PREFIX : &[u8] = b"exodus::"; const DB_STORAGE_PREFIX : &[u8] = b"exodus::";
const PALLET_LOG_TARGET : &str = "time:ghost-exodus"; const PALLET_LOG_TARGET : &str = "time:ghost-exodus";
@ -160,10 +160,7 @@ const fn encrypted_ciphertext_bytes_len(
header_bytes_len: usize, header_bytes_len: usize,
) -> usize { ) -> usize {
let ciphertext_len = header_bytes_len.saturating_add(scalar_bytes_len); let ciphertext_len = header_bytes_len.saturating_add(scalar_bytes_len);
let ciphertext_vec_prefix = postcard_varint_prefix_len(ciphertext_len); ciphertext_len.saturating_add(AUTHENTICATION_TAG_MAX_BYTES as usize)
ciphertext_len.saturating_add(ciphertext_vec_prefix)
.saturating_add(AUTHENTICATION_TAG_MAX_BYTES as usize)
} }
const fn round2_encrypted_package_size( const fn round2_encrypted_package_size(
@ -177,11 +174,13 @@ const fn round2_encrypted_package_size(
header_bytes_len, header_bytes_len,
); );
let ciphertext_len = encrypted_ciphertext_bytes_len let ciphertext_vec_prefix = scale_compact_prefix_len(
.saturating_add(ENCRYPTION_NONCE_MAX_BYTES as usize); encrypted_ciphertext_bytes_len
);
let ciphertext_vec_prefix = scale_compact_prefix_len(ciphertext_len); let size = encrypted_ciphertext_bytes_len
let size = ciphertext_len.saturating_add(ciphertext_vec_prefix); .saturating_add(ciphertext_vec_prefix)
.saturating_add(ENCRYPTION_NONCE_MAX_BYTES as usize);
participants.saturating_mul(size) participants.saturating_mul(size)
} }
@ -198,22 +197,6 @@ const fn round2_package_size(
participants.saturating_mul(size) participants.saturating_mul(size)
} }
const fn nonce_commitment_package_size(
element_max_bytes: usize,
header_max_bytes: usize,
) -> usize {
header_max_bytes
.saturating_add(element_max_bytes)
.saturating_add(element_max_bytes)
}
const fn signature_share_bytes_len(
scalar_bytes_len: usize,
header_bytes_len: usize,
) -> usize {
header_bytes_len.saturating_add(scalar_bytes_len)
}
type DkgIndex = ghost_helpers::DkgIndexU32; type DkgIndex = ghost_helpers::DkgIndexU32;
type AuthIndex = ghost_helpers::AuthIndexU16; type AuthIndex = ghost_helpers::AuthIndexU16;
type BitmapChunk = ghost_helpers::BitmapChunkU32; type BitmapChunk = ghost_helpers::BitmapChunkU32;
@ -261,14 +244,6 @@ impl<T: Config> Get<u32> for MaxEvmRotationSessionsSize<T> {
} }
} }
pub struct MaxAuthoritiesBitmaskSize<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for MaxAuthoritiesBitmaskSize<T> {
fn get() -> u32 {
let max_authorities = T::MaxAuthorities::get();
max_authorities.div_ceil(8)
}
}
pub struct Round1MaxBytes<T>(sp_std::marker::PhantomData<T>); pub struct Round1MaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for Round1MaxBytes<T> { impl<T: Config> Get<u32> for Round1MaxBytes<T> {
fn get() -> u32 { fn get() -> u32 {
@ -311,14 +286,6 @@ impl<T: Config> Get<u32> for EncryptedRound2BlobMaxBytes<T> {
} }
} }
pub struct AccusedIndicesMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for AccusedIndicesMaxBytes<T> {
fn get() -> u32 {
let max_authorities = T::MaxAuthorities::get();
max_authorities.saturating_add(7).div_ceil(8)
}
}
pub struct MerkleProofMaxSize<T>(sp_std::marker::PhantomData<T>); pub struct MerkleProofMaxSize<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for MerkleProofMaxSize<T> { impl<T: Config> Get<u32> for MerkleProofMaxSize<T> {
fn get() -> u32 { fn get() -> u32 {
@ -341,16 +308,6 @@ impl<T: Config> Get<u32> for BindingFactorsProof<T> {
} }
} }
pub struct NonceCommitmentMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for NonceCommitmentMaxBytes<T> {
fn get() -> u32 {
nonce_commitment_package_size(
ELEMENT_MAX_BYTES as usize,
HEADER_MAX_BYTES as usize,
) as u32
}
}
pub struct CiphertextMaxBytes<T>(sp_std::marker::PhantomData<T>); pub struct CiphertextMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for CiphertextMaxBytes<T> { impl<T: Config> Get<u32> for CiphertextMaxBytes<T> {
fn get() -> u32 { fn get() -> u32 {
@ -361,24 +318,6 @@ impl<T: Config> Get<u32> for CiphertextMaxBytes<T> {
} }
} }
pub struct SignatureShareMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for SignatureShareMaxBytes<T> {
fn get() -> u32 {
signature_share_bytes_len(
SCALAR_MAX_BYTES as usize,
HEADER_MAX_BYTES as usize,
) as u32
}
}
pub struct SignatureMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for SignatureMaxBytes<T> {
fn get() -> u32 {
let signature_max_bytes = ELEMENT_MAX_BYTES + SCALAR_MAX_BYTES;
postcard_varint_prefix_len(signature_max_bytes as usize) as u32
}
}
pub struct ZeroScalarDefault<T>(sp_std::marker::PhantomData<T>); pub struct ZeroScalarDefault<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<BoundedVec<u8, ConstU32<{ SCALAR_MAX_BYTES }>>> for ZeroScalarDefault<T> { impl<T: Config> Get<BoundedVec<u8, ConstU32<{ SCALAR_MAX_BYTES }>>> for ZeroScalarDefault<T> {
fn get() -> BoundedVec<u8, sp_core::ConstU32<{ SCALAR_MAX_BYTES }>> { fn get() -> BoundedVec<u8, sp_core::ConstU32<{ SCALAR_MAX_BYTES }>> {
@ -756,10 +695,7 @@ pub mod pallet {
_, _,
Twox64Concat, NetworkCurve, Twox64Concat, NetworkCurve,
Twox64Concat, DkgIndex, Twox64Concat, DkgIndex,
QualifiedConsensusState< QualifiedConsensusState<ParticipantsBitmap<T>>,
ParticipantsBitmap<T>,
BoundedVec<u8, MaxAuthoritiesBitmaskSize<T>>
>,
ValueQuery, ValueQuery,
>; >;
@ -1090,19 +1026,6 @@ pub mod pallet {
let state = QualificationDkgState::<T>::get(&network_curve); let state = QualificationDkgState::<T>::get(&network_curve);
ensure!(state.is_second_phase(), Error::<T>::DkgWrongRound); ensure!(state.is_second_phase(), Error::<T>::DkgWrongRound);
let encrypted_indexes = ParticipantsBitmap::<T>::from_bitmask(
&round2_bundle.bundle.bitmask,
);
let diff = state.get_indexes() ^ &encrypted_indexes;
let only_one_lost = diff.count_ones::<AuthIndex>() == 1;
let only_authority_index = diff.contains(authority_index);
ensure!(
only_one_lost && only_authority_index,
Error::<T>::Round2PackagesWrongCoefficients,
);
Round2Packages::<T>::try_mutate(&network_curve, Round2Packages::<T>::try_mutate(&network_curve,
|round2_packages| -> DispatchResult { |round2_packages| -> DispatchResult {
ensure!( ensure!(
@ -1115,8 +1038,11 @@ pub mod pallet {
Ok(()) Ok(())
})?; })?;
let encrypted_count = round2_bundle.bundle.bitmask
.count_ones::<AuthIndex>();
Self::deposit_event(Event::<T>::Round2PackagesRegistered { Self::deposit_event(Event::<T>::Round2PackagesRegistered {
encrypted_count: encrypted_indexes.count_ones::<AuthIndex>(), encrypted_count,
authority_index, authority_index,
network_curve, network_curve,
}); });
@ -1160,11 +1086,7 @@ pub mod pallet {
return Err(Error::<T>::Round3PackageInvalidProof.into()); return Err(Error::<T>::Round3PackageInvalidProof.into());
}; };
let complaints_bitmap = ParticipantsBitmap::<T>::from_bitmask( let complaints_count: AuthIndex = round3_complaints.indices.count_ones();
&round3_complaints.indices
);
let complaints_count: AuthIndex = complaints_bitmap.count_ones();
let state = QualificationDkgState::<T>::get(&network_curve); let state = QualificationDkgState::<T>::get(&network_curve);
ensure!(state.is_third_phase(), Error::<T>::DkgWrongRound); ensure!(state.is_third_phase(), Error::<T>::DkgWrongRound);
@ -1176,7 +1098,7 @@ pub mod pallet {
Error::<T>::Round3PackagesAlreadyRegistered, Error::<T>::Round3PackagesAlreadyRegistered,
); );
complaints.try_insert(authority_index, complaints_bitmap) complaints.try_insert(authority_index, round3_complaints.indices)
.map_err(|_| Error::<T>::TooManyPackages)?; .map_err(|_| Error::<T>::TooManyPackages)?;
Ok(()) Ok(())
@ -1216,14 +1138,12 @@ pub mod pallet {
return Err(Error::<T>::Round4PackageInvalidProof.into()); return Err(Error::<T>::Round4PackageInvalidProof.into());
}; };
let justifications_bitmap = ParticipantsBitmap::<T>::from_bitmask( let justifications_count = round4_bundle.bundle.bitmask.count_ones();
&round4_bundle.bundle.bitmask
);
let justifications_count: AuthIndex = justifications_bitmap.count_ones();
let state = QualificationDkgState::<T>::get(&network_curve); let state = QualificationDkgState::<T>::get(&network_curve);
ensure!(state.is_fourth_phase(), Error::<T>::DkgWrongRound); ensure!(state.is_fourth_phase(), Error::<T>::DkgWrongRound);
let encoded_bundle = round4_bundle.bundle.encode();
Justifications::<T>::try_mutate( Justifications::<T>::try_mutate(
&network_curve, &network_curve,
|justifications| -> DispatchResult { |justifications| -> DispatchResult {
@ -1232,8 +1152,10 @@ pub mod pallet {
Error::<T>::Round4PackagesAlreadyRegistered, Error::<T>::Round4PackagesAlreadyRegistered,
); );
justifications.try_insert(authority_index, justifications_bitmap) justifications.try_insert(
.map_err(|_| Error::<T>::TooManyPackages)?; authority_index,
round4_bundle.bundle.bitmask
).map_err(|_| Error::<T>::TooManyPackages)?;
Ok(()) Ok(())
})?; })?;
@ -1252,7 +1174,7 @@ pub mod pallet {
sp_io::offchain_index::set( sp_io::offchain_index::set(
&index_key, &index_key,
&round4_bundle.bundle.encode(), &encoded_bundle
); );
Ok(()) Ok(())
@ -1287,7 +1209,7 @@ pub mod pallet {
let metadata_hashes = [ let metadata_hashes = [
round5.merkle_root.as_ref(), round5.merkle_root.as_ref(),
round5.verifying_key.as_ref(), round5.verifying_key.as_ref(),
round5.key_members.as_ref(), round5.key_members.inner_as_ref(),
]; ];
let verifying_hash = cumulative_hash::<SubstrateBlake2Hasher, _>(metadata_hashes); let verifying_hash = cumulative_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
@ -1360,17 +1282,12 @@ pub mod pallet {
return Err(Error::<T>::Round6PackageInvalidProof.into()); return Err(Error::<T>::Round6PackageInvalidProof.into());
}; };
let expected_share_len = network_curve.element_bytes_len();
ensure!(
round6.verifying_share.len() == expected_share_len,
Error::<T>::Round6PackageInvalidProof,
);
let state = QualificationDkgState::<T>::get(&network_curve); let state = QualificationDkgState::<T>::get(&network_curve);
ensure!(state.is_sixth_phase(), Error::<T>::DkgWrongRound); ensure!(state.is_sixth_phase(), Error::<T>::DkgWrongRound);
let dkg_index = state.get_dkg_index(); let dkg_index = state.get_dkg_index();
let qualified_consensus = VerifyingKeyConsensus::<T>::get(&network_curve, &dkg_index); let qualified_consensus =
VerifyingKeyConsensus::<T>::get(&network_curve, &dkg_index);
network_curve.verify_merkle_proof( network_curve.verify_merkle_proof(
&round6.verifying_share, &round6.verifying_share,
@ -2581,8 +2498,7 @@ impl<T: Config> Pallet<T> {
if consensus.state.participants.count_ones::<AuthIndex>() < min_threshold { if consensus.state.participants.count_ones::<AuthIndex>() < min_threshold {
dkg_qualification.nullify_indexes(); dkg_qualification.nullify_indexes();
} else { } else {
let key_members = ParticipantsBitmap::<T>::from_bitmask(&consensus.members); dkg_qualification.intersect_indexes(&consensus.members);
dkg_qualification.intersect_indexes(&key_members);
} }
}) })
} }
@ -2962,39 +2878,24 @@ impl<T: Config> Pallet<T> {
return Err(InvalidTransaction::BadSigner); return Err(InvalidTransaction::BadSigner);
} }
let bundle_packages_count = round2.bundle.bitmask let bitmask_count = round2.bundle.bitmask.count_ones::<usize>();
.iter() let count_without_self = state.count_ones::<usize>().saturating_sub(1);
.map(|byte| byte.count_ones() as usize)
.sum::<usize>()
.saturating_add(1);
if bundle_packages_count != state.count_ones::<usize>() { // exclude self from the count; the first coefficient is never sent
if count_without_self != bitmask_count {
return Err(InvalidTransaction::BadProof); return Err(InvalidTransaction::BadProof);
} }
let has_invalid_round2_recipients = round2.bundle.bitmask let diff = state.get_indexes() ^ &round2.bundle.bitmask;
.iter() let only_one_lost = diff.count_ones::<AuthIndex>() == 1;
.enumerate() let only_authority_index = diff.contains(authority_index);
.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;
if global_index == authority_index as usize { return true; }
!state.contains_index(global_index)
})
});
if has_invalid_round2_recipients { if !only_one_lost || !only_authority_index {
return Err(InvalidTransaction::BadProof); return Err(InvalidTransaction::BadProof);
} }
let padded_participants = state.highest_bit::<usize>()
.map(|bit_pos| bit_pos.saturating_add(1))
.unwrap_or_default();
let expected_blob_len = round2_encrypted_package_size( let expected_blob_len = round2_encrypted_package_size(
padded_participants, count_without_self,
network_curve.scalar_bytes_len(), network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(), network_curve.header_bytes_len(),
); );
@ -3056,29 +2957,20 @@ impl<T: Config> Pallet<T> {
return Err(InvalidTransaction::BadSigner); return Err(InvalidTransaction::BadSigner);
} }
let estimated_indices_len = state let qualification_highest_bit = state.highest_bit::<usize>().unwrap_or(0);
let indices_highest_bit = round3_complaints.indices
.highest_bit::<usize>() .highest_bit::<usize>()
.map(|bit_index| bit_index.saturating_add(1).div_ceil(8)) .unwrap_or(0);
.unwrap_or_default();
if estimated_indices_len != round3_complaints.indices.len() { if qualification_highest_bit < indices_highest_bit {
return Err(InvalidTransaction::BadProof); return Err(InvalidTransaction::BadProof);
} }
let has_invalid_accused_indexes = round3_complaints.indices let contains_self = round3_complaints.indices.contains(authority_index);
.iter() let extra_bits = !state.get_indexes() & &round3_complaints.indices;
.enumerate() let has_extra_bits = extra_bits.count_ones::<AuthIndex>() > 0;
.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;
if global_index == authority_index as usize { return true; }
!state.contains_index(global_index)
})
});
if has_invalid_accused_indexes { if has_extra_bits || contains_self {
return Err(InvalidTransaction::BadProof); return Err(InvalidTransaction::BadProof);
} }
@ -3136,39 +3028,26 @@ impl<T: Config> Pallet<T> {
return Err(InvalidTransaction::BadSigner); return Err(InvalidTransaction::BadSigner);
} }
let bundle_packages_count = round4.bundle.bitmask
.iter()
.map(|byte| byte.count_ones() as usize)
.sum::<usize>()
.saturating_add(1);
if bundle_packages_count > state.count_ones::<usize>() { let qualification_highest_bit = state.highest_bit::<usize>().unwrap_or(0);
let bitmask_highest_bit = round4.bundle.bitmask
.highest_bit::<usize>()
.unwrap_or(0);
if qualification_highest_bit < bitmask_highest_bit {
return Err(InvalidTransaction::BadProof); return Err(InvalidTransaction::BadProof);
} }
let has_invalid_justifications = round4.bundle.bitmask let contains_self = round4.bundle.bitmask.contains(authority_index);
.iter() let extra_bits = !state.get_indexes() & &round4.bundle.bitmask;
.enumerate() let has_extra_bits = extra_bits.count_ones::<AuthIndex>() > 0;
.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;
if global_index == authority_index as usize { return true; }
!state.contains_index(global_index)
})
});
if has_invalid_justifications { if has_extra_bits || contains_self {
return Err(InvalidTransaction::BadProof); return Err(InvalidTransaction::BadProof);
} }
let padded_participants = state.highest_bit::<usize>()
.map(|bit_pos| bit_pos.saturating_add(1))
.unwrap_or_default();
let expected_blob_len = round2_package_size( let expected_blob_len = round2_package_size(
padded_participants, round4.bundle.bitmask.count_ones::<usize>(),
network_curve.scalar_bytes_len(), network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(), network_curve.header_bytes_len(),
); );
@ -3230,27 +3109,12 @@ impl<T: Config> Pallet<T> {
return Err(InvalidTransaction::BadSigner); return Err(InvalidTransaction::BadSigner);
} }
let estimated_max_size = network_curve let extra_bits = !state.get_indexes() & &round5.key_members;
.element_bytes_len() let has_extra_bits = extra_bits.count_ones::<usize>() > 0;
.saturating_add(ExodusHash::len_bytes()); let verifying_key_valid =
round5.verifying_key.len() == network_curve.element_bytes_len();
if dkg_package.bytes_len() as usize != estimated_max_size { if !verifying_key_valid || has_extra_bits {
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); return Err(InvalidTransaction::BadProof);
} }
@ -3281,6 +3145,10 @@ impl<T: Config> Pallet<T> {
dkg_package: &DkgPackage<NetworkCurve, BlockNumberFor<T>, T>, dkg_package: &DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature, signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
) -> Result<(), InvalidTransaction> { ) -> Result<(), InvalidTransaction> {
let DkgPackage::Round6(round6) = dkg_package else {
return Err(InvalidTransaction::BadProof);
};
let network_curve = dkg_package.get_network_curve(); let network_curve = dkg_package.get_network_curve();
let authority_index = dkg_package.get_authority_index(); let authority_index = dkg_package.get_authority_index();
let package_dkg_index = dkg_package.get_dkg_index(); let package_dkg_index = dkg_package.get_dkg_index();
@ -3303,12 +3171,16 @@ impl<T: Config> Pallet<T> {
return Err(InvalidTransaction::BadSigner); return Err(InvalidTransaction::BadSigner);
} }
if round6.verifying_share.len() != network_curve.element_bytes_len() {
return Err(InvalidTransaction::BadProof);
}
let estimated_proof_count = state let estimated_proof_count = state
.count_ones::<usize>() .count_ones::<usize>()
.next_power_of_two() .next_power_of_two()
.trailing_zeros(); .trailing_zeros();
if dkg_package.bytes_len() != estimated_proof_count { if round6.merkle_proof.len() != estimated_proof_count as usize {
return Err(InvalidTransaction::BadProof); return Err(InvalidTransaction::BadProof);
} }
@ -4405,24 +4277,11 @@ impl<T: Config> Pallet<T> {
.map(|bit_pos| bit_pos.saturating_add(1)) .map(|bit_pos| bit_pos.saturating_add(1))
.unwrap_or_default(); .unwrap_or_default();
let bitmask_len = padded_participants.div_ceil(8); let (bitmask, blob) = Self::ecdh_encrypt_round2_packages(
let blob_len = round2_encrypted_package_size(
padded_participants,
network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(),
);
let max_package_size = blob_len.saturating_div(padded_participants);
let mut bitmask = vec![0u8; bitmask_len];
let mut blob = vec![0u8; blob_len];
Self::ecdh_encrypt_round2_packages(
&round1_packages, &round1_packages,
&public_packages, &public_packages,
round1_secret_package, round1_secret_package,
&mut bitmask, padded_participants,
&mut blob,
max_package_size,
authority_index, authority_index,
qualification_state.get_dkg_index(), qualification_state.get_dkg_index(),
network_curve, network_curve,
@ -4595,36 +4454,30 @@ impl<T: Config> Pallet<T> {
.map(|bit_pos| bit_pos.saturating_add(1)) .map(|bit_pos| bit_pos.saturating_add(1))
.unwrap_or_default(); .unwrap_or_default();
let bitmask_len = padded_participants.div_ceil(8);
let blob_len = round2_package_size( let blob_len = round2_package_size(
padded_participants, padded_participants,
network_curve.scalar_bytes_len(), network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(), network_curve.header_bytes_len(),
); );
let round2_package_size = blob_len.saturating_div(padded_participants); let mut bitmask = ParticipantsBitmap::<T>::empty(padded_participants);
let mut blob = Vec::with_capacity(blob_len);
let mut bitmask = vec![0u8; bitmask_len]; Complaints::<T>::get(&network_curve)
let mut blob = vec![0u8; blob_len]; .iter()
.filter(|(&index, _)| qualification_state.contains_index(index))
.filter(|(&index, _)| authority_index != index)
.filter(|(_, bitmap)| bitmap.contains(authority_index))
.filter_map(|(accuser_index, _)| {
public_packages.get(accuser_index).map(|pkg| (accuser_index, pkg))
})
.for_each(|(&accuser_index, round2_package)| {
let encoded = round2_package.encode();
for (&accuser_index, bitmap) in Complaints::<T>::get(&network_curve).iter() { if let Some(_) = bitmask.insert(accuser_index) {
if !bitmap.contains(authority_index) { continue; } blob.extend_from_slice(&encoded);
if !qualification_state.contains_index(accuser_index) { continue; }
if authority_index == accuser_index { continue; }
let round2_package = match public_packages.get(&accuser_index) {
Some(round2_package) => round2_package,
None => continue,
};
Self::prepare_bundled_data(
&mut bitmask,
&mut blob,
&round2_package.encode(),
round2_package_size,
accuser_index,
);
} }
});
round4_metadata.update_with_block( round4_metadata.update_with_block(
Some(dkg_storage.get_release_block()), Some(dkg_storage.get_release_block()),
@ -4762,23 +4615,13 @@ impl<T: Config> Pallet<T> {
.map(|bit_pos| bit_pos.saturating_add(1)) .map(|bit_pos| bit_pos.saturating_add(1))
.unwrap_or_default(); .unwrap_or_default();
let bitmask_len = padded_participants.div_ceil(8); let mut key_members_bitmask = ParticipantsBitmap::<T>::empty(
padded_participants
let mut key_members_bitmask = vec![0u8; bitmask_len]; );
key_members_bitmask.insert(authority_index);
for &member_index in round1_packages.keys() { for &member_index in round1_packages.keys() {
let global_index = member_index as usize; key_members_bitmask.insert(member_index);
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 = qualification_state.get_dkg_index(); let current_dkg_index = qualification_state.get_dkg_index();
@ -4969,29 +4812,29 @@ impl<T: Config> Pallet<T> {
} }
for accused_index in accused_bitmap.iter::<AuthIndex>() { for accused_index in accused_bitmap.iter::<AuthIndex>() {
if !qualification_state.contains_index(accused_index) { continue; } if !qualification_state.contains_index(accused_index)
if kicked.contains(&accused_index) { continue; } || kicked.contains(&accused_index)
if accused_index == authority_index { continue; } || accused_index == authority_index
{
let justifications = all_justifications.entry(accused_index) continue;
.or_insert_with(|| { }
let key = Self::create_offchain_dkg_key(network_curve, accused_index, ROUND_NUMBER_4);
StorageValueRef::persistent(&key).get::<BundledPackages>().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::<u8>::decode(&mut &raw_slice[..]).ok()
})
})
});
let maybe_round1_package = round1_packages.get(&accused_index); let maybe_round1_package = round1_packages.get(&accused_index);
let maybe_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::<JustificationsBundledPackages<T>>().ok().flatten()
})
.as_ref()
.and_then(|bundle| {
bundle.get_package_by_index::<Vec<u8>>(
accuser_index,
round2_package_size
)
});
let (round1_package, round2_package) = let (round1_package, round2_package) =
match (maybe_round1_package, maybe_justification) { match (maybe_round1_package, maybe_justifications) {
(Some(round1_package), Some(round2_package)) => { (Some(round1_package), Some(round2_package)) => {
(round1_package, round2_package) (round1_package, round2_package)
}, },
@ -5019,7 +4862,6 @@ impl<T: Config> Pallet<T> {
round1_packages.remove(&accused_index); round1_packages.remove(&accused_index);
round2_packages.remove(&accused_index); round2_packages.remove(&accused_index);
kicked.insert(accused_index); kicked.insert(accused_index);
continue;
} }
} }
} }
@ -5027,26 +4869,17 @@ impl<T: Config> Pallet<T> {
} }
pub fn locate_package_bounds( pub fn locate_package_bounds(
bundle: &BundledPackages, bundle: &BundledPackages<T>,
single_bytes_len: usize, single_bytes_len: usize,
authority_index: AuthIndex, authority_index: AuthIndex,
) -> Option<(usize, usize)> { ) -> Option<(usize, usize)> {
let global_index = authority_index as usize; let popcount = bundle.bitmask.count_ones_before(authority_index)?;
let byte_index = global_index >> 3usize; let popcount: usize = popcount.unique_saturated_into();
let bit_index = global_index & 7usize;
let bit_mask = 1u8 << bit_index;
let bundle_bitmask_by_index = bundle.bitmask.get(byte_index)?; let start_offset = popcount.checked_mul(single_bytes_len)?;
if (bundle_bitmask_by_index & bit_mask) == 0 { let end_offset = start_offset.checked_add(single_bytes_len)?;
return None;
}
let start_offset = global_index * single_bytes_len; if end_offset > bundle.blob.len() { return None; }
let end_offset = start_offset + single_bytes_len;
if end_offset > bundle.blob.len() {
return None;
}
Some((start_offset, end_offset)) Some((start_offset, end_offset))
} }
@ -5088,17 +4921,15 @@ impl<T: Config> Pallet<T> {
fn ecdh_decrypt_round2_packages( fn ecdh_decrypt_round2_packages(
qualification_state: &PendingDkgAuthorities<T>, qualification_state: &PendingDkgAuthorities<T>,
round1_packages: &BTreeMap<AuthIndex, Vec<u8>>, round1_packages: &BTreeMap<AuthIndex, Vec<u8>>,
round1_secret_package: &Vec<u8>, round1_secret_package: &[u8],
authority_index: AuthIndex, authority_index: AuthIndex,
network_curve: NetworkCurve, network_curve: NetworkCurve,
) -> (Vec<u8>, BTreeMap<AuthIndex, Vec<u8>>) { ) -> (ParticipantsBitmap<T>, BTreeMap<AuthIndex, Vec<u8>>) {
let dkg_index = qualification_state.get_dkg_index(); let dkg_index = qualification_state.get_dkg_index();
let bitmask_len = qualification_state let len = qualification_state
.highest_bit::<usize>() .highest_bit::<usize>()
.map(|bit_pos| { .map(|bit_pos| bit_pos.saturating_add(1))
bit_pos.saturating_add(1).div_ceil(8)
})
.unwrap_or_default(); .unwrap_or_default();
let encryption_size = round2_encrypted_package_size( let encryption_size = round2_encrypted_package_size(
@ -5107,25 +4938,24 @@ impl<T: Config> Pallet<T> {
network_curve.header_bytes_len(), network_curve.header_bytes_len(),
); );
let mut accused_indexes = ParticipantsBitmap::<T>::empty(len);
let mut decrypted = BTreeMap::new();
round1_packages round1_packages
.iter() .iter()
.filter(|(&sender_index, _)| sender_index != authority_index) .filter(|(&sender_index, _)| sender_index != authority_index)
.map(|(&sender_index, round1_sender_package)| { .for_each(|(&sender_index, round1_sender_package)| {
let key = Self::create_offchain_dkg_key(network_curve, sender_index, ROUND_NUMBER_2); let key = Self::create_offchain_dkg_key(
network_curve,
sender_index,
ROUND_NUMBER_2
);
let decryption_result = StorageValueRef::persistent(&key) let decryption_result = StorageValueRef::persistent(&key)
.get::<BundledPackages>() .get::<EncryptionBundledPackages<T>>()
.ok() .ok()
.flatten() .flatten()
.and_then(|bundle| { .and_then(|bundle| bundle.get_package_by_index(authority_index, encryption_size))
Self::locate_package_bounds(&bundle, encryption_size, authority_index)
.and_then(|(start, end)| {
bundle.blob.get(start..end).and_then(|raw_slice| {
EncryptionData::decode(&mut &raw_slice[..]).ok()
})
})
})
.ok_or(sender_index)
.and_then(|encrypted_round2_package: EncryptionData<T>| { .and_then(|encrypted_round2_package: EncryptionData<T>| {
let info = network_curve.ecdh_prepare_additional_info( let info = network_curve.ecdh_prepare_additional_info(
sender_index, sender_index,
@ -5134,64 +4964,54 @@ impl<T: Config> Pallet<T> {
sp_std::marker::PhantomData::<EncryptionData<T>>, sp_std::marker::PhantomData::<EncryptionData<T>>,
); );
network_curve let cipher = network_curve.ecdh_get_cipher_from_keys(
.ecdh_get_cipher_from_keys(
round1_sender_package, round1_sender_package,
round1_secret_package, round1_secret_package,
&info, &info,
sp_std::marker::PhantomData::<EncryptionData<T>>, sp_std::marker::PhantomData::<EncryptionData<T>>,
) ).ok()?;
.and_then(|cipher| {
network_curve.ecdh_decrypt_package( let decrypted = network_curve.ecdh_decrypt_package(
&cipher, &cipher,
&encrypted_round2_package, &encrypted_round2_package,
&info, &info,
) ).ok()?;
})
.and_then(|decrypted| {
network_curve.dkg_verify_private_package( network_curve.dkg_verify_private_package(
authority_index, authority_index,
round1_sender_package, round1_sender_package,
&decrypted, &decrypted,
).map(|_| decrypted) ).map(|_| decrypted).ok()
}) });
.map_err(|_| sender_index)
})
.map(|decrypted_package| (sender_index, decrypted_package));
decryption_result
})
.fold(
(vec![0u8; bitmask_len], BTreeMap::new()),
|(mut accused_indexes, mut decrypted), decryption_result| {
match decryption_result { match decryption_result {
Ok((sender_index, package)) => { decrypted.insert(sender_index, package); }, Some(package) => { decrypted.insert(sender_index, package); },
Err(sender_index) => { None => { accused_indexes.insert(sender_index); },
let global_index = sender_index as usize; }
let byte_index = global_index >> 3usize; });
if let Some(byte) = accused_indexes.get_mut(byte_index) {
let bit_index = global_index & 7usize;
*byte |= 1u8 << bit_index;
}
},
}
(accused_indexes, decrypted) (accused_indexes, decrypted)
} }
)
}
fn ecdh_encrypt_round2_packages( fn ecdh_encrypt_round2_packages(
round1_packages: &BTreeMap<AuthIndex, Vec<u8>>, round1_packages: &BTreeMap<AuthIndex, Vec<u8>>,
round2_packages: &BTreeMap<AuthIndex, Vec<u8>>, round2_packages: &BTreeMap<AuthIndex, Vec<u8>>,
round1_secret_package: &[u8], round1_secret_package: &[u8],
bitmask: &mut Vec<u8>, padded_participants: usize,
blob: &mut Vec<u8>,
max_size: usize,
authority_index: AuthIndex, authority_index: AuthIndex,
dkg_index: DkgIndex, dkg_index: DkgIndex,
network_curve: NetworkCurve, network_curve: NetworkCurve,
) { ) -> (ParticipantsBitmap<T>, Vec<u8>) {
let blob_len = round2_encrypted_package_size(
padded_participants,
network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(),
);
let max_size = blob_len.saturating_div(padded_participants);
let mut bitmask = ParticipantsBitmap::<T>::empty(padded_participants);
let mut blob = Vec::with_capacity(blob_len);
let authority_index_bytes = authority_index.to_le_bytes(); let authority_index_bytes = authority_index.to_le_bytes();
let dkg_index_bytes = dkg_index.to_le_bytes(); let dkg_index_bytes = dkg_index.to_le_bytes();
let network_curve_bytes = [network_curve as u8]; let network_curve_bytes = [network_curve as u8];
@ -5207,10 +5027,9 @@ impl<T: Config> Pallet<T> {
for (&receiver_index, round2_package) in round2_packages.iter() { for (&receiver_index, round2_package) in round2_packages.iter() {
if receiver_index == authority_index { continue; } if receiver_index == authority_index { continue; }
let round1_receiver_package = match round1_packages let Some(round1_receiver_package) = round1_packages
.get(&receiver_index) { .get(&receiver_index) else {
Some(package) => package, continue;
None => continue,
}; };
let info = network_curve.ecdh_prepare_additional_info( let info = network_curve.ecdh_prepare_additional_info(
@ -5220,57 +5039,38 @@ impl<T: Config> Pallet<T> {
sp_std::marker::PhantomData::<EncryptionData<T>>, sp_std::marker::PhantomData::<EncryptionData<T>>,
); );
let encrypted_data: EncryptionData<T> = match network_curve let maybe_encoded_bytes = network_curve
.ecdh_get_cipher_from_keys( .ecdh_get_cipher_from_keys(
&round1_receiver_package, &round1_receiver_package,
&round1_secret_package, &round1_secret_package,
&info, &info,
sp_std::marker::PhantomData::<EncryptionData<T>>, sp_std::marker::PhantomData::<EncryptionData<T>>,
) )
.and_then(|cipher| network_curve.ecdh_encrypt_package( .and_then(|cipher| {
network_curve.ecdh_encrypt_package(
&cipher, &cipher,
&info, &info,
&round2_package, &round2_package,
&mut rng, &mut rng
)) { )
Ok(encrypted_data) => encrypted_data, })
Err(_) => break, .map(|data: EncryptionData<T>| data.encode())
}; .ok()
.filter(|b| b.len() == max_size);
Self::prepare_bundled_data( match maybe_encoded_bytes {
bitmask, Some(encoded) => {
blob, if let Some(_) = bitmask.insert(receiver_index) {
&encrypted_data.encode(), blob.extend_from_slice(&encoded);
max_size, }
receiver_index, },
); None => {
blob.extend_from_slice(&vec![0u8; max_size]);
}
} }
} }
fn prepare_bundled_data( (bitmask, blob)
bitmask: &mut Vec<u8>,
blob: &mut Vec<u8>,
encoded_bytes: &[u8],
max_size: usize,
authority_index: AuthIndex,
) {
let global_index = authority_index as usize;
let byte_index = global_index >> 3usize;
let bit_index = global_index & 7usize;
if let Some(byte) = bitmask.get_mut(byte_index) {
if encoded_bytes.len() > max_size { return; }
let start_offset = global_index.saturating_mul(max_size);
let end_offset = start_offset.saturating_add(max_size);
if end_offset > blob.len() { return; }
let target_slice = &mut blob[start_offset..end_offset];
target_slice[..encoded_bytes.len()].copy_from_slice(&encoded_bytes);
*byte |= 1 << bit_index;
}
} }
fn get_offchain_rng(context: &[&[u8]]) -> ChaCha20Rng { fn get_offchain_rng(context: &[&[u8]]) -> ChaCha20Rng {

View File

@ -41,7 +41,7 @@ impl frame_system::Config for Runtime {
} }
parameter_types! { parameter_types! {
pub static FixedAuthorities: Vec<u64> = vec![0, 1, 2, 3]; pub static FixedAuthorities: Vec<u64> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
} }
pub struct TestSessionManager; pub struct TestSessionManager;
@ -137,10 +137,10 @@ impl ghost_exodus::Config for Runtime {
type UnsignedPriority = ConstU64<{ 1 << 20 }>; type UnsignedPriority = ConstU64<{ 1 << 20 }>;
type UnsignedLongevity = ConstU64<5>; type UnsignedLongevity = ConstU64<5>;
type MaxAuthorities = ConstU32<10>; type MaxAuthorities = ConstU32<15>;
type MaxAuthoritiesChunks = ConstU32<1>; type MaxAuthoritiesChunks = ConstU32<2>;
type DkgRoundPeriod = ConstU64<300>; type DkgRoundPeriod = ConstU64<1000>;
type RemovalLimit = ConstU32<20>; type RemovalLimit = ConstU32<20>;
type MaxCursorLen = ConstU32<256>; type MaxCursorLen = ConstU32<256>;
type BaseEvmAmount = ConstU128<5>; type BaseEvmAmount = ConstU128<5>;

View File

@ -543,9 +543,7 @@ fn run_succesfull_dkg_session_partially(
} }
} }
let dummy_key_members: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<Runtime>> = let dummy_key_members = ParticipantsBitmap::<Runtime>::empty(padded_participants);
key_members_raw.try_into().unwrap();
let dummy_oversized_bitmap = ParticipantsBitmap::<Runtime>::all_ones(max_authorities_len); let dummy_oversized_bitmap = ParticipantsBitmap::<Runtime>::all_ones(max_authorities_len);
let mut map: BitmapByAuthority<Runtime> = Default::default(); let mut map: BitmapByAuthority<Runtime> = Default::default();
@ -767,9 +765,13 @@ fn test_dkg_robustness_against_malformed_scale_slots() {
let (mut ext, tx_pool_state) = new_test_ext(); let (mut ext, tx_pool_state) = new_test_ext();
let curve = NetworkCurve::Secp256k1; let curve = NetworkCurve::Secp256k1;
let honest_validators = vec![0, 1, 2, 4, 5, 6, 7, 8, 9]; let malicious_validator = 3u64;
let malicious_validator = 3; let all_validators = FixedAuthorities::get();
let all_validators = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; let honest_validators = all_validators
.iter()
.copied()
.filter(|&x| x != malicious_validator)
.collect::<Vec<_>>();
let authorities: Vec<UintAuthorityId> = all_validators.iter().map(|&id| id.into()).collect(); let authorities: Vec<UintAuthorityId> = all_validators.iter().map(|&id| id.into()).collect();
ext.execute_with(|| Exodus::start_dkg_qualification(authorities, NetworkCurve::iter())); ext.execute_with(|| Exodus::start_dkg_qualification(authorities, NetworkCurve::iter()));
@ -791,18 +793,15 @@ fn test_dkg_robustness_against_malformed_scale_slots() {
ext.execute_with(|| { ext.execute_with(|| {
let dkg_index = QualificationDkgState::<Runtime>::get(curve).get_dkg_index(); let dkg_index = QualificationDkgState::<Runtime>::get(curve).get_dkg_index();
let padded_participants = all_validators.len();
let padded_participants = 10usize; let mut perfect_bitmask = ParticipantsBitmap::<Runtime>::empty(padded_participants);
let bitmask_len = padded_participants.div_ceil(8);
let mut perfect_bitmask = vec![0u8; bitmask_len];
for &recipient in &honest_validators { for &recipient in &honest_validators {
let idx = recipient as usize; perfect_bitmask.insert(recipient);
perfect_bitmask[idx >> 3] |= 1 << (idx & 7);
} }
let expected_blob_len = round2_encrypted_package_size( let expected_blob_len = round2_encrypted_package_size(
padded_participants, padded_participants - 1,
curve.scalar_bytes_len(), curve.scalar_bytes_len(),
curve.header_bytes_len(), curve.header_bytes_len(),
); );
@ -825,7 +824,6 @@ fn test_dkg_robustness_against_malformed_scale_slots() {
.unwrap(); .unwrap();
let dummy_package = DkgPackage::Round2(dummy_context); let dummy_package = DkgPackage::Round2(dummy_context);
let malicious_signer: UintAuthorityId = malicious_validator.into(); let malicious_signer: UintAuthorityId = malicious_validator.into();
let dummy_signature = malicious_signer.sign(&dummy_package.encode()).unwrap(); let dummy_signature = malicious_signer.sign(&dummy_package.encode()).unwrap();
@ -863,9 +861,13 @@ fn test_exodus_dkg_distributed_synced_exclusion() {
let (mut ext, tx_pool_state) = new_test_ext(); let (mut ext, tx_pool_state) = new_test_ext();
let curve = NetworkCurve::Secp256k1; let curve = NetworkCurve::Secp256k1;
let honest_validators = vec![0, 1, 2, 4, 5, 6, 7, 8, 9]; let malicious_validator = 3u64;
let malicious_validator = 3; let all_validators = FixedAuthorities::get();
let all_validators = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; let honest_validators = all_validators
.iter()
.copied()
.filter(|&x| x != malicious_validator)
.collect::<Vec<_>>();
let authorities: Vec<UintAuthorityId> = all_validators.iter().map(|&id| id.into()).collect(); let authorities: Vec<UintAuthorityId> = all_validators.iter().map(|&id| id.into()).collect();
ext.execute_with(|| Exodus::start_dkg_qualification(authorities, NetworkCurve::iter())); ext.execute_with(|| Exodus::start_dkg_qualification(authorities, NetworkCurve::iter()));
@ -908,6 +910,63 @@ fn test_exodus_dkg_distributed_synced_exclusion() {
}); });
} }
#[test]
fn test_accused_authority_survives_dkg_via_justification() {
let (mut ext, tx_pool_state) = new_test_ext();
let curve = NetworkCurve::Secp256k1;
let reporter_index = 5;
let malicious_index = 3;
let raw_authorities = FixedAuthorities::get();
let authorities: Vec<UintAuthorityId> = raw_authorities.iter().map(|&id| id.into()).collect();
let authorities_len = authorities.len();
ext.execute_with(|| Exodus::start_dkg_qualification(authorities, NetworkCurve::iter()));
let dkg_rounds = [
execute_round0_happy_path,
execute_round1_happy_path,
execute_round2_happy_path,
execute_round3_happy_path,
];
for dkg_round in dkg_rounds {
run_to_next_round(&mut ext, curve);
for authority in raw_authorities.iter() {
assert_ok!(dkg_round(&mut ext, &tx_pool_state, curve, *authority));
}
}
ext.execute_with(|| {
let mut complaints = ParticipantsBitmap::<Runtime>::empty(authorities_len);
complaints.insert(malicious_index);
Complaints::<Runtime>::mutate(curve, |complaints_map| {
complaints_map.try_insert(reporter_index, complaints).unwrap();
});
});
let remaining_rounds = [
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 raw_authorities.iter() {
assert_ok!(dkg_round(&mut ext, &tx_pool_state, curve, *authority));
}
}
run_to_next_round(&mut ext, curve);
let state = ext.execute_with(|| QualificationDkgState::<Runtime>::get(curve));
assert_eq!(state.get_phase(), DkgPhase::Round7);
assert_eq!(state.count_ones::<usize>(), raw_authorities.len());
}
#[test] #[test]
fn test_all_validators_can_execute_initial_dkg() { fn test_all_validators_can_execute_initial_dkg() {
let curve = NetworkCurve::Secp256k1; let curve = NetworkCurve::Secp256k1;
@ -1302,6 +1361,11 @@ fn test_prepare_round5_kick_on_empty_justifications() {
let auth: Vec<AuthIndex> = raw_authorities.iter().map(|&id| id as AuthIndex).collect(); let auth: Vec<AuthIndex> = raw_authorities.iter().map(|&id| id as AuthIndex).collect();
for &curr_auth in auth.iter() {
complaints_map.try_insert(curr_auth, ParticipantsBitmap::<Runtime>::empty(authorities_len)).unwrap();
justifications_map.try_insert(curr_auth, ParticipantsBitmap::<Runtime>::empty(authorities_len)).unwrap();
}
let mut complaints_from_1 = ParticipantsBitmap::<Runtime>::empty(authorities_len); let mut complaints_from_1 = ParticipantsBitmap::<Runtime>::empty(authorities_len);
complaints_from_1.insert(auth[0]); complaints_from_1.insert(auth[0]);
complaints_from_1.insert(auth[3]); complaints_from_1.insert(auth[3]);
@ -1311,9 +1375,6 @@ fn test_prepare_round5_kick_on_empty_justifications() {
complaints_from_2.insert(auth[0]); complaints_from_2.insert(auth[0]);
complaints_map.try_insert(auth[2], complaints_from_2).unwrap(); complaints_map.try_insert(auth[2], complaints_from_2).unwrap();
complaints_map.try_insert(auth[0], ParticipantsBitmap::<Runtime>::empty(authorities_len)).unwrap();
complaints_map.try_insert(auth[3], ParticipantsBitmap::<Runtime>::empty(authorities_len)).unwrap();
let mut justifications_from_0 = ParticipantsBitmap::<Runtime>::empty(authorities_len); let mut justifications_from_0 = ParticipantsBitmap::<Runtime>::empty(authorities_len);
justifications_from_0.insert(auth[1]); justifications_from_0.insert(auth[1]);
justifications_from_0.insert(auth[2]); justifications_from_0.insert(auth[2]);
@ -1323,9 +1384,6 @@ fn test_prepare_round5_kick_on_empty_justifications() {
justifications_from_3.insert(auth[0]); justifications_from_3.insert(auth[0]);
justifications_map.try_insert(auth[3], justifications_from_3).unwrap(); justifications_map.try_insert(auth[3], justifications_from_3).unwrap();
justifications_map.try_insert(auth[1], ParticipantsBitmap::<Runtime>::empty(authorities_len)).unwrap();
justifications_map.try_insert(auth[2], ParticipantsBitmap::<Runtime>::empty(authorities_len)).unwrap();
Complaints::<Runtime>::insert(curve, complaints_map); Complaints::<Runtime>::insert(curve, complaints_map);
Justifications::<Runtime>::insert(curve, justifications_map); Justifications::<Runtime>::insert(curve, justifications_map);
}); });

View File

@ -7,17 +7,17 @@ use frame_support::{BoundedVec, traits::ConstU32};
use crate::{ExodusHash, ELEMENT_MAX_BYTES}; use crate::{ExodusHash, ELEMENT_MAX_BYTES};
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] #[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct QualifiedConsensusState<Bitmap, Bitmask> { pub struct QualifiedConsensusState<Bitmap> {
pub state: ConsensusState<Bitmap>, pub state: ConsensusState<Bitmap>,
pub members: Bitmask, pub members: Bitmap,
} }
impl<Bitmap, Bitmask> QualifiedConsensusState<Bitmap, Bitmask> { impl<Bitmap> QualifiedConsensusState<Bitmap> {
pub fn new( pub fn new(
element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>, element_bytes: BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
participants: Bitmap, participants: Bitmap,
merkle_root: ExodusHash, merkle_root: ExodusHash,
members: Bitmask, members: Bitmap,
) -> Self { ) -> Self {
Self { Self {
members, members,

View File

@ -3,8 +3,32 @@ use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug; use sp_runtime::RuntimeDebug;
use codec::{Encode, Decode}; use codec::{Encode, Decode};
use sp_runtime::traits::UniqueSaturatedInto;
use frame_support::{BoundedVec, traits::ConstU32}; use frame_support::{BoundedVec, traits::ConstU32};
use crate::{AuthIndex, DkgIndex, ExodusSession, ExodusHash, ExodusError}; use ghost_traits::bounded_bitmap::BoundedBitmapReader;
use crate::{
AuthIndex, DkgIndex, ExodusSession, ExodusHash, ExodusError,
ParticipantsBitmap,
};
pub trait IndexedBundle<T>
where
T: crate::pallet::Config,
{
fn blob(&self) -> &[u8];
fn bitmask(&self) -> &ParticipantsBitmap<T>;
fn get_package_by_index<D: Decode>(&self, index: AuthIndex, size: usize) -> Option<D> {
let index_usize = index.unique_saturated_into();
let popcount = self.bitmask().count_ones_before::<usize>(index_usize)?;
popcount.checked_mul(size)
.and_then(|start| start.checked_add(size).map(|end| (start, end)))
.and_then(|(start, end)| self.blob().get(start..end))
.and_then(|raw_slice| D::decode(&mut &raw_slice[..]).ok())
}
}
pub trait PackageMetadata<NetworkCurve, BlockNumber> pub trait PackageMetadata<NetworkCurve, BlockNumber>
where where
@ -77,7 +101,7 @@ where
} }
} }
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)] #[derive(RuntimeDebug, Encode, Decode, Clone, TypeInfo, PartialEq, Eq)]
#[scale_info(skip_type_params(T))] #[scale_info(skip_type_params(T))]
pub enum DkgPackage<NetworkCurve, BlockNumber, T> pub enum DkgPackage<NetworkCurve, BlockNumber, T>
where where
@ -100,12 +124,19 @@ where
let length = match self { let length = match self {
Self::Round0(_) => ExodusHash::len_bytes(), Self::Round0(_) => ExodusHash::len_bytes(),
Self::Round1(round1) => round1.package.len(), Self::Round1(round1) => round1.package.len(),
Self::Round2(round2) => round2.bundle.bitmask.len(), Self::Round2(round2) => round2.bundle.bitmask.count_ones(),
Self::Round3(round3) => round3.indices.len(), Self::Round3(round3) => {
Self::Round4(round4) => round4.bundle.bitmask.len(), round3.indices.highest_bit::<usize>().unwrap_or_default()
},
Self::Round4(round4) => round4.bundle.bitmask.count_ones(),
Self::Round5(round5) => { Self::Round5(round5) => {
let key_members_size = round5.key_members
.highest_bit::<usize>()
.unwrap_or_default();
round5.verifying_key.len() round5.verifying_key.len()
.saturating_add(ExodusHash::len_bytes()) .saturating_add(ExodusHash::len_bytes())
.saturating_add(key_members_size)
}, },
Self::Round6(round6) => round6.merkle_proof.len(), Self::Round6(round6) => round6.merkle_proof.len(),
}; };
@ -165,9 +196,21 @@ where
} }
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
pub struct BundledPackages { #[scale_info(skip_type_params(T))]
pub struct BundledPackages<T>
where
T: crate::pallet::Config
{
pub bitmask: ParticipantsBitmap<T>,
pub blob: Vec<u8>, pub blob: Vec<u8>,
pub bitmask: Vec<u8>, }
impl<T> IndexedBundle<T> for BundledPackages<T>
where
T: crate::pallet::Config,
{
fn blob(&self) -> &[u8] { &self.blob }
fn bitmask(&self) -> &ParticipantsBitmap<T> { &self.bitmask }
} }
#[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] #[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
@ -177,30 +220,25 @@ where
T: crate::pallet::Config, T: crate::pallet::Config,
{ {
pub blob: BoundedVec<u8, crate::EncryptedRound2BlobMaxBytes<T>>, pub blob: BoundedVec<u8, crate::EncryptedRound2BlobMaxBytes<T>>,
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>> pub bitmask: ParticipantsBitmap<T>,
} }
impl<T: crate::pallet::Config> EncryptionBundledPackages<T> { impl<T> IndexedBundle<T> for EncryptionBundledPackages<T>
pub fn len(&self) -> usize { where
let blob_len = self.blob.len(); T: crate::pallet::Config,
let bitmask_len = self.bitmask.len(); {
fn blob(&self) -> &[u8] { &self.blob }
bitmask_len.saturating_add(blob_len) fn bitmask(&self) -> &ParticipantsBitmap<T> { &self.bitmask }
}
} }
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for EncryptionBundledPackages<T> { impl<T: crate::pallet::Config> TryFrom<BundledPackages<T>> for EncryptionBundledPackages<T> {
type Error = ExodusError; type Error = ExodusError;
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> { fn try_from(source: BundledPackages<T>) -> Result<Self, Self::Error> {
let blob = let blob = BoundedVec::try_from(source.blob)
BoundedVec::<u8, crate::EncryptedRound2BlobMaxBytes<T>>::try_from(source.blob)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask) Ok(Self { blob, bitmask: source.bitmask })
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Self { blob, bitmask })
} }
} }
@ -211,30 +249,25 @@ where
T: crate::pallet::Config, T: crate::pallet::Config,
{ {
pub blob: BoundedVec<u8, crate::Round2BlobMaxBytes<T>>, pub blob: BoundedVec<u8, crate::Round2BlobMaxBytes<T>>,
pub bitmask: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>> pub bitmask: ParticipantsBitmap<T>,
} }
impl<T: crate::pallet::Config> JustificationsBundledPackages<T> { impl<T> IndexedBundle<T> for JustificationsBundledPackages<T>
pub fn len(&self) -> usize { where
let blob_len = self.blob.len(); T: crate::pallet::Config,
let bitmask_len = self.bitmask.len(); {
fn blob(&self) -> &[u8] { &self.blob }
bitmask_len.saturating_add(blob_len) fn bitmask(&self) -> &ParticipantsBitmap<T> { &self.bitmask }
}
} }
impl<T: crate::pallet::Config> TryFrom<BundledPackages> for JustificationsBundledPackages<T> { impl<T: crate::pallet::Config> TryFrom<BundledPackages<T>> for JustificationsBundledPackages<T> {
type Error = ExodusError; type Error = ExodusError;
fn try_from(source: BundledPackages) -> Result<Self, Self::Error> { fn try_from(source: BundledPackages<T>) -> Result<Self, Self::Error> {
let blob = let blob = BoundedVec::try_from(source.blob)
BoundedVec::<u8, crate::Round2BlobMaxBytes<T>>::try_from(source.blob)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
let bitmask = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(source.bitmask) Ok(Self { blob, bitmask: source.bitmask })
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Self { blob, bitmask })
} }
} }
@ -323,7 +356,7 @@ pub struct Round3Package<NetworkCurve, BlockNumber, T>
where where
T: crate::pallet::Config, T: crate::pallet::Config,
{ {
pub indices: BoundedVec<u8, crate::AccusedIndicesMaxBytes<T>>, pub indices: ParticipantsBitmap<T>,
context: PackageContext<NetworkCurve, BlockNumber>, context: PackageContext<NetworkCurve, BlockNumber>,
} }
@ -344,7 +377,7 @@ where
T: crate::pallet::Config, T: crate::pallet::Config,
{ {
context: PackageContext<NetworkCurve, BlockNumber>, context: PackageContext<NetworkCurve, BlockNumber>,
pub key_members: BoundedVec<u8, crate::MaxAuthoritiesBitmaskSize<T>>, pub key_members: ParticipantsBitmap<T>,
pub verifying_key: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>, pub verifying_key: BoundedVec<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>,
pub merkle_root: ExodusHash, pub merkle_root: ExodusHash,
} }
@ -367,12 +400,10 @@ impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
hiding_commitment_vec: Vec<u8>, hiding_commitment_vec: Vec<u8>,
binding_commitment_vec: Vec<u8>, binding_commitment_vec: Vec<u8>,
) -> Result<ExodusNonceCommitment<NetworkCurve, NetworkId, BlockNumber>, ExodusError> { ) -> Result<ExodusNonceCommitment<NetworkCurve, NetworkId, BlockNumber>, ExodusError> {
let hiding_commitment = let hiding_commitment = BoundedVec::try_from(hiding_commitment_vec)
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(hiding_commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
let binding_commitment = let binding_commitment = BoundedVec::try_from(binding_commitment_vec)
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(binding_commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusNonceCommitment { Ok(ExodusNonceCommitment {
@ -389,8 +420,7 @@ impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
commitment_vec: Vec<u8>, commitment_vec: Vec<u8>,
binding_factors_root: ExodusHash, binding_factors_root: ExodusHash,
) -> Result<ExodusGroupCommitment<NetworkCurve, NetworkId, BlockNumber>, ExodusError> { ) -> Result<ExodusGroupCommitment<NetworkCurve, NetworkId, BlockNumber>, ExodusError> {
let group_commitment = let group_commitment = BoundedVec::try_from(commitment_vec)
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(commitment_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
Ok(ExodusGroupCommitment { Ok(ExodusGroupCommitment {
@ -408,12 +438,10 @@ impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
binding_factors_proof_vec: Vec<ExodusHash>, binding_factors_proof_vec: Vec<ExodusHash>,
self_binding_factor_vec: Vec<u8>, self_binding_factor_vec: Vec<u8>,
) -> Result<ExodusSignatureShare<NetworkCurve, NetworkId, BlockNumber, T>, ExodusError> { ) -> Result<ExodusSignatureShare<NetworkCurve, NetworkId, BlockNumber, T>, ExodusError> {
let signature_share = let signature_share = BoundedVec::try_from(signature_share_vec)
BoundedVec::<u8, ConstU32<{ crate::SCALAR_MAX_BYTES }>>::try_from(signature_share_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
let binding_factors_proof = let binding_factors_proof = BoundedVec::try_from(binding_factors_proof_vec)
BoundedVec::<ExodusHash, crate::BindingFactorsProof<T>>::try_from(binding_factors_proof_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
let self_binding_factor = let self_binding_factor =
@ -440,8 +468,7 @@ impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
self, self,
package_vec: Vec<u8>, package_vec: Vec<u8>,
) -> Result<Round1Package<NetworkCurve, BlockNumber, T>, ExodusError> { ) -> Result<Round1Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let package = let package = BoundedVec::try_from(package_vec)
BoundedVec::<u8, crate::Round1MaxBytes<T>>::try_from(package_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
Ok(Round1Package { context: self, package }) Ok(Round1Package { context: self, package })
@ -449,7 +476,7 @@ impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
pub fn with_dkg_round2<T: crate::pallet::Config>( pub fn with_dkg_round2<T: crate::pallet::Config>(
self, self,
bundle_raw: BundledPackages, bundle_raw: BundledPackages<T>,
) -> Result<Round2Package<NetworkCurve, BlockNumber, T>, ExodusError> { ) -> Result<Round2Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let bundle: EncryptionBundledPackages<T> = bundle_raw.try_into()?; let bundle: EncryptionBundledPackages<T> = bundle_raw.try_into()?;
Ok(Round2Package { context: self, bundle }) Ok(Round2Package { context: self, bundle })
@ -457,18 +484,14 @@ impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
pub fn with_dkg_round3<T: crate::pallet::Config>( pub fn with_dkg_round3<T: crate::pallet::Config>(
self, self,
indices_vec: Vec<u8> indices: ParticipantsBitmap<T>,
) -> Result<Round3Package<NetworkCurve, BlockNumber, T>, ExodusError> { ) -> Result<Round3Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let indices =
BoundedVec::<u8, crate::AccusedIndicesMaxBytes<T>>::try_from(indices_vec)
.map_err(|_| ExodusError::DeserializationError)?;
Ok(Round3Package { context: self, indices }) Ok(Round3Package { context: self, indices })
} }
pub fn with_dkg_round4<T: crate::pallet::Config>( pub fn with_dkg_round4<T: crate::pallet::Config>(
self, self,
bundle_raw: BundledPackages, bundle_raw: BundledPackages<T>,
) -> Result<Round4Package<NetworkCurve, BlockNumber, T>, ExodusError> { ) -> Result<Round4Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let bundle: JustificationsBundledPackages<T> = bundle_raw.try_into()?; let bundle: JustificationsBundledPackages<T> = bundle_raw.try_into()?;
Ok(Round4Package { context: self, bundle }) Ok(Round4Package { context: self, bundle })
@ -477,14 +500,10 @@ impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
pub fn with_dkg_round5<T: crate::pallet::Config>( pub fn with_dkg_round5<T: crate::pallet::Config>(
self, self,
verifying_key_vec: Vec<u8>, verifying_key_vec: Vec<u8>,
key_members_vec: Vec<u8>, key_members: ParticipantsBitmap<T>,
merkle_root: ExodusHash, merkle_root: ExodusHash,
) -> Result<Round5Package<NetworkCurve, BlockNumber, T>, ExodusError> { ) -> Result<Round5Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let key_members = BoundedVec::<u8, crate::MaxAuthoritiesBitmaskSize<T>>::try_from(key_members_vec) let verifying_key = BoundedVec::try_from(verifying_key_vec)
.map_err(|_| ExodusError::DeserializationError)?;
let verifying_key =
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_key_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
Ok(Round5Package { context: self, key_members, verifying_key, merkle_root }) Ok(Round5Package { context: self, key_members, verifying_key, merkle_root })
@ -495,12 +514,10 @@ impl<NetworkCurve, BlockNumber> PackageContext<NetworkCurve, BlockNumber> {
verifying_share_vec: Vec<u8>, verifying_share_vec: Vec<u8>,
merkle_proof_vec: Vec<ExodusHash>, merkle_proof_vec: Vec<ExodusHash>,
) -> Result<Round6Package<NetworkCurve, BlockNumber, T>, ExodusError> { ) -> Result<Round6Package<NetworkCurve, BlockNumber, T>, ExodusError> {
let verifying_share = let verifying_share = BoundedVec::try_from(verifying_share_vec)
BoundedVec::<u8, ConstU32<{ crate::ELEMENT_MAX_BYTES }>>::try_from(verifying_share_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
let merkle_proof = let merkle_proof = BoundedVec::try_from(merkle_proof_vec)
BoundedVec::<ExodusHash, crate::MerkleProofMaxSize<T>>::try_from(merkle_proof_vec)
.map_err(|_| ExodusError::DeserializationError)?; .map_err(|_| ExodusError::DeserializationError)?;
Ok(Round6Package { context: self, verifying_share, merkle_proof }) Ok(Round6Package { context: self, verifying_share, merkle_proof })

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-helpers" name = "ghost-helpers"
version = "0.0.14" version = "0.0.15"
description = "Cryptographic utility suite for custom runtimes: optimized Bitmaps, UTXO parsing, Merkle Tree proofs, and Hash Chain components." description = "Cryptographic utility suite for custom runtimes: optimized Bitmaps, UTXO parsing, Merkle Tree proofs, and Hash Chain components."
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true

View File

@ -2,8 +2,8 @@ use codec::{Decode, Encode};
use num_traits::PrimInt; use num_traits::PrimInt;
use scale_info::TypeInfo; use scale_info::TypeInfo;
use sp_runtime::traits::UniqueSaturatedInto;
use sp_arithmetic::traits::AtLeast8BitUnsigned; use sp_arithmetic::traits::AtLeast8BitUnsigned;
use sp_runtime::{traits::UniqueSaturatedInto, RuntimeDebug};
use sp_std::{cmp::{min, max}, marker::PhantomData, vec, vec::Vec}; use sp_std::{cmp::{min, max}, marker::PhantomData, vec, vec::Vec};
use ghost_traits::bounded_bitmap as traits; use ghost_traits::bounded_bitmap as traits;
@ -33,7 +33,7 @@ pub fn validate_bitmap_sizes<B: traits::BoundedBitmapMetadata>(authorities: u32)
); );
} }
#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(MaxChunks))] #[scale_info(skip_type_params(MaxChunks))]
pub struct BoundedBitmap<Inner, MaxChunks> pub struct BoundedBitmap<Inner, MaxChunks>
where where
@ -43,6 +43,19 @@ where
active_bits_count: u32, active_bits_count: u32,
} }
impl<Inner: core::fmt::Debug, MaxChunks> core::fmt::Debug for BoundedBitmap<Inner, MaxChunks>
where
MaxChunks: frame_support::traits::Get<u32>,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let slice: &[Inner] = &self.data;
f.debug_struct("BoundedBitmap")
.field("data", &slice)
.field("active_bits_count", &self.active_bits_count)
.finish()
}
}
impl<Inner, MaxChunks> BoundedBitmap<Inner, MaxChunks> impl<Inner, MaxChunks> BoundedBitmap<Inner, MaxChunks>
where where
MaxChunks: Get<u32>, MaxChunks: Get<u32>,
@ -54,6 +67,15 @@ where
Inner: BitAndAssign + BitOrAssign + BitXorAssign, Inner: BitAndAssign + BitOrAssign + BitXorAssign,
Inner: Shl<usize, Output = Inner> + Shr<usize, Output = Inner>, Inner: Shl<usize, Output = Inner> + Shr<usize, Output = Inner>,
{ {
pub fn inner_as_ref(&self) -> &[u8] {
let slice: &[Inner] = self.data.as_ref();
let byte_len = slice.len() * core::mem::size_of::<Inner>();
unsafe {
core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len)
}
}
fn combine<F>(&self, rhs: &Self, op: F) -> Self fn combine<F>(&self, rhs: &Self, op: F) -> Self
where where
Inner: Copy, Inner: Copy,
@ -146,41 +168,6 @@ where
new_bitmap new_bitmap
} }
fn from_bitmask<Outer>(values: &Vec<Outer>) -> Self
where
Outer: AtLeast8BitUnsigned + PrimInt,
{
let value_bits = inner_bits::<Outer>();
let n = values.iter().enumerate().rev()
.find(|(_, &val)| val != Outer::zero())
.map(|(idx, &val)| {
let active_bits = value_bits - (val.leading_zeros() as usize);
idx.saturating_mul(value_bits).saturating_add(active_bits)
})
.unwrap_or(0);
let mut new_bitmap = Self::empty(n);
for (value_index, value) in values.iter().enumerate() {
for bit_index in 0..value_bits {
let bit_mask = Outer::one() << bit_index;
if (*value & bit_mask) == Outer::zero() {
continue;
}
let index = value_index
.saturating_mul(value_bits)
.saturating_add(bit_index);
if let None = new_bitmap.insert(index as u32) {
return new_bitmap;
}
}
}
new_bitmap
}
fn all_ones<N>(n: N) -> Self fn all_ones<N>(n: N) -> Self
where where
N: UniqueSaturatedInto<usize>, N: UniqueSaturatedInto<usize>,
@ -307,6 +294,35 @@ where
}) })
} }
fn count_ones_before<N>(&self, index: N) -> Option<N>
where
usize: UniqueSaturatedInto<N>,
N: UniqueSaturatedInto<usize>,
{
let index: usize = index.unique_saturated_into();
if index >= self.active_bits_count as usize { return None; }
let shift = inner_shift::<Inner>();
let element_index = index >> shift;
let bit_index = index & ((1 << shift) - 1);
let current_element = self.data.get(element_index)?;
let lower_bits_mask = (Inner::one() << bit_index) - Inner::one();
let bits_before = *current_element & lower_bits_mask;
self.data.get(0..element_index)?
.iter()
.try_fold(0usize, |acc, &element| {
acc.checked_add(element.count_ones() as usize)
})
.and_then(|popcount| {
popcount.checked_add(bits_before.count_ones() as usize)
})
.map(|total| total.unique_saturated_into())
}
fn count_ones<N>(&self) -> N fn count_ones<N>(&self) -> N
where where
u32: UniqueSaturatedInto<N>, u32: UniqueSaturatedInto<N>,
@ -425,6 +441,14 @@ where
self.highest_bit() self.highest_bit()
} }
fn count_ones_before<N>(&self, index: N) -> Option<N>
where
usize: UniqueSaturatedInto<N>,
N: UniqueSaturatedInto<usize>,
{
self.count_ones_before(index)
}
fn count_ones<N>(&self) -> N fn count_ones<N>(&self) -> N
where where
u32: UniqueSaturatedInto<N>, u32: UniqueSaturatedInto<N>,
@ -476,13 +500,6 @@ where
Self::empty_from(bitmap_ref) Self::empty_from(bitmap_ref)
} }
fn from_bitmask<Outer>(values_ref: &Vec<Outer>) -> Self
where
Outer: AtLeast8BitUnsigned + PrimInt,
{
Self::from_bitmask(values_ref)
}
fn from_vec<Outer>(values_ref: &Vec<Outer>) -> Self fn from_vec<Outer>(values_ref: &Vec<Outer>) -> Self
where where
Outer: AtLeast8BitUnsigned + TryInto<usize> + PartialEq + Copy, Outer: AtLeast8BitUnsigned + TryInto<usize> + PartialEq + Copy,
@ -1099,50 +1116,72 @@ mod tests {
} }
#[test] #[test]
fn test_from_bitmask_where_each_u8_is_a_bit() { fn test_count_ones_before_basic() {
let byte1 = 77u8; let w = <$inner_type>::BITS as usize;
let byte2 = 42u8; let idx = |x: usize| -> $inner_type {
let byte3 = 69u8; x.try_into().ok().expect("bit index fits inner type")
};
let raw_bits: Vec<u8> = vec![byte1, byte2, byte3]; let mut indices: Vec<$inner_type> = vec![
let bitmap = TestBitmap::from_bitmask(&raw_bits); 0u8.into(),
1u8.into(),
3u8.into()
];
let count_ones = raw_bits.iter().map(|b| b.count_ones()).sum::<u32>(); indices.extend([idx(w), idx(w + 1), idx(w + 3)] as [$inner_type; 3]);
let filled_bits = (raw_bits.len() as u32 - 1) * 8; let bitmap = TestBitmap::from_vec(&indices);
let expected_bits = filled_bits + (8 - byte3.leading_zeros());
assert_eq!(bitmap.active_bits_count(), expected_bits); assert_eq!(bitmap.count_ones_before(0), Some(0));
assert_eq!(bitmap.count_ones::<u32>(), count_ones); assert_eq!(bitmap.count_ones_before(1), Some(1));
assert_eq!(bitmap.count_ones_before(2), Some(2));
assert_eq!(bitmap.count_ones_before(3), Some(2));
assert_eq!(bitmap.count_ones_before(4), Some(3));
assert_eq!(bitmap.count_ones_before(w), Some(3));
// 77 - 10110010 assert_eq!(bitmap.count_ones_before(w + 1), Some(4));
assert!(bitmap.contains(0u32)); assert_eq!(bitmap.count_ones_before(w + 4), Some(6));
assert!(!bitmap.contains(1u32)); assert_eq!(bitmap.count_ones_before(2 * w), Some(6));
assert!(bitmap.contains(2u32)); }
assert!(bitmap.contains(3u32));
assert!(!bitmap.contains(4u32));
assert!(!bitmap.contains(5u32));
assert!(bitmap.contains(6u32));
assert!(!bitmap.contains(7u32));
// 42 - 01010100 #[test]
assert!(!bitmap.contains(8u32)); fn test_count_ones_before_crossing_elements() {
assert!(bitmap.contains(9u32)); let w = <$inner_type>::BITS as usize;
assert!(!bitmap.contains(10u32)); let idx = |x: usize| -> $inner_type {
assert!(bitmap.contains(11u32)); x.try_into().ok().expect("bit index fits inner type")
assert!(!bitmap.contains(12u32)); };
assert!(bitmap.contains(13u32));
assert!(!bitmap.contains(14u32));
assert!(!bitmap.contains(15u32));
// 69 - 10100010 let mut bitmap = TestBitmap::empty(2 * w + 1);
assert!(bitmap.contains(16u32)); for i in [0usize, 1, w, w + 1, w + 2] {
assert!(!bitmap.contains(17u32)); bitmap.insert(idx(i));
assert!(bitmap.contains(18u32)); }
assert!(!bitmap.contains(19u32));
assert!(!bitmap.contains(20u32)); let bits_per_element = core::mem::size_of::<$inner_type>() * 8;
assert!(!bitmap.contains(21u32));
assert!(bitmap.contains(22u32)); assert_eq!(bitmap.count_ones_before(bits_per_element), Some(2));
assert!(!bitmap.contains(23u32)); assert_eq!(bitmap.count_ones_before(bits_per_element + 1), Some(3));
assert_eq!(bitmap.count_ones_before(bits_per_element + 3), Some(5));
assert_eq!(bitmap.count_ones_before(2 * bits_per_element), Some(5));
assert_eq!(bitmap.count_ones_before(2 * bits_per_element + 1), None);
}
#[test]
fn test_count_ones_before_bounds_and_overflow() {
let w = <$inner_type>::BITS as usize;
let idx = |x: usize| -> $inner_type {
x.try_into().ok().expect("bit index fits inner type")
};
let mut bitmap = TestBitmap::empty(w);
for i in 0..w {
bitmap.insert(idx(i));
}
let bits_per_element = (core::mem::size_of::<$inner_type>() * 8) as u32;
assert_eq!(bitmap.count_ones_before(bits_per_element - 1), Some(bits_per_element - 1));
assert_eq!(bitmap.count_ones_before(bits_per_element), None);
assert_eq!(bitmap.count_ones_before(10000u32), None);
} }
} }
}; };

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-traits" name = "ghost-traits"
version = "0.4.9" version = "0.4.10"
description = "Shared traits including `GhostHasher`, `NetworkDataBasicHandler`, `BoundedBTreeMap`, `MerkleTree` and more." description = "Shared traits including `GhostHasher`, `NetworkDataBasicHandler`, `BoundedBTreeMap`, `MerkleTree` and more."
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true

View File

@ -20,6 +20,10 @@ pub trait BoundedBitmapReader {
fn highest_bit<N>(&self) -> Option<N> fn highest_bit<N>(&self) -> Option<N>
where where
usize: UniqueSaturatedInto<N>; usize: UniqueSaturatedInto<N>;
fn count_ones_before<N>(&self, index: N) -> Option<N>
where
N: UniqueSaturatedInto<usize>,
usize: UniqueSaturatedInto<N>;
fn count_ones<N>(&self) -> N fn count_ones<N>(&self) -> N
where where
u32: UniqueSaturatedInto<N>; u32: UniqueSaturatedInto<N>;
@ -40,9 +44,6 @@ pub trait BoundedBitmapGenerator {
where where
N: UniqueSaturatedInto<usize>; N: UniqueSaturatedInto<usize>;
fn empty_from(bitmap: &Self) -> Self; fn empty_from(bitmap: &Self) -> Self;
fn from_bitmask<Outer>(values: &Vec<Outer>) -> Self
where
Outer: AtLeast8BitUnsigned + num_traits::PrimInt;
fn from_vec<Outer>(values: &Vec<Outer>) -> Self fn from_vec<Outer>(values: &Vec<Outer>) -> Self
where where
Outer: AtLeast8BitUnsigned + TryInto<usize> + PartialEq + Copy; Outer: AtLeast8BitUnsigned + TryInto<usize> + PartialEq + Copy;