Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
parent
1dc61fb17d
commit
653bf08486
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ghost-exodus"
|
||||
version = "0.0.25"
|
||||
version = "0.0.26"
|
||||
description = "Threshold signature generation with DKG included"
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
@ -146,6 +146,7 @@ const fn round1_package_size(
|
||||
.saturating_add(commitments_bytes_len)
|
||||
}
|
||||
|
||||
// TODO: revisit and make it correctly compute
|
||||
const fn encrypted_ciphertext_bytes_len(
|
||||
scalar_bytes_len: usize,
|
||||
header_bytes_len: usize,
|
||||
@ -451,7 +452,7 @@ pub mod pallet {
|
||||
authority_index: AuthIndex,
|
||||
network_curve: NetworkCurve,
|
||||
},
|
||||
Round4JustificatPackage {
|
||||
Round4JustificationPackage {
|
||||
justifications_count: AuthIndex,
|
||||
authority_index: AuthIndex,
|
||||
network_curve: NetworkCurve,
|
||||
@ -502,11 +503,11 @@ pub mod pallet {
|
||||
MemberNotQualified,
|
||||
InvalidMerkleProof,
|
||||
NetworkDoesNotExist,
|
||||
NoActiveAuthorities,
|
||||
VerifyingShareNotFound,
|
||||
BridgeQueueMinimumNotMet,
|
||||
DkgAuthoritiesInProgress,
|
||||
DkgAuthoritiesNotInitialized,
|
||||
DkgQualificationNotInitialized,
|
||||
|
||||
Round0PackageInvalidProof,
|
||||
Round0PackageAlreadyRegistered,
|
||||
@ -963,26 +964,29 @@ pub mod pallet {
|
||||
return Err(Error::<T>::Round1PackageInvalidProof.into());
|
||||
};
|
||||
|
||||
let state = QualificationDkgState::<T>::get(&network_curve);
|
||||
ensure!(state.is_first_phase(), Error::<T>::DkgWrongRound);
|
||||
|
||||
let hash1 = Round0Packages::<T>::get(&network_curve, authority_index);
|
||||
let hash2 = ExodusHash::from(blake2_256(&round1.package));
|
||||
ensure!(!hash1.is_zero() && hash1 == hash2, Error::<T>::Round1PackageBadHash);
|
||||
|
||||
let needed_commitment_count = QualificationAuthorities::<T>::decode_len(&network_curve)
|
||||
.map(|max_signers| {
|
||||
get_byzantium_quorum_threshold(max_signers as AuthIndex)
|
||||
})
|
||||
.ok_or(Error::<T>::DkgQualificationNotInitialized)?;
|
||||
|
||||
let estimated_commitment_count =
|
||||
network_curve.dkg_verify_proof_of_knowledge(
|
||||
authority_index,
|
||||
&round1.package,
|
||||
).map_err(|_| Error::<T>::Round1PackageInvalidProof)?;
|
||||
|
||||
let state = QualificationDkgState::<T>::get(&network_curve);
|
||||
ensure!(state.is_first_phase(), Error::<T>::DkgWrongRound);
|
||||
|
||||
state.count_ones::<AuthIndex>().checked_sub(1)
|
||||
.map(|needed_commitment_count| {
|
||||
estimated_commitment_count
|
||||
.eq(&needed_commitment_count)
|
||||
.then(|| ())
|
||||
})
|
||||
.ok_or(Error::<T>::Round1PackageInvalidLength)?;
|
||||
ensure!(
|
||||
estimated_commitment_count == needed_commitment_count,
|
||||
Error::<T>::Round1PackageInvalidLength,
|
||||
);
|
||||
|
||||
Round1Packages::<T>::try_mutate(
|
||||
&network_curve,
|
||||
@ -1191,7 +1195,7 @@ pub mod pallet {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::<T>::Round4JustificatPackage {
|
||||
Self::deposit_event(Event::<T>::Round4JustificationPackage {
|
||||
justifications_count,
|
||||
authority_index,
|
||||
network_curve,
|
||||
@ -4173,36 +4177,12 @@ impl<T: Config> Pallet<T> {
|
||||
network_curve,
|
||||
);
|
||||
|
||||
let padded_participants = qualification_state
|
||||
.highest_bit::<usize>()
|
||||
.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 round2_packages =
|
||||
Self::gather_incoming_offchain_packages(
|
||||
network_curve,
|
||||
authority_index,
|
||||
ROUND_NUMBER_2,
|
||||
&qualification_state.get_indexes(),
|
||||
blob_len.saturating_div(padded_participants),
|
||||
|mut raw_slice| EncryptionData::decode(&mut raw_slice).ok(),
|
||||
);
|
||||
|
||||
let (indices, decrypted) =
|
||||
Self::ecdh_decrypt_round2_packages(
|
||||
&qualification_state,
|
||||
&round1_packages,
|
||||
&secret_package,
|
||||
&round2_packages,
|
||||
authority_index,
|
||||
bitmask_len,
|
||||
qualification_state.get_dkg_index(),
|
||||
network_curve,
|
||||
);
|
||||
|
||||
@ -4620,19 +4600,12 @@ impl<T: Config> Pallet<T> {
|
||||
network_curve: NetworkCurve,
|
||||
authority_index: AuthIndex,
|
||||
) {
|
||||
let padded_participants = qualification_state
|
||||
.highest_bit::<usize>()
|
||||
.map(|bit_pos| bit_pos.saturating_add(1))
|
||||
.unwrap_or_default();
|
||||
|
||||
let blob_len = round2_package_size(
|
||||
padded_participants,
|
||||
let round2_package_size = round2_package_size(
|
||||
1,
|
||||
network_curve.scalar_bytes_len(),
|
||||
network_curve.header_bytes_len(),
|
||||
);
|
||||
|
||||
let round2_package_size = blob_len.saturating_div(padded_participants);
|
||||
|
||||
let mut kicked = sp_std::collections::btree_set::BTreeSet::new();
|
||||
let mut all_justifications = sp_std::collections::btree_map::BTreeMap::new();
|
||||
|
||||
@ -4749,42 +4722,6 @@ impl<T: Config> Pallet<T> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn gather_incoming_offchain_packages<R, E, F>(
|
||||
network_curve: NetworkCurve,
|
||||
authority_index: AuthIndex,
|
||||
round: u8,
|
||||
participants: &ParticipantsBitmap<T>,
|
||||
single_bytes_len: usize,
|
||||
decode_fn: F
|
||||
) -> E
|
||||
where
|
||||
E: FromIterator<(AuthIndex, R)>,
|
||||
F: Fn(&[u8]) -> Option<R>,
|
||||
{
|
||||
participants
|
||||
.iter::<AuthIndex>()
|
||||
.filter(|&index| index != authority_index)
|
||||
.filter_map(|index| {
|
||||
let key = Self::create_offchain_dkg_key(network_curve, index, round);
|
||||
let bundle = StorageValueRef::persistent(&key)
|
||||
.get::<BundledPackages>()
|
||||
.ok()??;
|
||||
|
||||
let (start_offset, end_offset) =
|
||||
Self::locate_package_bounds(
|
||||
&bundle,
|
||||
single_bytes_len,
|
||||
authority_index,
|
||||
)?;
|
||||
|
||||
let raw_encrypted_slice = &bundle.blob[start_offset..end_offset];
|
||||
let decoded_data = decode_fn(raw_encrypted_slice)?;
|
||||
|
||||
Some((index, decoded_data))
|
||||
})
|
||||
.collect::<E>()
|
||||
}
|
||||
|
||||
fn clear_incoming_offchain_packages(
|
||||
network_curve: NetworkCurve,
|
||||
rounds: &[u8],
|
||||
@ -4797,55 +4734,83 @@ impl<T: Config> Pallet<T> {
|
||||
}
|
||||
|
||||
fn ecdh_decrypt_round2_packages(
|
||||
qualification_state: &QualifyingState<T>,
|
||||
round1_packages: &BTreeMap<AuthIndex, Vec<u8>>,
|
||||
round1_secret_package: &Vec<u8>,
|
||||
round2_packages: &BTreeMap<AuthIndex, EncryptionData<T>>,
|
||||
authority_index: AuthIndex,
|
||||
bitvec_len: usize,
|
||||
dkg_index: DkgIndex,
|
||||
network_curve: NetworkCurve,
|
||||
) -> (Vec<u8>, BTreeMap<AuthIndex, Vec<u8>>) {
|
||||
round2_packages
|
||||
let dkg_index = qualification_state.get_dkg_index();
|
||||
|
||||
let bitmask_len = qualification_state
|
||||
.highest_bit::<usize>()
|
||||
.map(|bit_pos| {
|
||||
bit_pos.saturating_add(1).div_ceil(8)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let encryption_size = round2_encrypted_package_size(
|
||||
1,
|
||||
network_curve.scalar_bytes_len(),
|
||||
network_curve.header_bytes_len(),
|
||||
);
|
||||
|
||||
round1_packages
|
||||
.iter()
|
||||
.filter(|(&sender_index, _)| sender_index != authority_index)
|
||||
.filter_map(|(sender_index, encrypted_round2_packages)| {
|
||||
let round1_sender_package = round1_packages.get(&sender_index)?;
|
||||
.map(|(&sender_index, round1_sender_package)| {
|
||||
let key = Self::create_offchain_dkg_key(network_curve, sender_index, ROUND_NUMBER_2);
|
||||
|
||||
let info = network_curve.ecdh_prepare_additional_info(
|
||||
*sender_index,
|
||||
authority_index,
|
||||
dkg_index,
|
||||
sp_std::marker::PhantomData::<EncryptionData<T>>,
|
||||
);
|
||||
|
||||
let decryption_result = network_curve
|
||||
.ecdh_get_cipher_from_keys(
|
||||
&round1_sender_package,
|
||||
&round1_secret_package,
|
||||
&info,
|
||||
sp_std::marker::PhantomData::<EncryptionData<T>>,
|
||||
)
|
||||
.and_then(|cipher| {
|
||||
network_curve.ecdh_decrypt_package(
|
||||
&cipher,
|
||||
&encrypted_round2_packages,
|
||||
&info,
|
||||
)
|
||||
let decryption_result = StorageValueRef::persistent(&key)
|
||||
.get::<BundledPackages>()
|
||||
.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()
|
||||
})
|
||||
})
|
||||
})
|
||||
.and_then(|decrypted| {
|
||||
network_curve.dkg_verify_private_package(
|
||||
.ok_or(sender_index)
|
||||
.and_then(|encrypted_round2_package: EncryptionData<T>| {
|
||||
let info = network_curve.ecdh_prepare_additional_info(
|
||||
sender_index,
|
||||
authority_index,
|
||||
&round1_sender_package,
|
||||
&decrypted,
|
||||
).map(|_| decrypted)
|
||||
})
|
||||
.map_err(|_| *sender_index)
|
||||
.map(|decrypted_package| (*sender_index, decrypted_package));
|
||||
dkg_index,
|
||||
sp_std::marker::PhantomData::<EncryptionData<T>>,
|
||||
);
|
||||
|
||||
Some(decryption_result)
|
||||
network_curve
|
||||
.ecdh_get_cipher_from_keys(
|
||||
round1_sender_package,
|
||||
round1_secret_package,
|
||||
&info,
|
||||
sp_std::marker::PhantomData::<EncryptionData<T>>,
|
||||
)
|
||||
.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));
|
||||
|
||||
decryption_result
|
||||
})
|
||||
.fold(
|
||||
(vec![0u8; bitvec_len], BTreeMap::new()),
|
||||
(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); },
|
||||
|
||||
@ -1042,6 +1042,99 @@ fn run_dkg_session(
|
||||
assert_ne!(prev_active_authorities, active_authorities);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 authorities: Vec<UintAuthorityId> = all_validators.iter().map(|&id| id.into()).collect();
|
||||
ext.execute_with(|| Exodus::start_dkg_qualification(authorities, NetworkCurve::iter()));
|
||||
|
||||
run_to_next_round(&mut ext, curve);
|
||||
for &authority in &all_validators {
|
||||
assert_ok!(execute_round0_happy_path(&mut ext, &tx_pool_state, curve, authority));
|
||||
}
|
||||
run_to_next_round(&mut ext, curve);
|
||||
for &authority in &all_validators {
|
||||
assert_ok!(execute_round1_happy_path(&mut ext, &tx_pool_state, curve, authority));
|
||||
}
|
||||
|
||||
run_to_next_round(&mut ext, curve);
|
||||
|
||||
for &authority in &honest_validators {
|
||||
assert_ok!(execute_round2_happy_path(&mut ext, &tx_pool_state, curve, authority));
|
||||
}
|
||||
|
||||
ext.execute_with(|| {
|
||||
let dkg_index = QualificationDkgState::<Runtime>::get(curve).get_dkg_index();
|
||||
|
||||
let padded_participants = 10usize;
|
||||
let bitmask_len = padded_participants.div_ceil(8);
|
||||
|
||||
let mut perfect_bitmask = vec![0u8; bitmask_len];
|
||||
for &recipient in &honest_validators {
|
||||
let idx = recipient as usize;
|
||||
perfect_bitmask[idx >> 3] |= 1 << (idx & 7);
|
||||
}
|
||||
|
||||
let expected_blob_len = round2_encrypted_package_size(
|
||||
padded_participants,
|
||||
curve.scalar_bytes_len(),
|
||||
curve.header_bytes_len(),
|
||||
);
|
||||
|
||||
let malformed_blob = vec![0xAAu8; expected_blob_len];
|
||||
|
||||
let dummy_encryption_bundle = BundledPackages {
|
||||
bitmask: perfect_bitmask,
|
||||
blob: malformed_blob,
|
||||
};
|
||||
|
||||
let dummy_context = PackageContext::default()
|
||||
.with_authority_index(malicious_validator as AuthIndex)
|
||||
.with_network_curve(curve)
|
||||
.with_dkg_index(dkg_index)
|
||||
.with_dkg_round2(dummy_encryption_bundle)
|
||||
.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();
|
||||
|
||||
assert_ok!(Exodus::register_encrypted_round2_packages(
|
||||
frame_system::RawOrigin::None.into(),
|
||||
dummy_package,
|
||||
dummy_signature
|
||||
));
|
||||
});
|
||||
|
||||
run_to_next_round(&mut ext, curve);
|
||||
|
||||
for &authority in &honest_validators {
|
||||
assert_ok!(execute_round3_happy_path(&mut ext, &tx_pool_state, curve, authority));
|
||||
}
|
||||
|
||||
ext.execute_with(|| {
|
||||
let complaints = Complaints::<Runtime>::get(curve);
|
||||
|
||||
for &accuser in &honest_validators {
|
||||
let accuser_bitmap = complaints.get(&(accuser as AuthIndex)).unwrap();
|
||||
assert!(accuser_bitmap.contains(malicious_validator as AuthIndex));
|
||||
}
|
||||
});
|
||||
|
||||
run_to_next_round(&mut ext, curve);
|
||||
|
||||
for &authority in &honest_validators {
|
||||
assert_ok!(execute_round4_happy_path(&mut ext, &tx_pool_state, curve, authority));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exodus_dkg_distributed_synced_exclusion() {
|
||||
let (mut ext, tx_pool_state) = new_test_ext();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user