happy path for key validation

This commit is contained in:
Jędrzej Stuczyński
2024-01-23 13:47:10 +00:00
parent c31561d46d
commit bd7eebf463
19 changed files with 898 additions and 494 deletions
@@ -64,7 +64,14 @@ pub fn to_cosmos_msg(
Ok(msg)
}
// DKG SAFETY:
// each legit verification proposal will only contain a single execute msg,
// if they have more than one, we can safely ignore it
pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<Addr> {
if msgs.len() != 1 {
return None
}
if let Some(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: _,
msg,
+6 -4
View File
@@ -228,10 +228,11 @@ pub fn check_vk_pairing(
// safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine
#[allow(clippy::unwrap_used)]
if &vk.alpha != *dkg_values.last().as_ref().unwrap() {
if &vk.alpha != *dkg_values.first().as_ref().unwrap() {
return false;
}
if dkg_values
let dkg_betas = &dkg_values[1..];
if dkg_betas
.iter()
.zip(vk.beta_g2.iter())
.any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta)
@@ -329,8 +330,9 @@ mod tests {
let params = setup(2).unwrap();
let keypair = keygen(&params);
let vk = keypair.verification_key();
let mut dkg_values = vk.beta_g2.clone();
dkg_values.push(vk.alpha);
let mut dkg_values = vec![vk.alpha];
dkg_values.append(&mut vk.beta_g2.clone());
assert!(check_vk_pairing(&params, &dkg_values, vk));
}
+2
View File
@@ -19,6 +19,8 @@ use nym_validator_client::nyxd::{AccountId, Fee, Hash, TxResponse};
#[async_trait]
pub trait Client {
async fn address(&self) -> AccountId;
async fn dkg_contract_address(&self) -> Result<AccountId>;
async fn get_tx(&self, tx_hash: Hash) -> Result<TxResponse>;
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse>;
async fn list_proposals(&self) -> Result<Vec<ProposalResponse>>;
+4
View File
@@ -35,6 +35,10 @@ impl DkgClient {
self.inner.address().await
}
pub(crate) async fn dkg_contract_address(&self) -> Result<AccountId, CoconutError> {
self.inner.dkg_contract_address().await
}
pub(crate) async fn get_current_epoch(&self) -> Result<Epoch, CoconutError> {
self.inner.get_current_epoch().await
}
+8 -6
View File
@@ -3,9 +3,7 @@
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::controller::error::DkgError;
use crate::coconut::dkg::key_derivation::{
verification_key_finalization, verification_key_validation,
};
use crate::coconut::dkg::key_finalization::verification_key_finalization;
use crate::coconut::dkg::state::{ConsistentState, PersistentState, State};
use crate::coconut::keys::KeyPair as CoconutKeyPair;
use crate::nyxd;
@@ -158,17 +156,19 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
async fn handle_verification_key_validation(
&mut self,
epoch_id: EpochId,
resharing: bool,
) -> Result<(), DkgError> {
debug!("DKG: verification key validation (resharing: {resharing})");
verification_key_validation(&self.dkg_client, &mut self.state, resharing)
self.verification_key_validation(epoch_id, resharing)
.await
.map_err(|source| DkgError::VerificationKeyValidationFailure { source })
}
async fn handle_verification_key_finalization(
&mut self,
epoch_id: EpochId,
resharing: bool,
) -> Result<(), DkgError> {
debug!("DKG: verification key finalization (resharing: {resharing})");
@@ -218,10 +218,12 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
.await?
}
EpochState::VerificationKeyValidation { resharing } => {
self.handle_verification_key_validation(resharing).await?
self.handle_verification_key_validation(epoch.epoch_id, resharing)
.await?
}
EpochState::VerificationKeyFinalization { resharing } => {
self.handle_verification_key_finalization(resharing).await?
self.handle_verification_key_finalization(epoch.epoch_id, resharing)
.await?
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => self.handle_in_progress().await?,
+8 -13
View File
@@ -60,16 +60,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
// ASSUMPTION: all dealers see the same contract data, i.e. if one fails to decode and verify the receiver's key,
// all of them will
let filtered_receivers: BTreeMap<_, _> = self
.state
.dkg_state(epoch_id)?
.dealers
.iter()
.filter_map(|(index, dealer)| match &dealer.state {
ParticipantState::Invalid(_) => None,
ParticipantState::VerifiedKey(key) => Some((*index, *key.public_key())),
})
.collect();
let filtered_receivers = self.state.valid_epoch_receivers_keys(epoch_id)?;
let dbg_receivers = filtered_receivers.keys().collect::<Vec<_>>();
debug!("generating dealings with threshold {threshold} for receivers: {dbg_receivers:?} with the following spec: {spec:?}. Our index is {dealer_index}");
@@ -283,7 +274,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
.expected_threshold = Some(threshold);
// establish our receiver index
let sorted_dealers = &self.state.dkg_state(epoch_id)?.dealers;
let sorted_dealers = &self.state.dkg_state(epoch_id)?.dealing_exchange.dealers;
let Some(receiver_index) = sorted_dealers.keys().position(|idx| idx == &dealer_index)
else {
// this branch should be impossible as `dealing_exchange` should never be called unless we're actually a dealer
@@ -452,7 +443,11 @@ pub(crate) mod tests {
let key_size = controller.dkg_client.get_contract_state().await?.key_size;
// initial state
assert!(controller.state.dkg_state(epoch)?.dealers.is_empty());
assert!(controller
.state
.dealing_exchange_state(epoch)?
.dealers
.is_empty());
assert!(controller
.state
.dealing_exchange_state(epoch)?
@@ -473,7 +468,7 @@ pub(crate) mod tests {
.collect::<Vec<_>>();
let dealers = controller
.state
.dkg_state(epoch)?
.dealing_exchange_state(epoch)?
.dealers
.values()
.map(|p| (p.assigned_index, p.unwrap_key()))
+193 -420
View File
@@ -24,192 +24,194 @@ use nym_dkg::bte::{decrypt_share, PublicKey};
use nym_dkg::error::DkgError;
use nym_dkg::{bte, combine_shares, try_recover_verification_keys, Dealing, Threshold};
use nym_pemstore::KeyPairPath;
use nym_validator_client::nyxd::bip32::secp256k1::elliptic_curve::group;
use nym_validator_client::nyxd::bip32::secp256k1::elliptic_curve::group::GroupEncoding;
use nym_validator_client::nyxd::cosmwasm_client::logs::find_attribute;
use rand::{CryptoRng, RngCore};
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
// Filter the dealers based on what dealing they posted (or not) in the contract
// TODO: change the return type to make sure that:
// - each entry has the same number of dealings
// - dealer data is not duplicated
// - each dealer has submitted all or nothing
async fn deterministic_filter_dealers(
dkg_client: &DkgClient,
state: &mut State,
epoch_id: EpochId,
threshold: Threshold,
resharing: bool,
) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
// if we're in resharing mode, the contract itself will forbid submission of dealings from
// parties that were not "initial" dealers, so we don't have to worry about it
let mut dealings_maps = Vec::new();
let initial_dealers_by_addr = state.current_dealers_by_addr();
let initial_receivers = state.current_dealers_by_idx();
let initial_resharing_dealers = if resharing {
dkg_client
.get_initial_dealers()
.await?
.map(|d| d.initial_dealers)
.unwrap_or_default()
} else {
vec![]
};
let params = dkg::params();
// note: this is a temporary solution to replicate the behaviour of the old code so that I wouldn't need to
// fix the filtering in this PR, because the old code is quite buggy and misses few edge cases
let mut raw_dealings = HashMap::new();
for dealer in state.all_dealers().keys() {
let dealer_dealings = dkg_client
.get_dealings(epoch_id, dealer.to_string())
.await?;
for dealing in dealer_dealings {
let old_contract_dealing = raw_dealings.entry(dealing.index).or_insert(Vec::new());
old_contract_dealing.push((dealer.clone(), dealing.data))
}
}
// this is a temporary thing to reintroduce the bug to make sure tests still pass : )
// i will fix it properly in next PR
for dealing_index in 0..5 {
let dealings = raw_dealings.remove(&dealing_index).unwrap_or_default();
let dealings_map =
BTreeMap::from_iter(dealings.into_iter().filter_map(|(dealer, dealing)| {
match Dealing::try_from(&dealing) {
Ok(dealing) => {
if dealing
.verify(params, threshold, &initial_receivers, None)
.is_err()
{
state.mark_bad_dealer(
&dealer,
ComplaintReason::DealingVerificationError,
);
None
} else {
initial_dealers_by_addr
.get(&dealer)
.map(|idx| (*idx, (dealer, dealing)))
}
}
Err(_) => {
state.mark_bad_dealer(&dealer, ComplaintReason::MalformedDealing);
None
}
}
}));
dealings_maps.push(dealings_map);
}
//
//
// for dealer in initial_dealers_by_addr.keys() {
// let Some(dealer_index) = initial_dealers_by_addr.get(dealer) else {
// warn!("could not obtain dealer index of {dealer}");
// continue;
// };
//
// let dealer_dealings = dkg_client
// .get_dealings(epoch_id, dealer.to_string())
// .await?;
//
// for contract_dealing in dealer_dealings {
// match Dealing::try_from(&contract_dealing.data) {
// // FIXME: bug: this doesn't check resharing
// Ok(dealing) => {
// if let Err(err) = dealing.verify(params, threshold, &initial_receivers, None) {
// println!("dealing verification failure from {dealer}: {err}");
// state.mark_bad_dealer(dealer, ComplaintReason::DealingVerificationError);
// } else {
// let entry = dealings_maps
// .entry(contract_dealing.index)
// .or_insert(BTreeMap::new());
// entry.insert(*dealer_index, (dealer.clone(), dealing));
// }
// }
// Err(err) => {
// warn!("malformed dealing from {dealer}: {err}");
// state.mark_bad_dealer(dealer, ComplaintReason::MalformedDealing);
// }
// }
// }
// }
for (addr, _) in initial_dealers_by_addr.iter() {
// in resharing mode, we don't commit dealings from dealers outside the initial set
if !resharing || initial_resharing_dealers.contains(addr) {
for dealings_map in dealings_maps.iter() {
if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
break;
}
}
}
}
Ok(dealings_maps)
}
fn derive_partial_keypair(
state: &mut State,
threshold: Threshold,
dealings_maps: Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>,
) -> Result<KeyPair, CoconutError> {
let filtered_receivers_by_idx = state.current_dealers_by_idx();
let filtered_dealers_by_addr = state.current_dealers_by_addr();
let dk = state.dkg_keypair().private_key();
let node_index_value = state.receiver_index_value()?;
let mut scalars = vec![];
let mut recovered_vks = vec![];
for dealings_map in dealings_maps.into_iter() {
let (filtered_dealers, filtered_dealings): (Vec<_>, Vec<_>) = dealings_map
.into_iter()
.filter_map(|(idx, (addr, dealing))| {
if filtered_dealers_by_addr.keys().any(|a| addr == *a) {
Some((idx, dealing))
} else {
None
}
})
.unzip();
debug!(
"Recovering verification keys from dealings of dealers {:?} with receivers {:?}",
filtered_dealers,
filtered_receivers_by_idx.keys().collect::<Vec<_>>()
);
let recovered = try_recover_verification_keys(
&filtered_dealings,
threshold,
&filtered_receivers_by_idx,
)?;
recovered_vks.push(recovered);
debug!("Decrypting shares");
let shares = filtered_dealings
.iter()
.map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None))
.collect::<Result<_, _>>()?;
debug!("Combining shares into one secret");
let scalar = combine_shares(shares, &filtered_dealers)?;
scalars.push(scalar);
}
state.set_recovered_vks(recovered_vks);
let x = scalars.pop().ok_or(CoconutError::DkgError(
DkgError::NotEnoughDealingsAvailable {
available: 0,
required: 1,
},
))?;
let sk = SecretKey::create_from_raw(x, scalars);
let vk = sk.verification_key(&BANDWIDTH_CREDENTIAL_PARAMS);
Ok(CoconutKeyPair::from_keys(sk, vk))
}
// // Filter the dealers based on what dealing they posted (or not) in the contract
//
// // TODO: change the return type to make sure that:
// // - each entry has the same number of dealings
// // - dealer data is not duplicated
// // - each dealer has submitted all or nothing
// async fn deterministic_filter_dealers(
// dkg_client: &DkgClient,
// state: &mut State,
// epoch_id: EpochId,
// threshold: Threshold,
// resharing: bool,
// ) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
// // if we're in resharing mode, the contract itself will forbid submission of dealings from
// // parties that were not "initial" dealers, so we don't have to worry about it
//
// let mut dealings_maps = Vec::new();
// let initial_dealers_by_addr = state.current_dealers_by_addr();
// let initial_receivers = state.current_dealers_by_idx();
// let initial_resharing_dealers = if resharing {
// dkg_client
// .get_initial_dealers()
// .await?
// .map(|d| d.initial_dealers)
// .unwrap_or_default()
// } else {
// vec![]
// };
//
// let params = dkg::params();
//
// // note: this is a temporary solution to replicate the behaviour of the old code so that I wouldn't need to
// // fix the filtering in this PR, because the old code is quite buggy and misses few edge cases
// let mut raw_dealings = HashMap::new();
// for dealer in state.all_dealers().keys() {
// let dealer_dealings = dkg_client
// .get_dealings(epoch_id, dealer.to_string())
// .await?;
// for dealing in dealer_dealings {
// let old_contract_dealing = raw_dealings.entry(dealing.index).or_insert(Vec::new());
// old_contract_dealing.push((dealer.clone(), dealing.data))
// }
// }
//
// // this is a temporary thing to reintroduce the bug to make sure tests still pass : )
// // i will fix it properly in next PR
// for dealing_index in 0..5 {
// let dealings = raw_dealings.remove(&dealing_index).unwrap_or_default();
// let dealings_map =
// BTreeMap::from_iter(dealings.into_iter().filter_map(|(dealer, dealing)| {
// match Dealing::try_from(&dealing) {
// Ok(dealing) => {
// if dealing
// .verify(params, threshold, &initial_receivers, None)
// .is_err()
// {
// state.mark_bad_dealer(
// &dealer,
// ComplaintReason::DealingVerificationError,
// );
// None
// } else {
// initial_dealers_by_addr
// .get(&dealer)
// .map(|idx| (*idx, (dealer, dealing)))
// }
// }
// Err(_) => {
// state.mark_bad_dealer(&dealer, ComplaintReason::MalformedDealing);
// None
// }
// }
// }));
// dealings_maps.push(dealings_map);
// }
//
// //
// //
// // for dealer in initial_dealers_by_addr.keys() {
// // let Some(dealer_index) = initial_dealers_by_addr.get(dealer) else {
// // warn!("could not obtain dealer index of {dealer}");
// // continue;
// // };
// //
// // let dealer_dealings = dkg_client
// // .get_dealings(epoch_id, dealer.to_string())
// // .await?;
// //
// // for contract_dealing in dealer_dealings {
// // match Dealing::try_from(&contract_dealing.data) {
// // // FIXME: bug: this doesn't check resharing
// // Ok(dealing) => {
// // if let Err(err) = dealing.verify(params, threshold, &initial_receivers, None) {
// // println!("dealing verification failure from {dealer}: {err}");
// // state.mark_bad_dealer(dealer, ComplaintReason::DealingVerificationError);
// // } else {
// // let entry = dealings_maps
// // .entry(contract_dealing.index)
// // .or_insert(BTreeMap::new());
// // entry.insert(*dealer_index, (dealer.clone(), dealing));
// // }
// // }
// // Err(err) => {
// // warn!("malformed dealing from {dealer}: {err}");
// // state.mark_bad_dealer(dealer, ComplaintReason::MalformedDealing);
// // }
// // }
// // }
// // }
//
// for (addr, _) in initial_dealers_by_addr.iter() {
// // in resharing mode, we don't commit dealings from dealers outside the initial set
// if !resharing || initial_resharing_dealers.contains(addr) {
// for dealings_map in dealings_maps.iter() {
// if !dealings_map.iter().any(|(_, (address, _))| address == addr) {
// state.mark_bad_dealer(addr, ComplaintReason::MissingDealing);
// break;
// }
// }
// }
// }
//
// Ok(dealings_maps)
// }
//
// fn derive_partial_keypair(
// state: &mut State,
// threshold: Threshold,
// dealings_maps: Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>,
// ) -> Result<KeyPair, CoconutError> {
// let filtered_receivers_by_idx = state.current_dealers_by_idx();
// let filtered_dealers_by_addr = state.current_dealers_by_addr();
// let dk = state.dkg_keypair().private_key();
// let node_index_value = state.receiver_index_value()?;
// let mut scalars = vec![];
// let mut recovered_vks = vec![];
// for dealings_map in dealings_maps.into_iter() {
// let (filtered_dealers, filtered_dealings): (Vec<_>, Vec<_>) = dealings_map
// .into_iter()
// .filter_map(|(idx, (addr, dealing))| {
// if filtered_dealers_by_addr.keys().any(|a| addr == *a) {
// Some((idx, dealing))
// } else {
// None
// }
// })
// .unzip();
// debug!(
// "Recovering verification keys from dealings of dealers {:?} with receivers {:?}",
// filtered_dealers,
// filtered_receivers_by_idx.keys().collect::<Vec<_>>()
// );
// let recovered = try_recover_verification_keys(
// &filtered_dealings,
// threshold,
// &filtered_receivers_by_idx,
// )?;
// recovered_vks.push(recovered);
//
// debug!("Decrypting shares");
// let shares = filtered_dealings
// .iter()
// .map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None))
// .collect::<Result<_, _>>()?;
// debug!("Combining shares into one secret");
// let scalar = combine_shares(shares, &filtered_dealers)?;
// scalars.push(scalar);
// }
// state.set_recovered_vks(recovered_vks);
//
// let x = scalars.pop().ok_or(CoconutError::DkgError(
// DkgError::NotEnoughDealingsAvailable {
// available: 0,
// required: 1,
// },
// ))?;
// let sk = SecretKey::create_from_raw(x, scalars);
// let vk = sk.verification_key(&BANDWIDTH_CREDENTIAL_PARAMS);
//
// Ok(CoconutKeyPair::from_keys(sk, vk))
// }
impl<R: RngCore + CryptoRng> DkgController<R> {
fn verified_dealer_dealings(
@@ -380,8 +382,6 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
let all_dealers = dealings[&0].keys().copied().collect::<Vec<_>>();
let decryption_key = self.state.dkg_keypair().private_key();
let mut derived_x = None;
let mut derived_secrets = Vec::new();
@@ -397,19 +397,19 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
debug!("recovering the partial verification keys");
let recovered =
try_recover_verification_keys(&dealings_vec, threshold, &epoch_receivers)?;
// TODO:
self.state
.key_derivation_state_mut(epoch_id)?
.derived_partials
.insert(dealing_index, recovered);
debug!("decrypting received shares");
// for every received share of the key
let mut shares = Vec::with_capacity(dealings_vec.len());
for (i, dealing) in dealings_vec.into_iter().enumerate() {
// attempt to decrypt our portion
let share = match decrypt_share(
decryption_key,
receiver_index,
&dealing.ciphertexts,
None,
) {
let dk = self.state.dkg_keypair().private_key();
let share = match decrypt_share(dk, receiver_index, &dealing.ciphertexts, None) {
Ok(share) => share,
Err(err) => {
let node_index = all_dealers[i];
@@ -582,106 +582,6 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
}
}
fn validate_proposal(proposal: &ProposalResponse) -> Option<(Addr, u64)> {
if proposal.status == Status::Open {
if let Some(owner) = owner_from_cosmos_msgs(&proposal.msgs) {
return Some((owner, proposal.id));
}
}
None
}
pub(crate) async fn verification_key_validation(
dkg_client: &DkgClient,
state: &mut State,
_resharing: bool,
) -> Result<(), CoconutError> {
if state.voted_vks() {
debug!("Already voted on the verification keys, nothing to do");
return Ok(());
}
let epoch_id = dkg_client.get_current_epoch().await?.epoch_id;
let vk_shares = dkg_client.get_verification_key_shares(epoch_id).await?;
let proposal_ids = BTreeMap::from_iter(
dkg_client
.list_proposals()
.await?
.iter()
.filter_map(validate_proposal),
);
let filtered_receivers_by_idx: Vec<_> =
state.current_dealers_by_idx().keys().copied().collect();
let recovered_partials: Vec<_> = state
.recovered_vks()
.iter()
.map(|recovered_vk| recovered_vk.recovered_partials.clone())
.collect();
let recovered_partials = transpose_matrix(recovered_partials);
let params = &BANDWIDTH_CREDENTIAL_PARAMS;
for contract_share in vk_shares {
if let Some(proposal_id) = proposal_ids.get(&contract_share.owner).copied() {
match VerificationKey::try_from_bs58(contract_share.share) {
Ok(vk) => {
if let Some(idx) = filtered_receivers_by_idx
.iter()
.position(|node_index| contract_share.node_index == *node_index)
{
let ret = if !check_vk_pairing(params, &recovered_partials[idx], &vk) {
debug!(
"Voting NO to proposal {} because of failed VK pairing",
proposal_id
);
dkg_client
.vote_verification_key_share(proposal_id, false)
.await
} else {
debug!("Voting YES to proposal {}", proposal_id);
dkg_client
.vote_verification_key_share(proposal_id, true)
.await
};
accepted_vote_err(ret)?;
}
}
Err(_) => {
debug!(
"Voting NO to proposal {} because of failed base 58 deserialization",
proposal_id
);
let ret = dkg_client
.vote_verification_key_share(proposal_id, false)
.await;
accepted_vote_err(ret)?;
}
}
}
}
state.set_voted_vks();
info!("DKG: Validated the other verification keys");
Ok(())
}
pub(crate) async fn verification_key_finalization(
dkg_client: &DkgClient,
state: &mut State,
_resharing: bool,
) -> Result<(), CoconutError> {
if state.executed_proposal() {
debug!("Already executed the proposal, nothing to do");
return Ok(());
}
let proposal_id = state.proposal_id_value()?;
dkg_client
.execute_verification_key_share(proposal_id)
.await?;
state.set_executed_proposal();
info!("DKG: Finalized own verification key on chain");
Ok(())
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
@@ -1095,11 +995,10 @@ pub(crate) mod tests {
submit_public_keys(&mut controllers, false).await;
exchange_dealings(&mut controllers, false).await;
let chain = controllers[0].chain_state.clone();
for controller in controllers.iter_mut() {
let res = controller.verification_key_submission(epoch, false).await;
assert!(res.is_ok());
derive_keypairs(&mut controllers, false).await;
for controller in controllers {
assert!(controller.state.key_derivation_state(epoch)?.completed);
let keys = controller.state.take_coconut_keypair().await;
assert!(keys.is_some());
@@ -1107,132 +1006,6 @@ pub(crate) mod tests {
}
Ok(())
// let db = MockContractDb::new();
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
//
// for controller in clients_and_states.iter_mut() {
// assert!(db
// .proposal_db
// .read()
// .unwrap()
// .contains_key(&controller.state.proposal_id_value().unwrap()));
// assert!(controller.state.coconut_keypair_is_some().await);
// }
}
#[tokio::test]
#[ignore] // expensive test
async fn validate_verification_key() {
todo!()
// let db = MockContractDb::new();
// let mut clients_and_states = prepare_clients_and_states_with_validation(&db).await;
// for controller in clients_and_states.iter_mut() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// assert_eq!(proposal.status, Status::Passed);
// }
}
#[tokio::test]
#[ignore] // expensive test
async fn validate_verification_key_malformed_share() {
todo!()
// let db = MockContractDb::new();
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
//
// db.verification_share_db
// .write()
// .unwrap()
// .entry(TEST_VALIDATORS_ADDRESS[0].to_string())
// .and_modify(|share| share.share.push('x'));
//
// for controller in clients_and_states.iter_mut() {
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
// .await
// .unwrap();
// }
//
// for (idx, controller) in clients_and_states.iter().enumerate() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// if idx == 0 {
// assert_eq!(proposal.status, Status::Rejected);
// } else {
// assert_eq!(proposal.status, Status::Passed);
// }
// }
}
#[tokio::test]
#[ignore] // expensive test
async fn validate_verification_key_unpaired_share() {
todo!()
// let db = MockContractDb::new();
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
//
// let second_share = db
// .verification_share_db
// .write()
// .unwrap()
// .get(TEST_VALIDATORS_ADDRESS[1])
// .unwrap()
// .share
// .clone();
// db.verification_share_db
// .write()
// .unwrap()
// .entry(TEST_VALIDATORS_ADDRESS[0].to_string())
// .and_modify(|share| share.share = second_share);
//
// for controller in clients_and_states.iter_mut() {
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
// .await
// .unwrap();
// }
//
// for (idx, controller) in clients_and_states.iter().enumerate() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// if idx == 0 {
// assert_eq!(proposal.status, Status::Rejected);
// } else {
// assert_eq!(proposal.status, Status::Passed);
// }
// }
}
#[tokio::test]
#[ignore] // expensive test
async fn finalize_verification_key() {
todo!()
// let db = MockContractDb::new();
// let clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
//
// for controller in clients_and_states.iter() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// assert_eq!(proposal.status, Status::Executed);
// }
}
#[tokio::test]
@@ -0,0 +1,54 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::controller::DkgController;
use crate::coconut::dkg::state::{ConsistentState, State};
use crate::coconut::error::CoconutError;
use rand::{CryptoRng, RngCore};
impl<R: RngCore + CryptoRng> DkgController<R> {}
pub(crate) async fn verification_key_finalization(
dkg_client: &DkgClient,
state: &mut State,
_resharing: bool,
) -> Result<(), CoconutError> {
if state.executed_proposal() {
log::debug!("Already executed the proposal, nothing to do");
return Ok(());
}
let proposal_id = state.proposal_id_value()?;
dkg_client
.execute_verification_key_share(proposal_id)
.await?;
state.set_executed_proposal();
info!("DKG: Finalized own verification key on chain");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore] // expensive test
async fn finalize_verification_key() {
todo!()
// let db = MockContractDb::new();
// let clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
//
// for controller in clients_and_states.iter() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// assert_eq!(proposal.status, Status::Executed);
// }
}
}
+393
View File
@@ -0,0 +1,393 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::api_routes::epoch_credentials;
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::controller::DkgController;
use crate::coconut::dkg::state::State;
use crate::coconut::error::CoconutError;
use crate::coconut::helpers::accepted_vote_err;
use crate::coconut::state::BANDWIDTH_CREDENTIAL_PARAMS;
use cosmwasm_std::Addr;
use cw3::{ProposalResponse, Status};
use nym_coconut::tests::helpers::transpose_matrix;
use nym_coconut::{check_vk_pairing, Base58, VerificationKey};
use nym_coconut_dkg_common::types::EpochId;
use nym_coconut_dkg_common::verification_key::{owner_from_cosmos_msgs, ContractVKShare};
use nym_validator_client::nyxd::AccountId;
use rand::{CryptoRng, RngCore};
use std::collections::HashMap;
impl<R: RngCore + CryptoRng> DkgController<R> {
fn filter_proposal(
&self,
dkg_contract: &AccountId,
proposal: &ProposalResponse,
) -> Option<(Addr, u64)> {
// make sure the proposal we're checking is:
// - still open (not point in voting for anything that has already expired)
// - was proposed by the DKG contract - so that we'd ignore anything from malicious dealers
// - contains valid verification request (checked inside `owner_from_cosmos_msgs`)
if proposal.status == Status::Open && proposal.proposer.as_str() == dkg_contract.as_ref() {
if let Some(owner) = owner_from_cosmos_msgs(&proposal.msgs) {
return Some((owner, proposal.id));
}
}
None
}
async fn get_validation_proposals(&self) -> Result<HashMap<Addr, u64>, CoconutError> {
let dkg_contract = self.dkg_client.dkg_contract_address().await?;
// FUTURE OPTIMIZATION: don't query for ALL proposals. say if we're in epoch 50,
// we don't care about expired proposals from epochs 0-49...
// to do it, we'll need to have dkg contract store proposal ids,
// which will require usage of submsgs and replies so that might be a future project
let all_proposals = self.dkg_client.list_proposals().await?;
let mut deduped_proposals = HashMap::new();
// for each proposal, make sure it's a valid validation request;
// if for some reason there exist multiple proposals from the same owner, choose the one
// with the higher id
for proposal in all_proposals {
if let Some((owner, id)) = self.filter_proposal(&dkg_contract, &proposal) {
if let Some(old_id) = deduped_proposals.get(&owner) {
if old_id < &id {
deduped_proposals.insert(owner, id);
}
} else {
deduped_proposals.insert(owner, id);
}
}
}
// UNHANDLED EDGE CASE:
// since currently proposals are **NOT** tied to epochs,
// we might run into proposals from older epochs we don't have to vote on or might not even have data for
Ok(deduped_proposals)
}
async fn verify_share(
&self,
epoch_id: EpochId,
share: ContractVKShare,
) -> Result<(), CoconutError> {
if share.verified {
todo!()
}
let Some(receiver_index) = self
.state
.valid_epoch_receivers(epoch_id)?
.iter()
.position(|(addr, _)| addr == share.owner)
else {
todo!()
};
// EDGE CASE:
// make sure the receiver index of this receiver/dealer is within the size of the derived keys
// attempt to recover the underlying key from its bs58 representation
let recovered_key = match VerificationKey::try_from_bs58(share.share) {
Ok(key) => key,
Err(err) => {
warn!(
"failed to decode verification share from {}: {err}",
share.owner
);
todo!()
}
};
let Some(self_derived) = self
.state
.key_derivation_state(epoch_id)?
.derived_partials_for(receiver_index)
else {
todo!()
};
if !check_vk_pairing(&BANDWIDTH_CREDENTIAL_PARAMS, &self_derived, &recovered_key) {
todo!()
}
Ok(())
}
pub(crate) async fn verification_key_validation(
&mut self,
epoch_id: EpochId,
resharing: bool,
) -> Result<(), CoconutError> {
let key_validation_state = self.state.key_validation_state(epoch_id)?;
// check if we have already validated and voted for all keys
if key_validation_state.completed() {
// the only way this could be a false positive is if the chain forked and blocks got reverted,
// but I don't think we have to worry about that
debug!("we have already voted in all validation proposals");
return Ok(());
}
// FAILURE CASE:
// check if we have already verified the keys, but some voting txs either didn't get executed
// or got executed without us knowing about it
if !key_validation_state.votes.is_empty() {
debug!(
"we have already validated all keys for this epoch, but might have failed to vote"
);
// dkg_client.query_vote to check if our vote is there
todo!()
}
let proposals = self.get_validation_proposals().await?;
let vk_shares = self
.dkg_client
.get_verification_key_shares(epoch_id)
.await?;
for contract_share in vk_shares {
// there's no point in checking anything if there doesn't exist an associated multisig proposal
let Some(proposal_id) = proposals.get(&contract_share.owner) else {
warn!(
"there does not seem to exist proposal for vk share from {}",
contract_share.owner
);
continue;
};
let vote = if let Err(err) = self.verify_share(epoch_id, contract_share).await {
todo!();
false
} else {
true
};
self.state
.key_validation_state_mut(epoch_id)?
.votes
.insert(*proposal_id, vote);
}
// do vote
for (&proposal, &vote) in &self.state.key_validation_state(epoch_id)?.votes {
// FUTURE OPTIMIZATION: we could batch them in a single tx
self.dkg_client
.vote_verification_key_share(proposal, vote)
.await?;
}
self.state.key_validation_state_mut(epoch_id)?.completed = true;
// if self.state.voted_vks() {
// log::debug!("Already voted on the verification keys, nothing to do");
// return Ok(());
// }
//
// let epoch_id = self.dkg_client.get_current_epoch().await?.epoch_id;
// let vk_shares = self
// .dkg_client
// .get_verification_key_shares(epoch_id)
// .await?;
// let proposal_ids = BTreeMap::from_iter(
// self.dkg_client
// .list_proposals()
// .await?
// .iter()
// .filter_map(validate_proposal),
// );
// let filtered_receivers_by_idx: Vec<_> = self
// .state
// .current_dealers_by_idx()
// .keys()
// .copied()
// .collect();
// let recovered_partials: Vec<_> = self
// .state
// .recovered_vks()
// .iter()
// .map(|recovered_vk| recovered_vk.recovered_partials.clone())
// .collect();
// let recovered_partials = transpose_matrix(recovered_partials);
// let params = &BANDWIDTH_CREDENTIAL_PARAMS;
// for contract_share in vk_shares {
// if let Some(proposal_id) = proposal_ids.get(&contract_share.owner).copied() {
// match VerificationKey::try_from_bs58(contract_share.share) {
// Ok(vk) => {
// if let Some(idx) = filtered_receivers_by_idx
// .iter()
// .position(|node_index| contract_share.node_index == *node_index)
// {
// let ret = if !check_vk_pairing(params, &recovered_partials[idx], &vk) {
// log::debug!(
// "Voting NO to proposal {} because of failed VK pairing",
// proposal_id
// );
// self.dkg_client
// .vote_verification_key_share(proposal_id, false)
// .await
// } else {
// log::debug!("Voting YES to proposal {}", proposal_id);
// self.dkg_client
// .vote_verification_key_share(proposal_id, true)
// .await
// };
// accepted_vote_err(ret)?;
// }
// }
// Err(_) => {
// log::debug!(
// "Voting NO to proposal {} because of failed base 58 deserialization",
// proposal_id
// );
// let ret = self
// .dkg_client
// .vote_verification_key_share(proposal_id, false)
// .await;
// accepted_vote_err(ret)?;
// }
// }
// }
// }
// self.state.set_voted_vks();
info!("DKG: validated all the other verification keys");
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coconut::tests::helpers::{
derive_keypairs, exchange_dealings, initialise_controllers, initialise_dkg,
submit_public_keys,
};
#[tokio::test]
#[ignore] // expensive test
async fn validate_verification_key() -> anyhow::Result<()> {
let unused = 42;
let validators = 1;
let mut controllers = initialise_controllers(validators);
let chain = controllers[0].chain_state.clone();
let epoch = chain.lock().unwrap().dkg_epoch.epoch_id;
initialise_dkg(&mut controllers, false);
submit_public_keys(&mut controllers, false).await;
exchange_dealings(&mut controllers, false).await;
derive_keypairs(&mut controllers, false).await;
for controller in controllers.iter_mut() {
let res = controller.verification_key_validation(epoch, false).await;
assert!(res.is_ok());
assert!(controller.state.key_validation_state(epoch)?.completed);
}
let chain = controllers[0].chain_state.clone();
let guard = chain.lock().unwrap();
let proposals = &guard.proposals;
assert_eq!(proposals.len(), validators);
for proposal in proposals.values() {
assert_eq!(Status::Passed, proposal.status)
}
Ok(())
// let db = MockContractDb::new();
// let mut clients_and_states = prepare_clients_and_states_with_validation(&db).await;
// for controller in clients_and_states.iter_mut() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// assert_eq!(proposal.status, Status::Passed);
// }
}
#[tokio::test]
#[ignore] // expensive test
async fn validate_verification_key_malformed_share() {
todo!()
// let db = MockContractDb::new();
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
//
// db.verification_share_db
// .write()
// .unwrap()
// .entry(TEST_VALIDATORS_ADDRESS[0].to_string())
// .and_modify(|share| share.share.push('x'));
//
// for controller in clients_and_states.iter_mut() {
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
// .await
// .unwrap();
// }
//
// for (idx, controller) in clients_and_states.iter().enumerate() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// if idx == 0 {
// assert_eq!(proposal.status, Status::Rejected);
// } else {
// assert_eq!(proposal.status, Status::Passed);
// }
// }
}
#[tokio::test]
#[ignore] // expensive test
async fn validate_verification_key_unpaired_share() {
todo!()
// let db = MockContractDb::new();
// let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await;
//
// let second_share = db
// .verification_share_db
// .write()
// .unwrap()
// .get(TEST_VALIDATORS_ADDRESS[1])
// .unwrap()
// .share
// .clone();
// db.verification_share_db
// .write()
// .unwrap()
// .entry(TEST_VALIDATORS_ADDRESS[0].to_string())
// .and_modify(|share| share.share = second_share);
//
// for controller in clients_and_states.iter_mut() {
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
// .await
// .unwrap();
// }
//
// for (idx, controller) in clients_and_states.iter().enumerate() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// if idx == 0 {
// assert_eq!(proposal.status, Status::Rejected);
// } else {
// assert_eq!(proposal.status, Status::Passed);
// }
// }
}
}
+2
View File
@@ -13,5 +13,7 @@ pub(crate) mod complaints;
pub(crate) mod controller;
pub(crate) mod dealing;
pub(crate) mod key_derivation;
mod key_finalization;
mod key_validation;
pub(crate) mod public_key;
pub(crate) mod state;
@@ -5,11 +5,13 @@ use super::serde_helpers::generated_dealings;
use nym_coconut_dkg_common::types::DealingIndex;
use nym_dkg::{Dealing, NodeIndex};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use crate::coconut::dkg::state::DkgParticipant;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct DealingExchangeState {
// pub(crate) assigned_index: Option<NodeIndex>,
pub(crate) dealers: BTreeMap<NodeIndex, DkgParticipant>,
#[serde(with = "generated_dealings")]
pub(crate) generated_dealings: HashMap<DealingIndex, Dealing>,
@@ -1,12 +1,20 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_dkg::Threshold;
use super::serde_helpers::recovered_keys;
use nym_coconut_dkg_common::types::DealingIndex;
use nym_dkg::{G2Projective, RecoveredVerificationKeys, Threshold};
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
type ReceiverIndex = usize;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct KeyDerivationState {
pub(crate) expected_threshold: Option<Threshold>, // pub(crate) completed: bool,
pub(crate) expected_threshold: Option<Threshold>,
#[serde(with = "recovered_keys")]
pub(crate) derived_partials: BTreeMap<DealingIndex, RecoveredVerificationKeys>,
pub(crate) completed: bool,
// because we couldn't decrypt shares or there were no shares, etc
@@ -14,6 +22,19 @@ pub struct KeyDerivationState {
}
impl KeyDerivationState {
pub fn derived_partials_for(&self, receiver_index: ReceiverIndex) -> Option<Vec<G2Projective>> {
let mut recovered = Vec::new();
for keys in self.derived_partials.values() {
// SAFETY:
// make sure the receiver index of this receiver/dealer is within the size of the derived keys
if keys.recovered_partials.len() <= receiver_index {
return None;
};
recovered.push(keys.recovered_partials[receiver_index])
}
Some(recovered)
}
pub fn completed(&self) -> bool {
self.completed
}
@@ -0,0 +1,14 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct FinalizationState {}
impl FinalizationState {
/// Specifies whether this dealer has already registered in the particular DKG epoch
pub fn completed(&self) -> bool {
todo!()
}
}
@@ -0,0 +1,21 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
type ProposalId = u64;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ValidationState {
pub votes: HashMap<ProposalId, bool>,
pub completed: bool,
}
impl ValidationState {
/// Specifies whether this dealer has already registered in the particular DKG epoch
pub fn completed(&self) -> bool {
self.completed
}
}
+53 -8
View File
@@ -26,9 +26,13 @@ use std::path::{Path, PathBuf};
use time::OffsetDateTime;
use tokio::sync::RwLockReadGuard;
use url::Url;
use crate::coconut::dkg::state::key_finalization::FinalizationState;
use crate::coconut::dkg::state::key_validation::ValidationState;
mod dealing_exchange;
mod key_derivation;
mod key_finalization;
mod key_validation;
mod registration;
mod serde_helpers;
@@ -242,18 +246,19 @@ impl PersistentState {
#[derive(Default)]
pub(crate) struct DkgState {
pub(crate) registration: RegistrationState,
// available after registration is finished:
pub(crate) dealers: BTreeMap<NodeIndex, DkgParticipant>,
pub(crate) dealing_exchange: DealingExchangeState,
pub(crate) key_generation: KeyDerivationState,
pub(crate) key_validation: ValidationState,
pub(crate) key_finalization: FinalizationState,
}
impl DkgState {
pub(crate) fn set_dealers(&mut self, raw_dealers: Vec<DealerDetails>) {
assert!(self.dealers.is_empty());
assert!(self.dealing_exchange.dealers.is_empty());
for raw_dealer in raw_dealers {
let dkg_participant = DkgParticipant::from(raw_dealer);
if let ParticipantState::Invalid(complaint) = &dkg_participant.state {
@@ -262,7 +267,7 @@ impl DkgState {
dkg_participant.address
)
}
self.dealers
self.dealing_exchange.dealers
.insert(dkg_participant.assigned_index, dkg_participant);
}
}
@@ -365,7 +370,7 @@ impl State {
epoch_id: EpochId,
) -> Result<Vec<(Addr, NodeIndex)>, CoconutError> {
Ok(self
.dkg_state(epoch_id)?
.dealing_exchange_state(epoch_id)?
.dealers
.values()
.filter_map(|d| {
@@ -383,7 +388,7 @@ impl State {
epoch_id: EpochId,
) -> Result<BTreeMap<NodeIndex, bte::PublicKey>, CoconutError> {
Ok(self
.dkg_state(epoch_id)?
.dealing_exchange_state(epoch_id)?
.dealers
.values()
.filter_map(|d| d.state.public_key().map(|k| (d.assigned_index, k)))
@@ -462,6 +467,46 @@ impl State {
.ok_or(CoconutError::MissingDkgState { epoch_id })
}
pub fn key_validation_state(
&self,
epoch_id: EpochId,
) -> Result<&ValidationState, CoconutError> {
self.dkg_instances
.get(&epoch_id)
.map(|state| &state.key_validation)
.ok_or(CoconutError::MissingDkgState { epoch_id })
}
pub fn key_validation_state_mut(
&mut self,
epoch_id: EpochId,
) -> Result<&mut ValidationState, CoconutError> {
self.dkg_instances
.get_mut(&epoch_id)
.map(|state| &mut state.key_validation)
.ok_or(CoconutError::MissingDkgState { epoch_id })
}
pub fn key_finalization_state(
&self,
epoch_id: EpochId,
) -> Result<&FinalizationState, CoconutError> {
self.dkg_instances
.get(&epoch_id)
.map(|state| &state.key_finalization)
.ok_or(CoconutError::MissingDkgState { epoch_id })
}
pub fn key_finalization_state_mut(
&mut self,
epoch_id: EpochId,
) -> Result<&mut FinalizationState, CoconutError> {
self.dkg_instances
.get_mut(&epoch_id)
.map(|state| &mut state.key_finalization)
.ok_or(CoconutError::MissingDkgState { epoch_id })
}
pub fn threshold(&self, epoch_id: EpochId) -> Result<Threshold, CoconutError> {
self.key_derivation_state(epoch_id)?
.expected_threshold
@@ -50,6 +50,44 @@ pub(super) mod vks_serde {
}
}
pub(super) mod recovered_keys {
use nym_coconut_dkg_common::types::DealingIndex;
use nym_dkg::RecoveredVerificationKeys;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::{BTreeMap, HashMap};
type Helper = BTreeMap<DealingIndex, Vec<u8>>;
pub fn serialize<S: Serializer>(
val: &BTreeMap<DealingIndex, RecoveredVerificationKeys>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let helper: Helper = val
.iter()
.map(|(idx, rec)| (*idx, rec.to_bytes()))
.collect();
helper.serialize(serializer)
}
pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<BTreeMap<DealingIndex, RecoveredVerificationKeys>, D::Error>
where
D: Deserializer<'de>,
{
let helper = Helper::deserialize(deserializer)?;
helper
.into_iter()
.map(|(idx, rec)| {
RecoveredVerificationKeys::try_from_bytes(&rec)
.map_err(|err| D::Error::custom(format_args!("{:?}", err)))
.map(|vk| (idx, vk))
})
.collect()
}
}
pub(super) mod generated_dealings {
use nym_coconut_dkg_common::types::DealingIndex;
use nym_dkg::Dealing;
+33 -19
View File
@@ -2,9 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::dkg::controller::DkgController;
use crate::coconut::dkg::key_derivation::{
verification_key_finalization, verification_key_validation,
};
use crate::coconut::tests::fixtures::{TestingDkgController, TestingDkgControllerBuilder};
use crate::coconut::tests::FakeChainState;
use nym_coconut_dkg_common::types::EpochState;
@@ -110,23 +107,40 @@ pub(crate) async fn derive_keypairs(controllers: &mut [TestingDkgController], re
}
pub(crate) async fn validate_keys(controllers: &mut [TestingDkgController], resharing: bool) {
todo!()
// let mut clients_and_states = derive_keypairs(db).await;
// for controller in clients_and_states.iter_mut() {
// verification_key_validation(&controller.dkg_client, &mut controller.state, false)
// .await
// .unwrap();
// }
// clients_and_states
let epoch = controllers[0]
.chain_state
.lock()
.unwrap()
.dkg_epoch
.epoch_id;
for controller in controllers.iter_mut() {
controller
.verification_key_validation(epoch, resharing)
.await
.unwrap();
}
let mut guard = controllers[0].chain_state.lock().unwrap();
guard.dkg_epoch.state = EpochState::VerificationKeyFinalization { resharing }
}
pub(crate) async fn finalize(controllers: &mut [TestingDkgController], resharing: bool) {
todo!()
// let mut clients_and_states = validate_keys(db).await;
// for controller in clients_and_states.iter_mut() {
// verification_key_finalization(&controller.dkg_client, &mut controller.state, false)
// .await
// .unwrap();
// }
// clients_and_states
let epoch = controllers[0]
.chain_state
.lock()
.unwrap()
.dkg_epoch
.epoch_id;
for controller in controllers.iter_mut() {
todo!()
// controller
// .verification_key_finalization(epoch, resharing)
// .await
// .unwrap();
}
let mut guard = controllers[0].chain_state.lock().unwrap();
guard.dkg_epoch.state = EpochState::InProgress {}
}
+26 -20
View File
@@ -64,6 +64,8 @@ const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8g
#[derive(Debug)]
pub(crate) struct FakeChainState {
pub(crate) dkg_contract: AccountId,
pub(crate) dealers: HashMap<NodeIndex, DealerDetails>,
pub(crate) past_dealers: HashMap<NodeIndex, DealerDetails>,
@@ -90,6 +92,7 @@ pub(crate) struct FakeChainState {
impl Default for FakeChainState {
fn default() -> Self {
FakeChainState {
dkg_contract: AccountId::new("n", &[69u8; 32]).unwrap(),
dealers: HashMap::new(),
past_dealers: Default::default(),
@@ -248,6 +251,10 @@ impl super::client::Client for DummyClient {
self.validator_address.clone()
}
async fn dkg_contract_address(&self) -> Result<AccountId> {
Ok(self.state.lock().unwrap().dkg_contract.clone())
}
async fn get_tx(&self, tx_hash: Hash) -> Result<TxResponse> {
todo!()
// Ok(self
@@ -274,15 +281,14 @@ impl super::client::Client for DummyClient {
}
async fn list_proposals(&self) -> Result<Vec<ProposalResponse>> {
todo!()
// Ok(self
// .state
// .lock()
// .unwrap()
// .proposals
// .values()
// .cloned()
// .collect())
Ok(self
.state
.lock()
.unwrap()
.proposals
.values()
.cloned()
.collect())
}
async fn get_spent_credential(
@@ -427,16 +433,15 @@ impl super::client::Client for DummyClient {
vote_yes: bool,
_fee: Option<Fee>,
) -> Result<()> {
todo!()
// if let Some(proposal) = self.state.lock().unwrap().proposals.get_mut(&proposal_id) {
// // for now, just suppose that every vote is honest
// if !vote_yes {
// proposal.status = cw3::Status::Rejected;
// } else if vote_yes && proposal.status == cw3::Status::Open {
// proposal.status = cw3::Status::Passed;
// }
// }
// Ok(())
if let Some(proposal) = self.state.lock().unwrap().proposals.get_mut(&proposal_id) {
// for now, just suppose that every vote is honest
if !vote_yes {
proposal.status = cw3::Status::Rejected;
} else if vote_yes && proposal.status == cw3::Status::Open {
proposal.status = cw3::Status::Passed;
}
}
Ok(())
}
async fn execute_proposal(&self, proposal_id: u64) -> Result<()> {
@@ -541,6 +546,7 @@ impl super::client::Client for DummyClient {
resharing: bool,
) -> Result<ExecuteResult> {
let address = self.validator_address.to_string();
let dkg_contract = self.state.lock().unwrap().dkg_contract.clone();
let Some(dealer_details) = self.get_dealer_by_address(&address).await else {
// Just throw some error, not really the correct one
@@ -588,7 +594,7 @@ impl super::client::Client for DummyClient {
percentage: Decimal::from_ratio(2u32, 3u32),
total_weight: 100,
},
proposer: Addr::unchecked(self.validator_address.as_ref()),
proposer: Addr::unchecked(dkg_contract.as_ref()),
deposit: None,
};
guard.proposals.insert(proposal_id, proposal);
+9
View File
@@ -346,6 +346,15 @@ impl crate::coconut::client::Client for Client {
self.client_address().await
}
async fn dkg_contract_address(&self) -> Result<AccountId, CoconutError> {
nyxd_query!(
self,
dkg_contract_address()
.cloned()
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract").into())
)
}
async fn get_tx(&self, tx_hash: Hash) -> crate::coconut::error::Result<nyxd::TxResponse> {
nyxd_query!(self, get_tx(tx_hash).await).map_err(|source| {
CoconutError::TxRetrievalFailure {