diff --git a/pallets/exodus/Cargo.toml b/pallets/exodus/Cargo.toml index f1d05e0..6d0de06 100644 --- a/pallets/exodus/Cargo.toml +++ b/pallets/exodus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-exodus" -version = "0.1.3" +version = "0.1.4" 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 298785d..ba01fed 100644 --- a/pallets/exodus/src/benchmarking.rs +++ b/pallets/exodus/src/benchmarking.rs @@ -125,9 +125,9 @@ mod benchmarks { #[benchmark] fn register_encrypted_round2_packages( - c: Linear<1, { T::MaxAuthorities::get() / 8 }> + c: Linear<1, { T::MaxAuthorities::get() }> ) -> Result<(), BenchmarkError> { - let max_signers = (c * 8) as AuthIndex; + let max_signers = c as AuthIndex; let (mut dkg_state, network_curve, authority, authority_index) = prepare_pallet::(max_signers as usize); @@ -149,21 +149,18 @@ mod benchmarks { QualificationDkgState::::insert(&network_curve, dkg_state); - let bitmask_len = padded_participants.div_ceil(8); let blob_len = round2_encrypted_package_size( - padded_participants, + padded_participants - 1, network_curve.scalar_bytes_len(), network_curve.header_bytes_len(), ); - let mut bitmask = vec![0u8; bitmask_len]; + let mut bitmask = ParticipantsBitmap::::empty(padded_participants); let blob = vec![0u8; blob_len]; (0..max_signers).for_each(|i| { if i != authority_index { - let byte_index = (i / 8) as usize; - let bit_index = (i % 8) as u8; - bitmask[byte_index] |= 1 << bit_index; + bitmask.insert(i); } }); @@ -195,9 +192,9 @@ mod benchmarks { #[benchmark] fn register_round3_complaints( - c: Linear<1, { T::MaxAuthorities::get() / 8 }> + c: Linear<1, { T::MaxAuthorities::get() }> ) -> Result<(), BenchmarkError> { - let max_signers = (c * 8) as AuthIndex; + let max_signers = c as AuthIndex; let (mut dkg_state, network_curve, authority, authority_index) = prepare_pallet::(max_signers as usize); @@ -215,14 +212,11 @@ mod benchmarks { QualificationDkgState::::insert(&network_curve, dkg_state); - let bitvec_len = (max_signers + 7) / 8; - let mut accused_indexes = vec![0u8; bitvec_len as usize]; + let mut accused_indexes = ParticipantsBitmap::::empty(max_signers); (0..max_signers).for_each(|i| { if i != authority_index { - let byte_index = (i / 8) as usize; - let bit_index = (i % 8) as u8; - accused_indexes[byte_index] |= 1 << bit_index; + accused_indexes.insert(i); } }); @@ -255,9 +249,9 @@ mod benchmarks { #[benchmark] fn register_round4_justifications( - c: Linear<1, { T::MaxAuthorities::get() / 8 }> + c: Linear<1, { T::MaxAuthorities::get() }> ) -> Result<(), BenchmarkError> { - let max_signers = (c * 8) as AuthIndex; + let max_signers = c as AuthIndex; let (mut dkg_state, network_curve, authority, authority_index) = prepare_pallet::(max_signers as usize); @@ -281,21 +275,18 @@ mod benchmarks { QualificationDkgState::::insert(&network_curve, dkg_state); - let bitmask_len = padded_participants.div_ceil(8); let blob_len = round2_package_size( - padded_participants, + padded_participants - 1, network_curve.scalar_bytes_len(), network_curve.header_bytes_len(), ); - let mut bitmask = vec![0u8; bitmask_len]; + let mut bitmask = ParticipantsBitmap::::empty(padded_participants); let blob = vec![0u8; blob_len]; (0..max_signers).for_each(|i| { if i != authority_index { - let byte_index = (i / 8) as usize; - let bit_index = (i % 8) as u8; - bitmask[byte_index] |= 1 << bit_index; + bitmask.insert(i); } }); @@ -352,7 +343,7 @@ 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 dummy_key_members = ParticipantsBitmap::::empty(4); let current_block = ::BlockNumberProvider::current_block_number(); let round5_package = PackageContext::default() @@ -384,7 +375,7 @@ mod benchmarks { let metadata_hashes = [ dummy_merkle_root.as_ref(), bounded_verifying_key.as_ref(), - dummy_key_members.as_ref(), + dummy_key_members.inner_as_ref(), ]; let verifying_hash = cumulative_hash::(metadata_hashes); @@ -397,7 +388,7 @@ mod benchmarks { 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); + assert_eq!(consensus.members, dummy_key_members); Ok(()) } diff --git a/pallets/exodus/src/lib.rs b/pallets/exodus/src/lib.rs index 0c025ab..0489445 100644 --- a/pallets/exodus/src/lib.rs +++ b/pallets/exodus/src/lib.rs @@ -92,7 +92,7 @@ pub mod sr25519 { } 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 PALLET_LOG_TARGET : &str = "time:ghost-exodus"; @@ -160,10 +160,7 @@ const fn encrypted_ciphertext_bytes_len( header_bytes_len: usize, ) -> usize { 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(ciphertext_vec_prefix) - .saturating_add(AUTHENTICATION_TAG_MAX_BYTES as usize) + ciphertext_len.saturating_add(AUTHENTICATION_TAG_MAX_BYTES as usize) } const fn round2_encrypted_package_size( @@ -177,11 +174,13 @@ const fn round2_encrypted_package_size( header_bytes_len, ); - let ciphertext_len = encrypted_ciphertext_bytes_len - .saturating_add(ENCRYPTION_NONCE_MAX_BYTES as usize); + let ciphertext_vec_prefix = scale_compact_prefix_len( + encrypted_ciphertext_bytes_len + ); - let ciphertext_vec_prefix = scale_compact_prefix_len(ciphertext_len); - let size = ciphertext_len.saturating_add(ciphertext_vec_prefix); + let size = encrypted_ciphertext_bytes_len + .saturating_add(ciphertext_vec_prefix) + .saturating_add(ENCRYPTION_NONCE_MAX_BYTES as usize); participants.saturating_mul(size) } @@ -198,22 +197,6 @@ const fn round2_package_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 AuthIndex = ghost_helpers::AuthIndexU16; type BitmapChunk = ghost_helpers::BitmapChunkU32; @@ -261,14 +244,6 @@ impl Get for MaxEvmRotationSessionsSize { } } -pub struct MaxAuthoritiesBitmaskSize(sp_std::marker::PhantomData); -impl Get for MaxAuthoritiesBitmaskSize { - fn get() -> u32 { - let max_authorities = T::MaxAuthorities::get(); - max_authorities.div_ceil(8) - } -} - pub struct Round1MaxBytes(sp_std::marker::PhantomData); impl Get for Round1MaxBytes { fn get() -> u32 { @@ -311,14 +286,6 @@ impl Get for EncryptedRound2BlobMaxBytes { } } -pub struct AccusedIndicesMaxBytes(sp_std::marker::PhantomData); -impl Get for AccusedIndicesMaxBytes { - fn get() -> u32 { - let max_authorities = T::MaxAuthorities::get(); - max_authorities.saturating_add(7).div_ceil(8) - } -} - pub struct MerkleProofMaxSize(sp_std::marker::PhantomData); impl Get for MerkleProofMaxSize { fn get() -> u32 { @@ -341,16 +308,6 @@ impl Get for BindingFactorsProof { } } -pub struct NonceCommitmentMaxBytes(sp_std::marker::PhantomData); -impl Get for NonceCommitmentMaxBytes { - fn get() -> u32 { - nonce_commitment_package_size( - ELEMENT_MAX_BYTES as usize, - HEADER_MAX_BYTES as usize, - ) as u32 - } -} - pub struct CiphertextMaxBytes(sp_std::marker::PhantomData); impl Get for CiphertextMaxBytes { fn get() -> u32 { @@ -361,24 +318,6 @@ impl Get for CiphertextMaxBytes { } } -pub struct SignatureShareMaxBytes(sp_std::marker::PhantomData); -impl Get for SignatureShareMaxBytes { - fn get() -> u32 { - signature_share_bytes_len( - SCALAR_MAX_BYTES as usize, - HEADER_MAX_BYTES as usize, - ) as u32 - } -} - -pub struct SignatureMaxBytes(sp_std::marker::PhantomData); -impl Get for SignatureMaxBytes { - 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(sp_std::marker::PhantomData); impl Get>> for ZeroScalarDefault { fn get() -> BoundedVec> { @@ -756,10 +695,7 @@ pub mod pallet { _, Twox64Concat, NetworkCurve, Twox64Concat, DkgIndex, - QualifiedConsensusState< - ParticipantsBitmap, - BoundedVec> - >, + QualifiedConsensusState>, ValueQuery, >; @@ -1090,19 +1026,6 @@ pub mod pallet { let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_second_phase(), Error::::DkgWrongRound); - let encrypted_indexes = ParticipantsBitmap::::from_bitmask( - &round2_bundle.bundle.bitmask, - ); - - let diff = state.get_indexes() ^ &encrypted_indexes; - let only_one_lost = diff.count_ones::() == 1; - let only_authority_index = diff.contains(authority_index); - - ensure!( - only_one_lost && only_authority_index, - Error::::Round2PackagesWrongCoefficients, - ); - Round2Packages::::try_mutate(&network_curve, |round2_packages| -> DispatchResult { ensure!( @@ -1115,8 +1038,11 @@ pub mod pallet { Ok(()) })?; + let encrypted_count = round2_bundle.bundle.bitmask + .count_ones::(); + Self::deposit_event(Event::::Round2PackagesRegistered { - encrypted_count: encrypted_indexes.count_ones::(), + encrypted_count, authority_index, network_curve, }); @@ -1160,11 +1086,7 @@ pub mod pallet { return Err(Error::::Round3PackageInvalidProof.into()); }; - let complaints_bitmap = ParticipantsBitmap::::from_bitmask( - &round3_complaints.indices - ); - - let complaints_count: AuthIndex = complaints_bitmap.count_ones(); + let complaints_count: AuthIndex = round3_complaints.indices.count_ones(); let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_third_phase(), Error::::DkgWrongRound); @@ -1176,7 +1098,7 @@ pub mod pallet { Error::::Round3PackagesAlreadyRegistered, ); - complaints.try_insert(authority_index, complaints_bitmap) + complaints.try_insert(authority_index, round3_complaints.indices) .map_err(|_| Error::::TooManyPackages)?; Ok(()) @@ -1216,14 +1138,12 @@ pub mod pallet { return Err(Error::::Round4PackageInvalidProof.into()); }; - let justifications_bitmap = ParticipantsBitmap::::from_bitmask( - &round4_bundle.bundle.bitmask - ); - - let justifications_count: AuthIndex = justifications_bitmap.count_ones(); + let justifications_count = round4_bundle.bundle.bitmask.count_ones(); let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_fourth_phase(), Error::::DkgWrongRound); + let encoded_bundle = round4_bundle.bundle.encode(); + Justifications::::try_mutate( &network_curve, |justifications| -> DispatchResult { @@ -1232,8 +1152,10 @@ pub mod pallet { Error::::Round4PackagesAlreadyRegistered, ); - justifications.try_insert(authority_index, justifications_bitmap) - .map_err(|_| Error::::TooManyPackages)?; + justifications.try_insert( + authority_index, + round4_bundle.bundle.bitmask + ).map_err(|_| Error::::TooManyPackages)?; Ok(()) })?; @@ -1252,7 +1174,7 @@ pub mod pallet { sp_io::offchain_index::set( &index_key, - &round4_bundle.bundle.encode(), + &encoded_bundle ); Ok(()) @@ -1287,7 +1209,7 @@ pub mod pallet { let metadata_hashes = [ round5.merkle_root.as_ref(), round5.verifying_key.as_ref(), - round5.key_members.as_ref(), + round5.key_members.inner_as_ref(), ]; let verifying_hash = cumulative_hash::(metadata_hashes); @@ -1360,17 +1282,12 @@ pub mod pallet { return Err(Error::::Round6PackageInvalidProof.into()); }; - let expected_share_len = network_curve.element_bytes_len(); - ensure!( - round6.verifying_share.len() == expected_share_len, - Error::::Round6PackageInvalidProof, - ); - let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_sixth_phase(), Error::::DkgWrongRound); let dkg_index = state.get_dkg_index(); - let qualified_consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); + let qualified_consensus = + VerifyingKeyConsensus::::get(&network_curve, &dkg_index); network_curve.verify_merkle_proof( &round6.verifying_share, @@ -2581,8 +2498,7 @@ impl Pallet { 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); + dkg_qualification.intersect_indexes(&consensus.members); } }) } @@ -2962,39 +2878,24 @@ impl Pallet { return Err(InvalidTransaction::BadSigner); } - let bundle_packages_count = round2.bundle.bitmask - .iter() - .map(|byte| byte.count_ones() as usize) - .sum::() - .saturating_add(1); + let bitmask_count = round2.bundle.bitmask.count_ones::(); + let count_without_self = state.count_ones::().saturating_sub(1); - if bundle_packages_count != state.count_ones::() { + // exclude self from the count; the first coefficient is never sent + if count_without_self != bitmask_count { return Err(InvalidTransaction::BadProof); } - let has_invalid_round2_recipients = round2.bundle.bitmask - .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; - if global_index == authority_index as usize { return true; } - !state.contains_index(global_index) - }) - }); + let diff = state.get_indexes() ^ &round2.bundle.bitmask; + let only_one_lost = diff.count_ones::() == 1; + let only_authority_index = diff.contains(authority_index); - if has_invalid_round2_recipients { + if !only_one_lost || !only_authority_index { return Err(InvalidTransaction::BadProof); } - let padded_participants = state.highest_bit::() - .map(|bit_pos| bit_pos.saturating_add(1)) - .unwrap_or_default(); - let expected_blob_len = round2_encrypted_package_size( - padded_participants, + count_without_self, network_curve.scalar_bytes_len(), network_curve.header_bytes_len(), ); @@ -3056,29 +2957,20 @@ impl Pallet { return Err(InvalidTransaction::BadSigner); } - let estimated_indices_len = state + let qualification_highest_bit = state.highest_bit::().unwrap_or(0); + let indices_highest_bit = round3_complaints.indices .highest_bit::() - .map(|bit_index| bit_index.saturating_add(1).div_ceil(8)) - .unwrap_or_default(); + .unwrap_or(0); - if estimated_indices_len != round3_complaints.indices.len() { + if qualification_highest_bit < indices_highest_bit { return Err(InvalidTransaction::BadProof); } - let has_invalid_accused_indexes = round3_complaints.indices - .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; - if global_index == authority_index as usize { return true; } - !state.contains_index(global_index) - }) - }); + let contains_self = round3_complaints.indices.contains(authority_index); + let extra_bits = !state.get_indexes() & &round3_complaints.indices; + let has_extra_bits = extra_bits.count_ones::() > 0; - if has_invalid_accused_indexes { + if has_extra_bits || contains_self { return Err(InvalidTransaction::BadProof); } @@ -3136,39 +3028,26 @@ impl Pallet { return Err(InvalidTransaction::BadSigner); } - let bundle_packages_count = round4.bundle.bitmask - .iter() - .map(|byte| byte.count_ones() as usize) - .sum::() - .saturating_add(1); - if bundle_packages_count > state.count_ones::() { + let qualification_highest_bit = state.highest_bit::().unwrap_or(0); + let bitmask_highest_bit = round4.bundle.bitmask + .highest_bit::() + .unwrap_or(0); + + if qualification_highest_bit < bitmask_highest_bit { return Err(InvalidTransaction::BadProof); } - let has_invalid_justifications = round4.bundle.bitmask - .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; - if global_index == authority_index as usize { return true; } - !state.contains_index(global_index) - }) - }); + let contains_self = round4.bundle.bitmask.contains(authority_index); + let extra_bits = !state.get_indexes() & &round4.bundle.bitmask; + let has_extra_bits = extra_bits.count_ones::() > 0; - if has_invalid_justifications { + if has_extra_bits || contains_self { return Err(InvalidTransaction::BadProof); } - let padded_participants = state.highest_bit::() - .map(|bit_pos| bit_pos.saturating_add(1)) - .unwrap_or_default(); - let expected_blob_len = round2_package_size( - padded_participants, + round4.bundle.bitmask.count_ones::(), network_curve.scalar_bytes_len(), network_curve.header_bytes_len(), ); @@ -3230,27 +3109,12 @@ impl Pallet { return Err(InvalidTransaction::BadSigner); } - let estimated_max_size = network_curve - .element_bytes_len() - .saturating_add(ExodusHash::len_bytes()); + let extra_bits = !state.get_indexes() & &round5.key_members; + let has_extra_bits = extra_bits.count_ones::() > 0; + let verifying_key_valid = + round5.verifying_key.len() == network_curve.element_bytes_len(); - if dkg_package.bytes_len() as usize != estimated_max_size { - 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 { + if !verifying_key_valid || has_extra_bits { return Err(InvalidTransaction::BadProof); } @@ -3281,6 +3145,10 @@ impl Pallet { dkg_package: &DkgPackage, T>, signature: &::Signature, ) -> Result<(), InvalidTransaction> { + let DkgPackage::Round6(round6) = 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(); @@ -3303,12 +3171,16 @@ impl Pallet { return Err(InvalidTransaction::BadSigner); } + if round6.verifying_share.len() != network_curve.element_bytes_len() { + return Err(InvalidTransaction::BadProof); + } + let estimated_proof_count = state .count_ones::() .next_power_of_two() .trailing_zeros(); - if dkg_package.bytes_len() != estimated_proof_count { + if round6.merkle_proof.len() != estimated_proof_count as usize { return Err(InvalidTransaction::BadProof); } @@ -4405,24 +4277,11 @@ impl Pallet { .map(|bit_pos| bit_pos.saturating_add(1)) .unwrap_or_default(); - let bitmask_len = padded_participants.div_ceil(8); - 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( + let (bitmask, blob) = Self::ecdh_encrypt_round2_packages( &round1_packages, &public_packages, round1_secret_package, - &mut bitmask, - &mut blob, - max_package_size, + padded_participants, authority_index, qualification_state.get_dkg_index(), network_curve, @@ -4595,36 +4454,30 @@ impl Pallet { .map(|bit_pos| bit_pos.saturating_add(1)) .unwrap_or_default(); - let bitmask_len = padded_participants.div_ceil(8); let blob_len = round2_package_size( padded_participants, network_curve.scalar_bytes_len(), network_curve.header_bytes_len(), ); - let round2_package_size = blob_len.saturating_div(padded_participants); + let mut bitmask = ParticipantsBitmap::::empty(padded_participants); + let mut blob = Vec::with_capacity(blob_len); - let mut bitmask = vec![0u8; bitmask_len]; - let mut blob = vec![0u8; blob_len]; + Complaints::::get(&network_curve) + .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::::get(&network_curve).iter() { - if !bitmap.contains(authority_index) { continue; } - 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, - ); - } + if let Some(_) = bitmask.insert(accuser_index) { + blob.extend_from_slice(&encoded); + } + }); round4_metadata.update_with_block( Some(dkg_storage.get_release_block()), @@ -4762,23 +4615,13 @@ impl Pallet { .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]; + let mut key_members_bitmask = ParticipantsBitmap::::empty( + padded_participants + ); + key_members_bitmask.insert(authority_index); 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); + key_members_bitmask.insert(member_index); } let current_dkg_index = qualification_state.get_dkg_index(); @@ -4969,29 +4812,29 @@ impl Pallet { } 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() - }) - }) - }); + if !qualification_state.contains_index(accused_index) + || kicked.contains(&accused_index) + || accused_index == authority_index + { + continue; + } 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::>().ok().flatten() + }) + .as_ref() + .and_then(|bundle| { + bundle.get_package_by_index::>( + accuser_index, + round2_package_size + ) + }); let (round1_package, round2_package) = - match (maybe_round1_package, maybe_justification) { + match (maybe_round1_package, maybe_justifications) { (Some(round1_package), Some(round2_package)) => { (round1_package, round2_package) }, @@ -5019,7 +4862,6 @@ impl Pallet { round1_packages.remove(&accused_index); round2_packages.remove(&accused_index); kicked.insert(accused_index); - continue; } } } @@ -5027,26 +4869,17 @@ impl Pallet { } pub fn locate_package_bounds( - bundle: &BundledPackages, + bundle: &BundledPackages, single_bytes_len: usize, authority_index: AuthIndex, ) -> Option<(usize, usize)> { - let global_index = authority_index as usize; - let byte_index = global_index >> 3usize; - let bit_index = global_index & 7usize; - let bit_mask = 1u8 << bit_index; + let popcount = bundle.bitmask.count_ones_before(authority_index)?; + let popcount: usize = popcount.unique_saturated_into(); - let bundle_bitmask_by_index = bundle.bitmask.get(byte_index)?; - if (bundle_bitmask_by_index & bit_mask) == 0 { - return None; - } + let start_offset = popcount.checked_mul(single_bytes_len)?; + let end_offset = start_offset.checked_add(single_bytes_len)?; - let start_offset = global_index * single_bytes_len; - let end_offset = start_offset + single_bytes_len; - - if end_offset > bundle.blob.len() { - return None; - } + if end_offset > bundle.blob.len() { return None; } Some((start_offset, end_offset)) } @@ -5088,17 +4921,15 @@ impl Pallet { fn ecdh_decrypt_round2_packages( qualification_state: &PendingDkgAuthorities, round1_packages: &BTreeMap>, - round1_secret_package: &Vec, + round1_secret_package: &[u8], authority_index: AuthIndex, network_curve: NetworkCurve, - ) -> (Vec, BTreeMap>) { + ) -> (ParticipantsBitmap, BTreeMap>) { let dkg_index = qualification_state.get_dkg_index(); - let bitmask_len = qualification_state + let len = qualification_state .highest_bit::() - .map(|bit_pos| { - bit_pos.saturating_add(1).div_ceil(8) - }) + .map(|bit_pos| bit_pos.saturating_add(1)) .unwrap_or_default(); let encryption_size = round2_encrypted_package_size( @@ -5107,25 +4938,24 @@ impl Pallet { network_curve.header_bytes_len(), ); + let mut accused_indexes = ParticipantsBitmap::::empty(len); + let mut decrypted = BTreeMap::new(); + round1_packages .iter() .filter(|(&sender_index, _)| sender_index != authority_index) - .map(|(&sender_index, round1_sender_package)| { - let key = Self::create_offchain_dkg_key(network_curve, sender_index, ROUND_NUMBER_2); + .for_each(|(&sender_index, round1_sender_package)| { + let key = Self::create_offchain_dkg_key( + network_curve, + sender_index, + ROUND_NUMBER_2 + ); let decryption_result = StorageValueRef::persistent(&key) - .get::() + .get::>() .ok() .flatten() - .and_then(|bundle| { - 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(|bundle| bundle.get_package_by_index(authority_index, encryption_size)) .and_then(|encrypted_round2_package: EncryptionData| { let info = network_curve.ecdh_prepare_additional_info( sender_index, @@ -5134,64 +4964,54 @@ impl Pallet { sp_std::marker::PhantomData::>, ); - network_curve - .ecdh_get_cipher_from_keys( - round1_sender_package, - round1_secret_package, - &info, - sp_std::marker::PhantomData::>, - ) - .and_then(|cipher| { - network_curve.ecdh_decrypt_package( - &cipher, - &encrypted_round2_package, - &info, - ) - }) - .and_then(|decrypted| { - network_curve.dkg_verify_private_package( - authority_index, - round1_sender_package, - &decrypted, - ).map(|_| decrypted) - }) - .map_err(|_| sender_index) - }) - .map(|decrypted_package| (sender_index, decrypted_package)); + let cipher = network_curve.ecdh_get_cipher_from_keys( + round1_sender_package, + round1_secret_package, + &info, + sp_std::marker::PhantomData::>, + ).ok()?; - decryption_result - }) - .fold( - (vec![0u8; bitmask_len], BTreeMap::new()), - |(mut accused_indexes, mut decrypted), decryption_result| { - match decryption_result { - Ok((sender_index, package)) => { decrypted.insert(sender_index, package); }, - Err(sender_index) => { - let global_index = sender_index as usize; - let byte_index = global_index >> 3usize; + let decrypted = network_curve.ecdh_decrypt_package( + &cipher, + &encrypted_round2_package, + &info, + ).ok()?; - if let Some(byte) = accused_indexes.get_mut(byte_index) { - let bit_index = global_index & 7usize; - *byte |= 1u8 << bit_index; - } - }, - } - (accused_indexes, decrypted) + network_curve.dkg_verify_private_package( + authority_index, + round1_sender_package, + &decrypted, + ).map(|_| decrypted).ok() + }); + + match decryption_result { + Some(package) => { decrypted.insert(sender_index, package); }, + None => { accused_indexes.insert(sender_index); }, } - ) + }); + + (accused_indexes, decrypted) } fn ecdh_encrypt_round2_packages( round1_packages: &BTreeMap>, round2_packages: &BTreeMap>, round1_secret_package: &[u8], - bitmask: &mut Vec, - blob: &mut Vec, - max_size: usize, + padded_participants: usize, authority_index: AuthIndex, dkg_index: DkgIndex, network_curve: NetworkCurve, - ) { + ) -> (ParticipantsBitmap, Vec) { + 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::::empty(padded_participants); + let mut blob = Vec::with_capacity(blob_len); + let authority_index_bytes = authority_index.to_le_bytes(); let dkg_index_bytes = dkg_index.to_le_bytes(); let network_curve_bytes = [network_curve as u8]; @@ -5207,10 +5027,9 @@ impl Pallet { for (&receiver_index, round2_package) in round2_packages.iter() { if receiver_index == authority_index { continue; } - let round1_receiver_package = match round1_packages - .get(&receiver_index) { - Some(package) => package, - None => continue, + let Some(round1_receiver_package) = round1_packages + .get(&receiver_index) else { + continue; }; let info = network_curve.ecdh_prepare_additional_info( @@ -5220,57 +5039,38 @@ impl Pallet { sp_std::marker::PhantomData::>, ); - let encrypted_data: EncryptionData = match network_curve + let maybe_encoded_bytes = network_curve .ecdh_get_cipher_from_keys( &round1_receiver_package, &round1_secret_package, &info, sp_std::marker::PhantomData::>, ) - .and_then(|cipher| network_curve.ecdh_encrypt_package( - &cipher, - &info, - &round2_package, - &mut rng, - )) { - Ok(encrypted_data) => encrypted_data, - Err(_) => break, - }; + .and_then(|cipher| { + network_curve.ecdh_encrypt_package( + &cipher, + &info, + &round2_package, + &mut rng + ) + }) + .map(|data: EncryptionData| data.encode()) + .ok() + .filter(|b| b.len() == max_size); - Self::prepare_bundled_data( - bitmask, - blob, - &encrypted_data.encode(), - max_size, - receiver_index, - ); + match maybe_encoded_bytes { + Some(encoded) => { + if let Some(_) = bitmask.insert(receiver_index) { + blob.extend_from_slice(&encoded); + } + }, + None => { + blob.extend_from_slice(&vec![0u8; max_size]); + } + } } - } - fn prepare_bundled_data( - bitmask: &mut Vec, - blob: &mut Vec, - 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; - } + (bitmask, blob) } fn get_offchain_rng(context: &[&[u8]]) -> ChaCha20Rng { diff --git a/pallets/exodus/src/mock.rs b/pallets/exodus/src/mock.rs index a73cc4b..306ee60 100644 --- a/pallets/exodus/src/mock.rs +++ b/pallets/exodus/src/mock.rs @@ -41,7 +41,7 @@ impl frame_system::Config for Runtime { } parameter_types! { - pub static FixedAuthorities: Vec = vec![0, 1, 2, 3]; + pub static FixedAuthorities: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; } pub struct TestSessionManager; @@ -137,10 +137,10 @@ impl ghost_exodus::Config for Runtime { type UnsignedPriority = ConstU64<{ 1 << 20 }>; type UnsignedLongevity = ConstU64<5>; - type MaxAuthorities = ConstU32<10>; - type MaxAuthoritiesChunks = ConstU32<1>; + type MaxAuthorities = ConstU32<15>; + type MaxAuthoritiesChunks = ConstU32<2>; - type DkgRoundPeriod = ConstU64<300>; + type DkgRoundPeriod = ConstU64<1000>; type RemovalLimit = ConstU32<20>; type MaxCursorLen = ConstU32<256>; type BaseEvmAmount = ConstU128<5>; diff --git a/pallets/exodus/src/tests.rs b/pallets/exodus/src/tests.rs index 86ddef3..b58d330 100644 --- a/pallets/exodus/src/tests.rs +++ b/pallets/exodus/src/tests.rs @@ -543,9 +543,7 @@ fn run_succesfull_dkg_session_partially( } } - let dummy_key_members: BoundedVec> = - key_members_raw.try_into().unwrap(); - + let dummy_key_members = ParticipantsBitmap::::empty(padded_participants); let dummy_oversized_bitmap = ParticipantsBitmap::::all_ones(max_authorities_len); let mut map: BitmapByAuthority = Default::default(); @@ -767,9 +765,13 @@ fn test_dkg_robustness_against_malformed_scale_slots() { 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 malicious_validator = 3u64; + let all_validators = FixedAuthorities::get(); + let honest_validators = all_validators + .iter() + .copied() + .filter(|&x| x != malicious_validator) + .collect::>(); let authorities: Vec = all_validators.iter().map(|&id| id.into()).collect(); 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(|| { let dkg_index = QualificationDkgState::::get(curve).get_dkg_index(); + let padded_participants = all_validators.len(); - let padded_participants = 10usize; - let bitmask_len = padded_participants.div_ceil(8); - - let mut perfect_bitmask = vec![0u8; bitmask_len]; + let mut perfect_bitmask = ParticipantsBitmap::::empty(padded_participants); for &recipient in &honest_validators { - let idx = recipient as usize; - perfect_bitmask[idx >> 3] |= 1 << (idx & 7); + perfect_bitmask.insert(recipient); } let expected_blob_len = round2_encrypted_package_size( - padded_participants, + padded_participants - 1, curve.scalar_bytes_len(), curve.header_bytes_len(), ); @@ -825,7 +824,6 @@ fn test_dkg_robustness_against_malformed_scale_slots() { .unwrap(); let dummy_package = DkgPackage::Round2(dummy_context); - let malicious_signer: UintAuthorityId = malicious_validator.into(); 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 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 malicious_validator = 3u64; + let all_validators = FixedAuthorities::get(); + let honest_validators = all_validators + .iter() + .copied() + .filter(|&x| x != malicious_validator) + .collect::>(); let authorities: Vec = all_validators.iter().map(|&id| id.into()).collect(); 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 = 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::::empty(authorities_len); + complaints.insert(malicious_index); + + Complaints::::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::::get(curve)); + assert_eq!(state.get_phase(), DkgPhase::Round7); + assert_eq!(state.count_ones::(), raw_authorities.len()); +} + #[test] fn test_all_validators_can_execute_initial_dkg() { let curve = NetworkCurve::Secp256k1; @@ -1302,6 +1361,11 @@ fn test_prepare_round5_kick_on_empty_justifications() { let auth: Vec = raw_authorities.iter().map(|&id| id as AuthIndex).collect(); + for &curr_auth in auth.iter() { + complaints_map.try_insert(curr_auth, ParticipantsBitmap::::empty(authorities_len)).unwrap(); + justifications_map.try_insert(curr_auth, ParticipantsBitmap::::empty(authorities_len)).unwrap(); + } + let mut complaints_from_1 = ParticipantsBitmap::::empty(authorities_len); complaints_from_1.insert(auth[0]); 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_map.try_insert(auth[2], complaints_from_2).unwrap(); - complaints_map.try_insert(auth[0], ParticipantsBitmap::::empty(authorities_len)).unwrap(); - complaints_map.try_insert(auth[3], ParticipantsBitmap::::empty(authorities_len)).unwrap(); - let mut justifications_from_0 = ParticipantsBitmap::::empty(authorities_len); justifications_from_0.insert(auth[1]); 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_map.try_insert(auth[3], justifications_from_3).unwrap(); - justifications_map.try_insert(auth[1], ParticipantsBitmap::::empty(authorities_len)).unwrap(); - justifications_map.try_insert(auth[2], ParticipantsBitmap::::empty(authorities_len)).unwrap(); - Complaints::::insert(curve, complaints_map); Justifications::::insert(curve, justifications_map); }); diff --git a/pallets/exodus/src/types/consensus.rs b/pallets/exodus/src/types/consensus.rs index 66d01cd..e792460 100644 --- a/pallets/exodus/src/types/consensus.rs +++ b/pallets/exodus/src/types/consensus.rs @@ -7,17 +7,17 @@ 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 struct QualifiedConsensusState { pub state: ConsensusState, - pub members: Bitmask, + pub members: Bitmap, } -impl QualifiedConsensusState { +impl QualifiedConsensusState { pub fn new( element_bytes: BoundedVec>, participants: Bitmap, merkle_root: ExodusHash, - members: Bitmask, + members: Bitmap, ) -> Self { Self { members, diff --git a/pallets/exodus/src/types/packages.rs b/pallets/exodus/src/types/packages.rs index 18d1b4c..a98337f 100644 --- a/pallets/exodus/src/types/packages.rs +++ b/pallets/exodus/src/types/packages.rs @@ -3,8 +3,32 @@ use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; use codec::{Encode, Decode}; +use sp_runtime::traits::UniqueSaturatedInto; 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 +where + T: crate::pallet::Config, +{ + fn blob(&self) -> &[u8]; + fn bitmask(&self) -> &ParticipantsBitmap; + + fn get_package_by_index(&self, index: AuthIndex, size: usize) -> Option { + let index_usize = index.unique_saturated_into(); + let popcount = self.bitmask().count_ones_before::(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 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))] pub enum DkgPackage where @@ -100,12 +124,19 @@ where let length = match self { Self::Round0(_) => ExodusHash::len_bytes(), Self::Round1(round1) => round1.package.len(), - Self::Round2(round2) => round2.bundle.bitmask.len(), - Self::Round3(round3) => round3.indices.len(), - Self::Round4(round4) => round4.bundle.bitmask.len(), + Self::Round2(round2) => round2.bundle.bitmask.count_ones(), + Self::Round3(round3) => { + round3.indices.highest_bit::().unwrap_or_default() + }, + Self::Round4(round4) => round4.bundle.bitmask.count_ones(), Self::Round5(round5) => { + let key_members_size = round5.key_members + .highest_bit::() + .unwrap_or_default(); + round5.verifying_key.len() .saturating_add(ExodusHash::len_bytes()) + .saturating_add(key_members_size) }, Self::Round6(round6) => round6.merkle_proof.len(), }; @@ -165,9 +196,21 @@ where } #[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] -pub struct BundledPackages { +#[scale_info(skip_type_params(T))] +pub struct BundledPackages +where + T: crate::pallet::Config +{ + pub bitmask: ParticipantsBitmap, pub blob: Vec, - pub bitmask: Vec, +} + +impl IndexedBundle for BundledPackages +where + T: crate::pallet::Config, +{ + fn blob(&self) -> &[u8] { &self.blob } + fn bitmask(&self) -> &ParticipantsBitmap { &self.bitmask } } #[derive(RuntimeDebug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] @@ -177,30 +220,25 @@ where T: crate::pallet::Config, { pub blob: BoundedVec>, - pub bitmask: BoundedVec> + pub bitmask: ParticipantsBitmap, } -impl EncryptionBundledPackages { - pub fn len(&self) -> usize { - let blob_len = self.blob.len(); - let bitmask_len = self.bitmask.len(); - - bitmask_len.saturating_add(blob_len) - } +impl IndexedBundle for EncryptionBundledPackages +where + T: crate::pallet::Config, +{ + fn blob(&self) -> &[u8] { &self.blob } + fn bitmask(&self) -> &ParticipantsBitmap { &self.bitmask } } -impl TryFrom for EncryptionBundledPackages { +impl TryFrom> for EncryptionBundledPackages { type Error = ExodusError; - fn try_from(source: BundledPackages) -> Result { - let blob = - BoundedVec::>::try_from(source.blob) - .map_err(|_| ExodusError::DeserializationError)?; - - let bitmask = BoundedVec::>::try_from(source.bitmask) + fn try_from(source: BundledPackages) -> Result { + let blob = BoundedVec::try_from(source.blob) .map_err(|_| ExodusError::DeserializationError)?; - Ok(Self { blob, bitmask }) + Ok(Self { blob, bitmask: source.bitmask }) } } @@ -211,30 +249,25 @@ where T: crate::pallet::Config, { pub blob: BoundedVec>, - pub bitmask: BoundedVec> + pub bitmask: ParticipantsBitmap, } -impl JustificationsBundledPackages { - pub fn len(&self) -> usize { - let blob_len = self.blob.len(); - let bitmask_len = self.bitmask.len(); - - bitmask_len.saturating_add(blob_len) - } +impl IndexedBundle for JustificationsBundledPackages +where + T: crate::pallet::Config, +{ + fn blob(&self) -> &[u8] { &self.blob } + fn bitmask(&self) -> &ParticipantsBitmap { &self.bitmask } } -impl TryFrom for JustificationsBundledPackages { +impl TryFrom> for JustificationsBundledPackages { type Error = ExodusError; - fn try_from(source: BundledPackages) -> Result { - let blob = - BoundedVec::>::try_from(source.blob) - .map_err(|_| ExodusError::DeserializationError)?; - - let bitmask = BoundedVec::>::try_from(source.bitmask) + fn try_from(source: BundledPackages) -> Result { + let blob = BoundedVec::try_from(source.blob) .map_err(|_| ExodusError::DeserializationError)?; - Ok(Self { blob, bitmask }) + Ok(Self { blob, bitmask: source.bitmask }) } } @@ -323,7 +356,7 @@ pub struct Round3Package where T: crate::pallet::Config, { - pub indices: BoundedVec>, + pub indices: ParticipantsBitmap, context: PackageContext, } @@ -344,7 +377,7 @@ where T: crate::pallet::Config, { context: PackageContext, - pub key_members: BoundedVec>, + pub key_members: ParticipantsBitmap, pub verifying_key: BoundedVec>, pub merkle_root: ExodusHash, } @@ -367,13 +400,11 @@ impl PackageContext { hiding_commitment_vec: Vec, binding_commitment_vec: Vec, ) -> Result, ExodusError> { - let hiding_commitment = - BoundedVec::>::try_from(hiding_commitment_vec) - .map_err(|_| ExodusError::DeserializationError)?; + let hiding_commitment = BoundedVec::try_from(hiding_commitment_vec) + .map_err(|_| ExodusError::DeserializationError)?; - let binding_commitment = - BoundedVec::>::try_from(binding_commitment_vec) - .map_err(|_| ExodusError::DeserializationError)?; + let binding_commitment = BoundedVec::try_from(binding_commitment_vec) + .map_err(|_| ExodusError::DeserializationError)?; Ok(ExodusNonceCommitment { package_context: self, @@ -389,9 +420,8 @@ impl PackageContext { commitment_vec: Vec, binding_factors_root: ExodusHash, ) -> Result, ExodusError> { - let group_commitment = - BoundedVec::>::try_from(commitment_vec) - .map_err(|_| ExodusError::DeserializationError)?; + let group_commitment = BoundedVec::try_from(commitment_vec) + .map_err(|_| ExodusError::DeserializationError)?; Ok(ExodusGroupCommitment { package_context: self, @@ -408,13 +438,11 @@ impl PackageContext { binding_factors_proof_vec: Vec, self_binding_factor_vec: Vec, ) -> Result, ExodusError> { - let signature_share = - BoundedVec::>::try_from(signature_share_vec) - .map_err(|_| ExodusError::DeserializationError)?; + let signature_share = BoundedVec::try_from(signature_share_vec) + .map_err(|_| ExodusError::DeserializationError)?; - let binding_factors_proof = - BoundedVec::>::try_from(binding_factors_proof_vec) - .map_err(|_| ExodusError::DeserializationError)?; + let binding_factors_proof = BoundedVec::try_from(binding_factors_proof_vec) + .map_err(|_| ExodusError::DeserializationError)?; let self_binding_factor = BoundedVec::>::try_from(self_binding_factor_vec) @@ -440,16 +468,15 @@ impl PackageContext { self, package_vec: Vec, ) -> Result, ExodusError> { - let package = - BoundedVec::>::try_from(package_vec) - .map_err(|_| ExodusError::DeserializationError)?; + let package = BoundedVec::try_from(package_vec) + .map_err(|_| ExodusError::DeserializationError)?; Ok(Round1Package { context: self, package }) } pub fn with_dkg_round2( self, - bundle_raw: BundledPackages, + bundle_raw: BundledPackages, ) -> Result, ExodusError> { let bundle: EncryptionBundledPackages = bundle_raw.try_into()?; Ok(Round2Package { context: self, bundle }) @@ -457,18 +484,14 @@ impl PackageContext { pub fn with_dkg_round3( self, - indices_vec: Vec + indices: ParticipantsBitmap, ) -> Result, ExodusError> { - let indices = - BoundedVec::>::try_from(indices_vec) - .map_err(|_| ExodusError::DeserializationError)?; - Ok(Round3Package { context: self, indices }) } pub fn with_dkg_round4( self, - bundle_raw: BundledPackages, + bundle_raw: BundledPackages, ) -> Result, ExodusError> { let bundle: JustificationsBundledPackages = bundle_raw.try_into()?; Ok(Round4Package { context: self, bundle }) @@ -477,16 +500,12 @@ impl PackageContext { pub fn with_dkg_round5( self, verifying_key_vec: Vec, - key_members_vec: Vec, + key_members: ParticipantsBitmap, merkle_root: ExodusHash, ) -> Result, ExodusError> { - let key_members = BoundedVec::>::try_from(key_members_vec) + let verifying_key = BoundedVec::try_from(verifying_key_vec) .map_err(|_| ExodusError::DeserializationError)?; - let verifying_key = - BoundedVec::>::try_from(verifying_key_vec) - .map_err(|_| ExodusError::DeserializationError)?; - Ok(Round5Package { context: self, key_members, verifying_key, merkle_root }) } @@ -495,13 +514,11 @@ impl PackageContext { verifying_share_vec: Vec, merkle_proof_vec: Vec, ) -> Result, ExodusError> { - let verifying_share = - BoundedVec::>::try_from(verifying_share_vec) - .map_err(|_| ExodusError::DeserializationError)?; + let verifying_share = BoundedVec::try_from(verifying_share_vec) + .map_err(|_| ExodusError::DeserializationError)?; - let merkle_proof = - BoundedVec::>::try_from(merkle_proof_vec) - .map_err(|_| ExodusError::DeserializationError)?; + let merkle_proof = BoundedVec::try_from(merkle_proof_vec) + .map_err(|_| ExodusError::DeserializationError)?; Ok(Round6Package { context: self, verifying_share, merkle_proof }) } diff --git a/pallets/helpers/Cargo.toml b/pallets/helpers/Cargo.toml index 2a497b2..07c4e9c 100644 --- a/pallets/helpers/Cargo.toml +++ b/pallets/helpers/Cargo.toml @@ -1,6 +1,6 @@ [package] 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." license.workspace = true authors.workspace = true diff --git a/pallets/helpers/src/bounded_bitmap.rs b/pallets/helpers/src/bounded_bitmap.rs index 8afe3c4..87c1de5 100644 --- a/pallets/helpers/src/bounded_bitmap.rs +++ b/pallets/helpers/src/bounded_bitmap.rs @@ -2,8 +2,8 @@ use codec::{Decode, Encode}; use num_traits::PrimInt; use scale_info::TypeInfo; +use sp_runtime::traits::UniqueSaturatedInto; use sp_arithmetic::traits::AtLeast8BitUnsigned; -use sp_runtime::{traits::UniqueSaturatedInto, RuntimeDebug}; use sp_std::{cmp::{min, max}, marker::PhantomData, vec, vec::Vec}; use ghost_traits::bounded_bitmap as traits; @@ -33,7 +33,7 @@ pub fn validate_bitmap_sizes(authorities: u32) ); } -#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxChunks))] pub struct BoundedBitmap where @@ -43,6 +43,19 @@ where active_bits_count: u32, } +impl core::fmt::Debug for BoundedBitmap +where + MaxChunks: frame_support::traits::Get, +{ + 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 BoundedBitmap where MaxChunks: Get, @@ -54,6 +67,15 @@ where Inner: BitAndAssign + BitOrAssign + BitXorAssign, Inner: Shl + Shr, { + pub fn inner_as_ref(&self) -> &[u8] { + let slice: &[Inner] = self.data.as_ref(); + let byte_len = slice.len() * core::mem::size_of::(); + + unsafe { + core::slice::from_raw_parts(slice.as_ptr() as *const u8, byte_len) + } + } + fn combine(&self, rhs: &Self, op: F) -> Self where Inner: Copy, @@ -146,41 +168,6 @@ where new_bitmap } - fn from_bitmask(values: &Vec) -> Self - where - Outer: AtLeast8BitUnsigned + PrimInt, - { - let value_bits = inner_bits::(); - 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) -> Self where N: UniqueSaturatedInto, @@ -307,6 +294,35 @@ where }) } + fn count_ones_before(&self, index: N) -> Option + where + usize: UniqueSaturatedInto, + N: UniqueSaturatedInto, + { + let index: usize = index.unique_saturated_into(); + + if index >= self.active_bits_count as usize { return None; } + + let shift = inner_shift::(); + 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(&self) -> N where u32: UniqueSaturatedInto, @@ -425,6 +441,14 @@ where self.highest_bit() } + fn count_ones_before(&self, index: N) -> Option + where + usize: UniqueSaturatedInto, + N: UniqueSaturatedInto, + { + self.count_ones_before(index) + } + fn count_ones(&self) -> N where u32: UniqueSaturatedInto, @@ -476,13 +500,6 @@ where Self::empty_from(bitmap_ref) } - fn from_bitmask(values_ref: &Vec) -> Self - where - Outer: AtLeast8BitUnsigned + PrimInt, - { - Self::from_bitmask(values_ref) - } - fn from_vec(values_ref: &Vec) -> Self where Outer: AtLeast8BitUnsigned + TryInto + PartialEq + Copy, @@ -1099,50 +1116,72 @@ mod tests { } #[test] - fn test_from_bitmask_where_each_u8_is_a_bit() { - let byte1 = 77u8; - let byte2 = 42u8; - let byte3 = 69u8; + fn test_count_ones_before_basic() { + let w = <$inner_type>::BITS as usize; + let idx = |x: usize| -> $inner_type { + x.try_into().ok().expect("bit index fits inner type") + }; - let raw_bits: Vec = vec![byte1, byte2, byte3]; - let bitmap = TestBitmap::from_bitmask(&raw_bits); + let mut indices: Vec<$inner_type> = vec![ + 0u8.into(), + 1u8.into(), + 3u8.into() + ]; - let count_ones = raw_bits.iter().map(|b| b.count_ones()).sum::(); - let filled_bits = (raw_bits.len() as u32 - 1) * 8; - let expected_bits = filled_bits + (8 - byte3.leading_zeros()); + indices.extend([idx(w), idx(w + 1), idx(w + 3)] as [$inner_type; 3]); + let bitmap = TestBitmap::from_vec(&indices); - assert_eq!(bitmap.active_bits_count(), expected_bits); - assert_eq!(bitmap.count_ones::(), count_ones); + assert_eq!(bitmap.count_ones_before(0), Some(0)); + 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!(bitmap.contains(0u32)); - assert!(!bitmap.contains(1u32)); - assert!(bitmap.contains(2u32)); - assert!(bitmap.contains(3u32)); - assert!(!bitmap.contains(4u32)); - assert!(!bitmap.contains(5u32)); - assert!(bitmap.contains(6u32)); - assert!(!bitmap.contains(7u32)); + assert_eq!(bitmap.count_ones_before(w + 1), Some(4)); + assert_eq!(bitmap.count_ones_before(w + 4), Some(6)); + assert_eq!(bitmap.count_ones_before(2 * w), Some(6)); + } - // 42 - 01010100 - assert!(!bitmap.contains(8u32)); - assert!(bitmap.contains(9u32)); - assert!(!bitmap.contains(10u32)); - assert!(bitmap.contains(11u32)); - assert!(!bitmap.contains(12u32)); - assert!(bitmap.contains(13u32)); - assert!(!bitmap.contains(14u32)); - assert!(!bitmap.contains(15u32)); + #[test] + fn test_count_ones_before_crossing_elements() { + let w = <$inner_type>::BITS as usize; + let idx = |x: usize| -> $inner_type { + x.try_into().ok().expect("bit index fits inner type") + }; - // 69 - 10100010 - assert!(bitmap.contains(16u32)); - assert!(!bitmap.contains(17u32)); - assert!(bitmap.contains(18u32)); - assert!(!bitmap.contains(19u32)); - assert!(!bitmap.contains(20u32)); - assert!(!bitmap.contains(21u32)); - assert!(bitmap.contains(22u32)); - assert!(!bitmap.contains(23u32)); + let mut bitmap = TestBitmap::empty(2 * w + 1); + for i in [0usize, 1, w, w + 1, w + 2] { + bitmap.insert(idx(i)); + } + + let bits_per_element = core::mem::size_of::<$inner_type>() * 8; + + assert_eq!(bitmap.count_ones_before(bits_per_element), Some(2)); + 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); } } }; diff --git a/pallets/traits/Cargo.toml b/pallets/traits/Cargo.toml index fd24f1b..b61a175 100644 --- a/pallets/traits/Cargo.toml +++ b/pallets/traits/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-traits" -version = "0.4.9" +version = "0.4.10" description = "Shared traits including `GhostHasher`, `NetworkDataBasicHandler`, `BoundedBTreeMap`, `MerkleTree` and more." license.workspace = true authors.workspace = true diff --git a/pallets/traits/src/bounded_bitmap.rs b/pallets/traits/src/bounded_bitmap.rs index fe7ba54..52c98b1 100644 --- a/pallets/traits/src/bounded_bitmap.rs +++ b/pallets/traits/src/bounded_bitmap.rs @@ -20,6 +20,10 @@ pub trait BoundedBitmapReader { fn highest_bit(&self) -> Option where usize: UniqueSaturatedInto; + fn count_ones_before(&self, index: N) -> Option + where + N: UniqueSaturatedInto, + usize: UniqueSaturatedInto; fn count_ones(&self) -> N where u32: UniqueSaturatedInto; @@ -40,9 +44,6 @@ pub trait BoundedBitmapGenerator { where N: UniqueSaturatedInto; fn empty_from(bitmap: &Self) -> Self; - fn from_bitmask(values: &Vec) -> Self - where - Outer: AtLeast8BitUnsigned + num_traits::PrimInt; fn from_vec(values: &Vec) -> Self where Outer: AtLeast8BitUnsigned + TryInto + PartialEq + Copy;