From 605176551b86fdce38e58b9490a92686427a3aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 22 Jan 2024 16:55:04 +0000 Subject: [PATCH] actually working happy path with a unit test --- contracts/coconut-dkg/src/contract.rs | 5 +- .../src/verification_key_shares/queries.rs | 20 +- .../src/verification_key_shares/storage.rs | 2 - nym-api/src/coconut/api_routes/mod.rs | 5 +- nym-api/src/coconut/dkg/controller/mod.rs | 3 +- nym-api/src/coconut/dkg/dealing.rs | 31 +- nym-api/src/coconut/dkg/key_derivation.rs | 1707 ++++++++--------- nym-api/src/coconut/dkg/state/mod.rs | 35 +- nym-api/src/coconut/error.rs | 6 +- nym-api/src/coconut/keys/mod.rs | 20 +- nym-api/src/coconut/tests/fixtures.rs | 30 +- nym-api/src/coconut/tests/helpers.rs | 122 ++ nym-api/src/coconut/tests/mod.rs | 387 ++-- 13 files changed, 1277 insertions(+), 1096 deletions(-) diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index e6eaa114d0..1bbfb72a70 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -17,7 +17,7 @@ use crate::epoch_state::transactions::{ use crate::error::ContractError; use crate::state::queries::query_state; use crate::state::storage::{DKG_ADMIN, MULTISIG, STATE}; -use crate::verification_key_shares::queries::query_vk_shares_paged; +use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_paged}; use crate::verification_key_shares::transactions::try_commit_verification_key_share; use crate::verification_key_shares::transactions::try_verify_verification_key_share; use cosmwasm_std::{ @@ -159,6 +159,9 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { + to_binary(&query_vk_share(deps, owner, epoch_id)?)? + } QueryMsg::GetVerificationKeys { epoch_id, limit, diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs index 958fd6b68f..77abfd8d6e 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/queries.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -1,4 +1,4 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::verification_key_shares::storage; @@ -6,7 +6,23 @@ use crate::verification_key_shares::storage::vk_shares; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; use nym_coconut_dkg_common::types::EpochId; -use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse; +use nym_coconut_dkg_common::verification_key::{PagedVKSharesResponse, VkShareResponse}; + +// TODO: unit tests +pub fn query_vk_share( + deps: Deps<'_>, + owner: String, + epoch_id: EpochId, +) -> StdResult { + let owner = deps.api.addr_validate(&owner)?; + let share = vk_shares().may_load(deps.storage, (&owner, epoch_id))?; + + Ok(VkShareResponse { + owner, + epoch_id, + share, + }) +} pub fn query_vk_shares_paged( deps: Deps<'_>, diff --git a/contracts/coconut-dkg/src/verification_key_shares/storage.rs b/contracts/coconut-dkg/src/verification_key_shares/storage.rs index 51a7fbb03e..7c65518df4 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/storage.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/storage.rs @@ -1,7 +1,5 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE}; use crate::epoch_state::storage::CURRENT_EPOCH; diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index a406a53126..04e796d272 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -58,7 +58,10 @@ pub async fn post_blind_sign( // check if we have the signing key available debug!("checking if we actually have coconut keys derived..."); - let keypair_guard = state.coconut_keypair.get().await; + let maybe_keypair_guard = state.coconut_keypair.get().await; + let Some(keypair_guard) = maybe_keypair_guard.as_ref() else { + return Err(CoconutError::KeyPairNotDerivedYet); + }; let Some(signing_key) = keypair_guard.as_ref() else { return Err(CoconutError::KeyPairNotDerivedYet); }; diff --git a/nym-api/src/coconut/dkg/controller/mod.rs b/nym-api/src/coconut/dkg/controller/mod.rs index 2a0b782df7..7558ff7d1c 100644 --- a/nym-api/src/coconut/dkg/controller/mod.rs +++ b/nym-api/src/coconut/dkg/controller/mod.rs @@ -302,10 +302,11 @@ impl DkgController { rng: rand_chacha::ChaCha20Rng, dkg_client: DkgClient, state: State, + coconut_key_path: PathBuf, ) -> DkgController { DkgController { dkg_client, - coconut_key_path: Default::default(), + coconut_key_path, state, rng, polling_rate: Default::default(), diff --git a/nym-api/src/coconut/dkg/dealing.rs b/nym-api/src/coconut/dkg/dealing.rs index 92072f0fdd..b58fc06750 100644 --- a/nym-api/src/coconut/dkg/dealing.rs +++ b/nym-api/src/coconut/dkg/dealing.rs @@ -234,13 +234,6 @@ impl DkgController { ) -> Result<(), CoconutError> { let dealing_state = self.state.dealing_exchange_state(epoch_id)?; - // TODO: - /* - let receiver_index = receivers - .keys() - .position(|node_index| *node_index == dealer_index); - */ - // check if we have already submitted the dealings if dealing_state.completed() { // the only way this could be a false positive is if the chain forked and blocks got reverted, @@ -277,6 +270,30 @@ impl DkgController { self.state.dkg_state_mut(epoch_id)?.set_dealers(dealers); + // obtain our dealer index to correctly set receiver index (used for share decryption) + let dealer_index = self.state.assigned_index(epoch_id)?; + + // update internally used threshold value which should have been available after all dealers registered + let Some(threshold) = self.dkg_client.get_current_epoch_threshold().await? else { + // TODO: if we're in the dealing exchange phase, the threshold must have been already established + todo!("yell at me clippy") + }; + self.state + .key_derivation_state_mut(epoch_id)? + .expected_threshold = Some(threshold); + + // establish our receiver index + let sorted_dealers = &self.state.dkg_state(epoch_id)?.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 + error!("could not establish receiver index for epoch {epoch_id} even though we're a dealer!"); + return Err(CoconutError::UnavailableReceiverIndex { epoch_id }); + }; + self.state + .dealing_exchange_state_mut(epoch_id)? + .receiver_index = Some(receiver_index); + // get the expected key size which will determine the number of dealings we need to construct let contract_state = self.dkg_client.get_contract_state().await?; let expected_key_size = contract_state.key_size; diff --git a/nym-api/src/coconut/dkg/key_derivation.rs b/nym-api/src/coconut/dkg/key_derivation.rs index 41fa8a013b..984828de41 100644 --- a/nym-api/src/coconut/dkg/key_derivation.rs +++ b/nym-api/src/coconut/dkg/key_derivation.rs @@ -282,7 +282,7 @@ impl DkgController { let mut valid_dealings: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new(); // 1. for every valid dealer in this epoch, obtain its dealings - for (dealer, dealer_index) in self.state.valid_epoch_dealers(epoch_id)? { + for (dealer, dealer_index) in self.state.valid_epoch_receivers(epoch_id)? { // 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 @@ -316,7 +316,7 @@ impl DkgController { if let Ok(verified_dealings) = self.verified_dealer_dealings( epoch_id, dealer, - &epoch_receivers, + epoch_receivers, raw_dealings, old_public_key, ) { @@ -330,6 +330,24 @@ impl DkgController { } else { todo!("blacklist dealer") } + + // if let Ok(verified_dealings) = self.verified_dealer_dealings( + // epoch_id, + // dealer, + // epoch_receivers, + // raw_dealings, + // old_public_key, + // ) { + // // if we managed to verify ALL the dealings from this dealer, insert them into the map + // for (dealing_index, dealing) in verified_dealings { + // valid_dealings + // .entry(dealing_index) + // .or_default() + // .insert(dealer_index, dealing); + // } + // } else { + // todo!("blacklist dealer") + // } } Ok(valid_dealings) @@ -360,7 +378,7 @@ impl DkgController { todo!("fail - can't reach threshold") } - let TEMP_TODO_all_indices = Vec::new(); + let all_dealers = dealings[&0].keys().copied().collect::>(); let decryption_key = self.state.dkg_keypair().private_key(); @@ -371,17 +389,20 @@ impl DkgController { // for every part of the key for (dealing_index, dealings) in dealings { + let dealings_vec = dealings.into_values().collect::>(); + let human_index = dealing_index + 1; debug!("recovering part {human_index}/{total} of the keys"); debug!("recovering the partial verification keys"); - let recovered = try_recover_verification_keys(&[], threshold, &epoch_receivers)?; + let recovered = + try_recover_verification_keys(&dealings_vec, threshold, &epoch_receivers)?; // TODO: debug!("decrypting received shares"); // for every received share of the key - let mut shares = Vec::with_capacity(dealings.len()); - for (node_index, dealing) in dealings { + 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, @@ -391,6 +412,7 @@ impl DkgController { ) { Ok(share) => share, Err(err) => { + let node_index = all_dealers[i]; error!("failed to decrypt share {human_index}/{total} generated from dealer {node_index}: {err} - can't generate the full key"); todo!("do something about it") } @@ -402,7 +424,7 @@ impl DkgController { // SAFETY: combining shares can only fail if we have different number shares and indices // however, we returned an error if decryption of any share failed and thus we know those values must match - let secret = combine_shares(shares, &TEMP_TODO_all_indices).unwrap(); + let secret = combine_shares(shares, &all_dealers).unwrap(); if derived_x.is_none() { derived_x = Some(secret) } else { @@ -486,7 +508,9 @@ impl DkgController { todo!("finalize, we're done") } - let epoch_receivers = BTreeMap::new(); + // ASSUMPTION: + // all nym-apis would have filtered the dealers the same way since they'd have had the same data + let epoch_receivers = self.state.valid_epoch_receivers_keys(epoch_id)?; let dealings = self .get_valid_dealings(&epoch_receivers, epoch_id, resharing) @@ -661,966 +685,875 @@ pub(crate) async fn verification_key_finalization( #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::coconut::dkg::controller::DkgController; - use crate::coconut::dkg::state::PersistentState; - use crate::coconut::tests::DummyClient; - use crate::coconut::KeyPair; - use nym_coconut::aggregate_verification_keys; - use nym_coconut_dkg_common::dealer::DealerDetails; - use nym_coconut_dkg_common::types::{EpochId, InitialReplacementData, PartialContractDealing}; - use nym_coconut_dkg_common::verification_key::ContractVKShare; - use nym_crypto::asymmetric::identity; - use nym_dkg::bte::keys::KeyPair as DkgKeyPair; - use nym_validator_client::nyxd::AccountId; - use rand::rngs::OsRng; - use rand::Rng; - use rand_07::thread_rng; - use std::collections::HashMap; - use std::env::temp_dir; - use std::path::PathBuf; - use std::str::FromStr; - use std::sync::{Arc, RwLock}; - use url::Url; - - struct MockContractDb { - dealer_details_db: Arc>>, - // it's a really bad practice, but I'm not going to be changing it now... - #[allow(clippy::type_complexity)] - dealings_db: Arc>>>>, - proposal_db: Arc>>, - verification_share_db: Arc>>, - threshold_db: Arc>>, - initial_dealers_db: Arc>>, - } - - impl MockContractDb { - pub fn new() -> Self { - MockContractDb { - dealer_details_db: Arc::new(Default::default()), - dealings_db: Arc::new(Default::default()), - proposal_db: Arc::new(Default::default()), - verification_share_db: Arc::new(Default::default()), - threshold_db: Arc::new(RwLock::new(Some(2))), - initial_dealers_db: Arc::new(RwLock::new(Default::default())), - } - } - } - - const TEST_VALIDATORS_ADDRESS: [&str; 4] = [ - "n1aq9kakfgwqcufr23lsv644apavcntrsqsk4yus", - "n1s9l3xr4g0rglvk4yctktmck3h4eq0gp6z2e20v", - "n19kl4py32vsk297dm93ezem992cdyzdy4zuc2x6", - "n1jfrs6cmw9t7dv0x8cgny6geunzjh56n2s89fkv", - ]; - - async fn prepare_clients_and_states(db: &MockContractDb) -> Vec { - let params = dkg::params(); - let mut clients_and_states = vec![]; - let identity_keypair = identity::KeyPair::new(&mut thread_rng()); - - for addr in TEST_VALIDATORS_ADDRESS { - let dkg_client = DkgClient::new( - DummyClient::new(AccountId::from_str(addr).unwrap()) - .with_dealer_details(&db.dealer_details_db) - .with_dealings(&db.dealings_db) - .with_proposal_db(&db.proposal_db) - .with_verification_share(&db.verification_share_db) - .with_threshold(&db.threshold_db) - .with_initial_dealers_db(&db.initial_dealers_db), - ); - let keypair = DkgKeyPair::new(params, OsRng); - let state = State::new( - PathBuf::default(), - PersistentState::default(), - Url::parse("localhost:8000").unwrap(), - keypair, - *identity_keypair.public_key(), - KeyPair::new(), - ); - clients_and_states.push(DkgController::test_mock(dkg_client, state)); - } - for controller in clients_and_states.iter_mut() { - controller.public_key_submission(0, false).await.unwrap(); - } - clients_and_states - } - - async fn prepare_clients_and_states_with_dealing(db: &MockContractDb) -> Vec { - let mut clients_and_states = prepare_clients_and_states(db).await; - for controller in clients_and_states.iter_mut() { - controller.dealing_exchange(0, false).await.unwrap(); - } - clients_and_states - } - - async fn prepare_clients_and_states_with_submission(db: &MockContractDb) -> Vec { - let mut clients_and_states = prepare_clients_and_states_with_dealing(db).await; - for controller in clients_and_states.iter_mut() { - let random_file: usize = OsRng.gen(); - let keypath = temp_dir().join(format!("coconut{}.pem", random_file)); - verification_key_submission( - &controller.dkg_client, - &mut controller.state, - 0, - &keypath, - false, - ) - .await - .unwrap(); - std::fs::remove_file(keypath).unwrap(); - } - clients_and_states - } - - async fn prepare_clients_and_states_with_validation(db: &MockContractDb) -> Vec { - let mut clients_and_states = prepare_clients_and_states_with_submission(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 - } - - async fn prepare_clients_and_states_with_finalization( - db: &MockContractDb, - ) -> Vec { - let mut clients_and_states = prepare_clients_and_states_with_validation(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 - } + use crate::coconut::tests::helpers::{ + derive_keypairs, exchange_dealings, initialise_controllers, initialise_dkg, + submit_public_keys, + }; #[tokio::test] #[ignore] // expensive test - async fn check_dealers_filter_all_good() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0] - .dkg_client - .get_contract_state() - .await - .unwrap(); + async fn check_dealers_filter_all_good() -> anyhow::Result<()> { + let mut controllers = initialise_controllers(4); + let chain = controllers[0].chain_state.clone(); + let expected = chain.lock().unwrap().dkg_contract_state.clone(); - for controller in clients_and_states.iter_mut() { - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - assert_eq!(filtered.len(), contract_state.key_size as usize); - for mapping in filtered.iter() { - assert_eq!(mapping.len(), 4); - } - } + submit_public_keys(&mut controllers, false).await; + exchange_dealings(&mut controllers, false).await; + + todo!() + // + // for controller in controllers.iter_mut() { + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // assert_eq!(filtered.len(), contract_state.key_size as usize); + // for mapping in filtered.iter() { + // assert_eq!(mapping.len(), 4); + // } + // } + // + // Ok(()) } #[tokio::test] #[ignore] // expensive test async fn check_dealers_filter_one_bad_dealing() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0] - .dkg_client - .get_contract_state() - .await - .unwrap(); - - // corrupt just one dealing - db.dealings_db - .write() - .unwrap() - .entry(0) - .and_modify(|epoch_dealings| { - let validator_dealings = epoch_dealings - .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) - .or_default(); - let mut last = validator_dealings.pop().unwrap(); - last.data.0.pop(); - validator_dealings.push(last); - }); - - for controller in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - assert_eq!(filtered.len(), contract_state.key_size as usize); - let corrupted_status = controller - .state - .all_dealers() - .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) - .unwrap() - .as_ref() - .unwrap_err(); - assert_eq!(*corrupted_status, ComplaintReason::MissingDealing); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; + // let contract_state = clients_and_states[0] + // .dkg_client + // .get_contract_state() + // .await + // .unwrap(); + // + // // corrupt just one dealing + // db.dealings_db + // .write() + // .unwrap() + // .entry(0) + // .and_modify(|epoch_dealings| { + // let validator_dealings = epoch_dealings + // .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) + // .or_default(); + // let mut last = validator_dealings.pop().unwrap(); + // last.data.0.pop(); + // validator_dealings.push(last); + // }); + // + // for controller in clients_and_states.iter_mut().skip(1) { + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // assert_eq!(filtered.len(), contract_state.key_size as usize); + // let corrupted_status = controller + // .state + // .all_dealers() + // .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) + // .unwrap() + // .as_ref() + // .unwrap_err(); + // assert_eq!(*corrupted_status, ComplaintReason::MissingDealing); + // } } #[tokio::test] #[ignore] // expensive test async fn check_dealers_resharing_filter_one_missing_dealing() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states(&db).await; - let contract_state = clients_and_states[0] - .dkg_client - .get_contract_state() - .await - .unwrap(); - - // add all but the first dealing - for controller in clients_and_states.iter_mut().skip(1) { - controller.dealing_exchange(0, true).await.unwrap(); - } - - for controller in clients_and_states.iter_mut().skip(1) { - *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { - initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])], - initial_height: 1, - }); - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - true, - ) - .await - .unwrap(); - assert_eq!(filtered.len(), contract_state.key_size as usize); - let corrupted_status = controller - .state - .all_dealers() - .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) - .unwrap() - .as_ref() - .unwrap_err(); - - assert_eq!(*corrupted_status, ComplaintReason::MissingDealing); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states(&db).await; + // let contract_state = clients_and_states[0] + // .dkg_client + // .get_contract_state() + // .await + // .unwrap(); + // + // // add all but the first dealing + // for controller in clients_and_states.iter_mut().skip(1) { + // controller.dealing_exchange(0, true).await.unwrap(); + // } + // + // for controller in clients_and_states.iter_mut().skip(1) { + // *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { + // initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])], + // initial_height: 1, + // }); + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // true, + // ) + // .await + // .unwrap(); + // assert_eq!(filtered.len(), contract_state.key_size as usize); + // let corrupted_status = controller + // .state + // .all_dealers() + // .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) + // .unwrap() + // .as_ref() + // .unwrap_err(); + // + // assert_eq!(*corrupted_status, ComplaintReason::MissingDealing); + // } } #[tokio::test] #[ignore] // expensive test async fn check_dealers_resharing_filter_one_noninitial_missing_dealing() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states(&db).await; - let contract_state = clients_and_states[0] - .dkg_client - .get_contract_state() - .await - .unwrap(); - - // add all but the first dealing - for controller in clients_and_states.iter_mut().skip(1) { - controller.dealing_exchange(0, true).await.unwrap(); - } - - for controller in clients_and_states.iter_mut().skip(1) { - *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { - initial_dealers: vec![], - initial_height: 1, - }); - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - true, - ) - .await - .unwrap(); - assert_eq!(filtered.len(), contract_state.key_size as usize); - assert!(controller - .state - .all_dealers() - .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) - .unwrap() - .as_ref() - .is_ok(),); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states(&db).await; + // let contract_state = clients_and_states[0] + // .dkg_client + // .get_contract_state() + // .await + // .unwrap(); + // + // // add all but the first dealing + // for controller in clients_and_states.iter_mut().skip(1) { + // controller.dealing_exchange(0, true).await.unwrap(); + // } + // + // for controller in clients_and_states.iter_mut().skip(1) { + // *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { + // initial_dealers: vec![], + // initial_height: 1, + // }); + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // true, + // ) + // .await + // .unwrap(); + // assert_eq!(filtered.len(), contract_state.key_size as usize); + // assert!(controller + // .state + // .all_dealers() + // .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) + // .unwrap() + // .as_ref() + // .is_ok(),); + // } } #[tokio::test] #[ignore] // expensive test async fn check_dealers_filter_all_bad_dealings() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0] - .dkg_client - .get_contract_state() - .await - .unwrap(); - - // corrupt all dealings of one address - db.dealings_db - .write() - .unwrap() - .entry(0) - .and_modify(|epoch_dealings| { - let validator_dealings = epoch_dealings - .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) - .or_default(); - validator_dealings.iter_mut().for_each(|dealing| { - dealing.data.0.pop(); - }); - }); - - for controller in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - assert_eq!(filtered.len(), contract_state.key_size as usize); - for mapping in filtered.iter() { - assert_eq!(mapping.len(), 3); - } - let corrupted_status = controller - .state - .all_dealers() - .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) - .unwrap() - .as_ref() - .unwrap_err(); - assert_eq!(*corrupted_status, ComplaintReason::MissingDealing); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; + // let contract_state = clients_and_states[0] + // .dkg_client + // .get_contract_state() + // .await + // .unwrap(); + // + // // corrupt all dealings of one address + // db.dealings_db + // .write() + // .unwrap() + // .entry(0) + // .and_modify(|epoch_dealings| { + // let validator_dealings = epoch_dealings + // .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) + // .or_default(); + // validator_dealings.iter_mut().for_each(|dealing| { + // dealing.data.0.pop(); + // }); + // }); + // + // for controller in clients_and_states.iter_mut().skip(1) { + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // assert_eq!(filtered.len(), contract_state.key_size as usize); + // for mapping in filtered.iter() { + // assert_eq!(mapping.len(), 3); + // } + // let corrupted_status = controller + // .state + // .all_dealers() + // .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) + // .unwrap() + // .as_ref() + // .unwrap_err(); + // assert_eq!(*corrupted_status, ComplaintReason::MissingDealing); + // } } #[tokio::test] #[ignore] // expensive test async fn check_dealers_filter_malformed_dealing() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0] - .dkg_client - .get_contract_state() - .await - .unwrap(); - - // corrupt just one dealing - db.dealings_db - .write() - .unwrap() - .entry(0) - .and_modify(|epoch_dealings| { - let validator_dealings = epoch_dealings - .get_mut(TEST_VALIDATORS_ADDRESS[0]) - .expect("no dealing"); - let mut last = validator_dealings.pop().unwrap(); - last.data.0.pop(); - validator_dealings.push(last); - }); - - for controller in clients_and_states.iter_mut().skip(1) { - deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - // second filter will leave behind the bad dealer and surface why it was left out - // in the first place - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - assert_eq!(filtered.len(), contract_state.key_size as usize); - let corrupted_status = controller - .state - .all_dealers() - .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) - .unwrap() - .as_ref() - .unwrap_err(); - assert_eq!(*corrupted_status, ComplaintReason::MalformedDealing); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; + // let contract_state = clients_and_states[0] + // .dkg_client + // .get_contract_state() + // .await + // .unwrap(); + // + // // corrupt just one dealing + // db.dealings_db + // .write() + // .unwrap() + // .entry(0) + // .and_modify(|epoch_dealings| { + // let validator_dealings = epoch_dealings + // .get_mut(TEST_VALIDATORS_ADDRESS[0]) + // .expect("no dealing"); + // let mut last = validator_dealings.pop().unwrap(); + // last.data.0.pop(); + // validator_dealings.push(last); + // }); + // + // for controller in clients_and_states.iter_mut().skip(1) { + // deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // // second filter will leave behind the bad dealer and surface why it was left out + // // in the first place + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // assert_eq!(filtered.len(), contract_state.key_size as usize); + // let corrupted_status = controller + // .state + // .all_dealers() + // .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) + // .unwrap() + // .as_ref() + // .unwrap_err(); + // assert_eq!(*corrupted_status, ComplaintReason::MalformedDealing); + // } } #[tokio::test] #[ignore] // expensive test async fn check_dealers_filter_dealing_verification_error() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - let contract_state = clients_and_states[0] - .dkg_client - .get_contract_state() - .await - .unwrap(); - - // corrupt just one dealing - db.dealings_db - .write() - .unwrap() - .entry(0) - .and_modify(|epoch_dealings| { - let validator_dealings = epoch_dealings - .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) - .or_default(); - let mut last = validator_dealings.pop().unwrap(); - let value = last.data.0.pop().unwrap(); - if value == 42 { - last.data.0.push(43); - } else { - last.data.0.push(42); - } - validator_dealings.push(last); - }); - - for controller in clients_and_states.iter_mut().skip(1) { - deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - // second filter will leave behind the bad dealer and surface why it was left out - // in the first place - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - assert_eq!(filtered.len(), contract_state.key_size as usize); - let corrupted_status = controller - .state - .all_dealers() - .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) - .unwrap() - .as_ref() - .unwrap_err(); - assert_eq!(*corrupted_status, ComplaintReason::DealingVerificationError); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; + // let contract_state = clients_and_states[0] + // .dkg_client + // .get_contract_state() + // .await + // .unwrap(); + // + // // corrupt just one dealing + // db.dealings_db + // .write() + // .unwrap() + // .entry(0) + // .and_modify(|epoch_dealings| { + // let validator_dealings = epoch_dealings + // .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) + // .or_default(); + // let mut last = validator_dealings.pop().unwrap(); + // let value = last.data.0.pop().unwrap(); + // if value == 42 { + // last.data.0.push(43); + // } else { + // last.data.0.push(42); + // } + // validator_dealings.push(last); + // }); + // + // for controller in clients_and_states.iter_mut().skip(1) { + // deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // // second filter will leave behind the bad dealer and surface why it was left out + // // in the first place + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // assert_eq!(filtered.len(), contract_state.key_size as usize); + // let corrupted_status = controller + // .state + // .all_dealers() + // .get(&Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])) + // .unwrap() + // .as_ref() + // .unwrap_err(); + // assert_eq!(*corrupted_status, ComplaintReason::DealingVerificationError); + // } } #[tokio::test] #[ignore] // expensive test async fn partial_keypair_derivation() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - for controller in clients_and_states.iter_mut() { - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - assert!(derive_partial_keypair(&mut controller.state, 2, filtered).is_ok()); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; + // for controller in clients_and_states.iter_mut() { + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // assert!(derive_partial_keypair(&mut controller.state, 2, filtered).is_ok()); + // } } #[tokio::test] #[ignore] // expensive test async fn partial_keypair_derivation_with_threshold() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; - - // corrupt just one dealing - db.dealings_db - .write() - .unwrap() - .entry(0) - .and_modify(|epoch_dealings| { - let validator_dealings = epoch_dealings - .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) - .or_default(); - let mut last = validator_dealings.pop().unwrap(); - last.data.0.pop(); - validator_dealings.push(last); - }); - - for controller in clients_and_states.iter_mut().skip(1) { - let filtered = deterministic_filter_dealers( - &controller.dkg_client, - &mut controller.state, - 0, - 2, - false, - ) - .await - .unwrap(); - assert!(derive_partial_keypair(&mut controller.state, 2, filtered).is_ok()); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states_with_dealing(&db).await; + // + // // corrupt just one dealing + // db.dealings_db + // .write() + // .unwrap() + // .entry(0) + // .and_modify(|epoch_dealings| { + // let validator_dealings = epoch_dealings + // .entry(TEST_VALIDATORS_ADDRESS[0].to_string()) + // .or_default(); + // let mut last = validator_dealings.pop().unwrap(); + // last.data.0.pop(); + // validator_dealings.push(last); + // }); + // + // for controller in clients_and_states.iter_mut().skip(1) { + // let filtered = deterministic_filter_dealers( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // 2, + // false, + // ) + // .await + // .unwrap(); + // assert!(derive_partial_keypair(&mut controller.state, 2, filtered).is_ok()); + // } } #[tokio::test] #[ignore] // expensive test - async fn submit_verification_key() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_submission(&db).await; + async fn submit_verification_key() -> anyhow::Result<()> { + let mut controllers = initialise_controllers(4); + let chain = controllers[0].chain_state.clone(); + let epoch = chain.lock().unwrap().dkg_epoch.epoch_id; - 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); + initialise_dkg(&mut controllers, false); + submit_public_keys(&mut controllers, false).await; + exchange_dealings(&mut controllers, false).await; + + let chain = controllers[0].chain_state.clone(); + + 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()); + assert_eq!(keys.as_ref().unwrap().issued_for_epoch, epoch); } + + 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() { - 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); - } + 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() { - 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); - } - } + 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() { - 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); - } - } + 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() { - 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); - } + 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] #[ignore] // expensive test async fn reshare_preserves_keys() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await; - for controller in clients_and_states.iter_mut() { - controller.state.set_was_in_progress(); - } - - let mut vks = vec![]; - let mut indices = vec![]; - for controller in clients_and_states.iter() { - let vk = controller - .state - .coconut_keypair() - .await - .as_ref() - .unwrap() - .keys - .verification_key() - .clone(); - let index = controller.state.node_index().unwrap(); - vks.push(vk); - indices.push(index); - } - let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); - - let new_dkg_client = DkgClient::new( - DummyClient::new( - AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(), - ) - .with_dealer_details(&db.dealer_details_db) - .with_dealings(&db.dealings_db) - .with_proposal_db(&db.proposal_db) - .with_verification_share(&db.verification_share_db) - .with_threshold(&db.threshold_db) - .with_initial_dealers_db(&db.initial_dealers_db), - ); - let keypair = DkgKeyPair::new(dkg::params(), OsRng); - let identity_keypair = identity::KeyPair::new(&mut thread_rng()); - let state = State::new( - PathBuf::default(), - PersistentState::default(), - Url::parse("localhost:8000").unwrap(), - keypair, - *identity_keypair.public_key(), - KeyPair::new(), - ); - - for (_, active) in db.dealer_details_db.write().unwrap().values_mut() { - *active = false; - } - - *db.dealings_db.write().unwrap() = Default::default(); - *db.verification_share_db.write().unwrap() = Default::default(); - let mut initial_dealers = vec![]; - for controller in clients_and_states.iter() { - let client_address = - Addr::unchecked(controller.dkg_client.get_address().await.as_ref()); - initial_dealers.push(client_address); - } - *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { - initial_dealers, - initial_height: 1, - }); - *clients_and_states.first_mut().unwrap() = DkgController::test_mock(new_dkg_client, state); - - for controller in clients_and_states.iter_mut() { - controller.public_key_submission(0, true).await.unwrap(); - controller.dealing_exchange(0, true).await.unwrap(); - } - - for controller in clients_and_states.iter_mut() { - let random_file: usize = OsRng.gen(); - let keypath = temp_dir().join(format!("coconut{}.pem", random_file)); - verification_key_submission( - &controller.dkg_client, - &mut controller.state, - 0, - &keypath, - true, - ) - .await - .unwrap(); - std::fs::remove_file(keypath).unwrap(); - } - for controller in clients_and_states.iter_mut() { - verification_key_validation(&controller.dkg_client, &mut controller.state, true) - .await - .unwrap(); - } - for controller in clients_and_states.iter_mut() { - verification_key_finalization(&controller.dkg_client, &mut controller.state, true) - .await - .unwrap(); - } - assert!(db - .proposal_db - .read() - .unwrap() - .values() - .all(|proposal| { proposal.status == Status::Executed })); - - let mut vks = vec![]; - let mut indices = vec![]; - for controller in clients_and_states.iter() { - let vk = controller - .state - .coconut_keypair() - .await - .as_ref() - .unwrap() - .keys - .verification_key() - .clone(); - let index = controller.state.node_index().unwrap(); - vks.push(vk); - indices.push(index); - } - let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); - assert_eq!(initial_master_vk, reshared_master_vk); - } - - #[tokio::test] - #[ignore] // expensive test - async fn reshare_after_reset() { - let db = MockContractDb::new(); - let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await; - for controller in clients_and_states.iter_mut() { - controller.state.set_was_in_progress(); - } - - let new_dkg_client = DkgClient::new( - DummyClient::new( - AccountId::from_str("n1vxkywf9g4cg0k2dehanzwzz64jw782qm0kuynf").unwrap(), - ) - .with_dealer_details(&db.dealer_details_db) - .with_dealings(&db.dealings_db) - .with_proposal_db(&db.proposal_db) - .with_verification_share(&db.verification_share_db) - .with_threshold(&db.threshold_db) - .with_initial_dealers_db(&db.initial_dealers_db), - ); - let keypair = DkgKeyPair::new(dkg::params(), OsRng); - let identity_keypair = identity::KeyPair::new(&mut thread_rng()); - let state = State::new( - PathBuf::default(), - PersistentState::default(), - Url::parse("localhost:8000").unwrap(), - keypair, - *identity_keypair.public_key(), - KeyPair::new(), - ); - let new_dkg_client2 = DkgClient::new( - DummyClient::new( - AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(), - ) - .with_dealer_details(&db.dealer_details_db) - .with_dealings(&db.dealings_db) - .with_proposal_db(&db.proposal_db) - .with_verification_share(&db.verification_share_db) - .with_threshold(&db.threshold_db) - .with_initial_dealers_db(&db.initial_dealers_db), - ); - let keypair = DkgKeyPair::new(dkg::params(), OsRng); - let identity_keypair = identity::KeyPair::new(&mut thread_rng()); - let state2 = State::new( - PathBuf::default(), - PersistentState::default(), - Url::parse("localhost:8000").unwrap(), - keypair, - *identity_keypair.public_key(), - KeyPair::new(), - ); - - for (_, active) in db.dealer_details_db.write().unwrap().values_mut() { - *active = false; - } - - *db.dealings_db.write().unwrap() = Default::default(); - *db.verification_share_db.write().unwrap() = Default::default(); - clients_and_states.pop().unwrap(); - let controller2 = clients_and_states.pop().unwrap(); - clients_and_states.push(DkgController::test_mock(new_dkg_client, state)); - clients_and_states.push(DkgController::test_mock(new_dkg_client2, state2)); - - // DKG in reset mode - for controller in clients_and_states.iter_mut() { - controller.public_key_submission(0, false).await.unwrap(); - } - for controller in clients_and_states.iter_mut() { - controller.dealing_exchange(0, false).await.unwrap(); - } - for controller in clients_and_states.iter_mut() { - let random_file: usize = OsRng.gen(); - let keypath = temp_dir().join(format!("coconut{}.pem", random_file)); - verification_key_submission( - &controller.dkg_client, - &mut controller.state, - 0, - &keypath, - false, - ) - .await - .unwrap(); - std::fs::remove_file(keypath).unwrap(); - } - for controller in clients_and_states.iter_mut() { - verification_key_validation(&controller.dkg_client, &mut controller.state, false) - .await - .unwrap(); - } - for controller in clients_and_states.iter_mut() { - verification_key_finalization(&controller.dkg_client, &mut controller.state, false) - .await - .unwrap(); - } - assert!(db - .proposal_db - .read() - .unwrap() - .values() - .all(|proposal| { proposal.status == Status::Executed })); - for controller in clients_and_states.iter_mut() { - controller.state.set_was_in_progress(); - } - - // DKG in reshare mode - let mut vks = vec![]; - let mut indices = vec![]; - for controller in clients_and_states.iter() { - let vk = controller - .state - .coconut_keypair() - .await - .as_ref() - .unwrap() - .keys - .verification_key() - .clone(); - let index = controller.state.node_index().unwrap(); - vks.push(vk); - indices.push(index); - } - let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); - - for (_, active) in db.dealer_details_db.write().unwrap().values_mut() { - *active = false; - } - *db.dealings_db.write().unwrap() = Default::default(); - *db.verification_share_db.write().unwrap() = Default::default(); - let mut initial_dealers = vec![]; - for controller in clients_and_states.iter() { - let client_address = - Addr::unchecked(controller.dkg_client.get_address().await.as_ref()); - initial_dealers.push(client_address); - } - *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { - initial_dealers, - initial_height: 1, - }); - *clients_and_states.last_mut().unwrap() = controller2; - - for controller in clients_and_states.iter_mut() { - controller.public_key_submission(0, true).await.unwrap(); - controller.dealing_exchange(0, true).await.unwrap(); - } - - for controller in clients_and_states.iter_mut() { - let random_file: usize = OsRng.gen(); - let keypath = temp_dir().join(format!("coconut{}.pem", random_file)); - verification_key_submission( - &controller.dkg_client, - &mut controller.state, - 0, - &keypath, - true, - ) - .await - .unwrap(); - std::fs::remove_file(keypath).unwrap(); - } - - for controller in clients_and_states.iter_mut() { - verification_key_validation(&controller.dkg_client, &mut controller.state, true) - .await - .unwrap(); - } - for controller in clients_and_states.iter_mut() { - verification_key_finalization(&controller.dkg_client, &mut controller.state, true) - .await - .unwrap(); - } + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await; + // for controller in clients_and_states.iter_mut() { + // controller.state.set_was_in_progress(); + // } + // + // let mut vks = vec![]; + // let mut indices = vec![]; + // for controller in clients_and_states.iter() { + // let vk = controller + // .state + // .coconut_keypair() + // .await + // .as_ref() + // .unwrap() + // .keys + // .verification_key() + // .clone(); + // let index = controller.state.node_index().unwrap(); + // vks.push(vk); + // indices.push(index); + // } + // let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); + // + // let new_dkg_client = DkgClient::new( + // DummyClient::new( + // AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(), + // ) + // .with_dealer_details(&db.dealer_details_db) + // .with_dealings(&db.dealings_db) + // .with_proposal_db(&db.proposal_db) + // .with_verification_share(&db.verification_share_db) + // .with_threshold(&db.threshold_db) + // .with_initial_dealers_db(&db.initial_dealers_db), + // ); + // let keypair = DkgKeyPair::new(dkg::params(), OsRng); + // let identity_keypair = identity::KeyPair::new(&mut thread_rng()); + // let state = State::new( + // PathBuf::default(), + // PersistentState::default(), + // Url::parse("localhost:8000").unwrap(), + // keypair, + // *identity_keypair.public_key(), + // KeyPair::new(), + // ); + // + // for (_, active) in db.dealer_details_db.write().unwrap().values_mut() { + // *active = false; + // } + // + // *db.dealings_db.write().unwrap() = Default::default(); + // *db.verification_share_db.write().unwrap() = Default::default(); + // let mut initial_dealers = vec![]; + // for controller in clients_and_states.iter() { + // let client_address = + // Addr::unchecked(controller.dkg_client.get_address().await.as_ref()); + // initial_dealers.push(client_address); + // } + // *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { + // initial_dealers, + // initial_height: 1, + // }); + // *clients_and_states.first_mut().unwrap() = DkgController::test_mock(new_dkg_client, state); + // + // for controller in clients_and_states.iter_mut() { + // controller.public_key_submission(0, true).await.unwrap(); + // controller.dealing_exchange(0, true).await.unwrap(); + // } + // + // for controller in clients_and_states.iter_mut() { + // let random_file: usize = OsRng.gen(); + // let keypath = temp_dir().join(format!("coconut{}.pem", random_file)); + // verification_key_submission( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // &keypath, + // true, + // ) + // .await + // .unwrap(); + // std::fs::remove_file(keypath).unwrap(); + // } + // for controller in clients_and_states.iter_mut() { + // verification_key_validation(&controller.dkg_client, &mut controller.state, true) + // .await + // .unwrap(); + // } + // for controller in clients_and_states.iter_mut() { + // verification_key_finalization(&controller.dkg_client, &mut controller.state, true) + // .await + // .unwrap(); + // } // assert!(db // .proposal_db // .read() // .unwrap() // .values() // .all(|proposal| { proposal.status == Status::Executed })); + // + // let mut vks = vec![]; + // let mut indices = vec![]; + // for controller in clients_and_states.iter() { + // let vk = controller + // .state + // .coconut_keypair() + // .await + // .as_ref() + // .unwrap() + // .keys + // .verification_key() + // .clone(); + // let index = controller.state.node_index().unwrap(); + // vks.push(vk); + // indices.push(index); + // } + // let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); + // assert_eq!(initial_master_vk, reshared_master_vk); + } - let mut vks = vec![]; - let mut indices = vec![]; - for controller in clients_and_states.iter() { - let vk = controller - .state - .coconut_keypair() - .await - .as_ref() - .unwrap() - .keys - .verification_key() - .clone(); - let index = controller.state.node_index().unwrap(); - vks.push(vk); - indices.push(index); - } - let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); - assert_eq!(initial_master_vk, reshared_master_vk); + #[tokio::test] + #[ignore] // expensive test + async fn reshare_after_reset() { + todo!() + // let db = MockContractDb::new(); + // let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await; + // for controller in clients_and_states.iter_mut() { + // controller.state.set_was_in_progress(); + // } + // + // let new_dkg_client = DkgClient::new( + // DummyClient::new( + // AccountId::from_str("n1vxkywf9g4cg0k2dehanzwzz64jw782qm0kuynf").unwrap(), + // ) + // .with_dealer_details(&db.dealer_details_db) + // .with_dealings(&db.dealings_db) + // .with_proposal_db(&db.proposal_db) + // .with_verification_share(&db.verification_share_db) + // .with_threshold(&db.threshold_db) + // .with_initial_dealers_db(&db.initial_dealers_db), + // ); + // let keypair = DkgKeyPair::new(dkg::params(), OsRng); + // let identity_keypair = identity::KeyPair::new(&mut thread_rng()); + // let state = State::new( + // PathBuf::default(), + // PersistentState::default(), + // Url::parse("localhost:8000").unwrap(), + // keypair, + // *identity_keypair.public_key(), + // KeyPair::new(), + // ); + // let new_dkg_client2 = DkgClient::new( + // DummyClient::new( + // AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(), + // ) + // .with_dealer_details(&db.dealer_details_db) + // .with_dealings(&db.dealings_db) + // .with_proposal_db(&db.proposal_db) + // .with_verification_share(&db.verification_share_db) + // .with_threshold(&db.threshold_db) + // .with_initial_dealers_db(&db.initial_dealers_db), + // ); + // let keypair = DkgKeyPair::new(dkg::params(), OsRng); + // let identity_keypair = identity::KeyPair::new(&mut thread_rng()); + // let state2 = State::new( + // PathBuf::default(), + // PersistentState::default(), + // Url::parse("localhost:8000").unwrap(), + // keypair, + // *identity_keypair.public_key(), + // KeyPair::new(), + // ); + // + // for (_, active) in db.dealer_details_db.write().unwrap().values_mut() { + // *active = false; + // } + // + // *db.dealings_db.write().unwrap() = Default::default(); + // *db.verification_share_db.write().unwrap() = Default::default(); + // clients_and_states.pop().unwrap(); + // let controller2 = clients_and_states.pop().unwrap(); + // clients_and_states.push(DkgController::test_mock(new_dkg_client, state)); + // clients_and_states.push(DkgController::test_mock(new_dkg_client2, state2)); + // + // // DKG in reset mode + // for controller in clients_and_states.iter_mut() { + // controller.public_key_submission(0, false).await.unwrap(); + // } + // for controller in clients_and_states.iter_mut() { + // controller.dealing_exchange(0, false).await.unwrap(); + // } + // for controller in clients_and_states.iter_mut() { + // let random_file: usize = OsRng.gen(); + // let keypath = temp_dir().join(format!("coconut{}.pem", random_file)); + // verification_key_submission( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // &keypath, + // false, + // ) + // .await + // .unwrap(); + // std::fs::remove_file(keypath).unwrap(); + // } + // for controller in clients_and_states.iter_mut() { + // verification_key_validation(&controller.dkg_client, &mut controller.state, false) + // .await + // .unwrap(); + // } + // for controller in clients_and_states.iter_mut() { + // verification_key_finalization(&controller.dkg_client, &mut controller.state, false) + // .await + // .unwrap(); + // } + // assert!(db + // .proposal_db + // .read() + // .unwrap() + // .values() + // .all(|proposal| { proposal.status == Status::Executed })); + // for controller in clients_and_states.iter_mut() { + // controller.state.set_was_in_progress(); + // } + // + // // DKG in reshare mode + // let mut vks = vec![]; + // let mut indices = vec![]; + // for controller in clients_and_states.iter() { + // let vk = controller + // .state + // .coconut_keypair() + // .await + // .as_ref() + // .unwrap() + // .keys + // .verification_key() + // .clone(); + // let index = controller.state.node_index().unwrap(); + // vks.push(vk); + // indices.push(index); + // } + // let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); + // + // for (_, active) in db.dealer_details_db.write().unwrap().values_mut() { + // *active = false; + // } + // *db.dealings_db.write().unwrap() = Default::default(); + // *db.verification_share_db.write().unwrap() = Default::default(); + // let mut initial_dealers = vec![]; + // for controller in clients_and_states.iter() { + // let client_address = + // Addr::unchecked(controller.dkg_client.get_address().await.as_ref()); + // initial_dealers.push(client_address); + // } + // *db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData { + // initial_dealers, + // initial_height: 1, + // }); + // *clients_and_states.last_mut().unwrap() = controller2; + // + // for controller in clients_and_states.iter_mut() { + // controller.public_key_submission(0, true).await.unwrap(); + // controller.dealing_exchange(0, true).await.unwrap(); + // } + // + // for controller in clients_and_states.iter_mut() { + // let random_file: usize = OsRng.gen(); + // let keypath = temp_dir().join(format!("coconut{}.pem", random_file)); + // verification_key_submission( + // &controller.dkg_client, + // &mut controller.state, + // 0, + // &keypath, + // true, + // ) + // .await + // .unwrap(); + // std::fs::remove_file(keypath).unwrap(); + // } + // + // for controller in clients_and_states.iter_mut() { + // verification_key_validation(&controller.dkg_client, &mut controller.state, true) + // .await + // .unwrap(); + // } + // for controller in clients_and_states.iter_mut() { + // verification_key_finalization(&controller.dkg_client, &mut controller.state, true) + // .await + // .unwrap(); + // } + // // assert!(db + // // .proposal_db + // // .read() + // // .unwrap() + // // .values() + // // .all(|proposal| { proposal.status == Status::Executed })); + // + // let mut vks = vec![]; + // let mut indices = vec![]; + // for controller in clients_and_states.iter() { + // let vk = controller + // .state + // .coconut_keypair() + // .await + // .as_ref() + // .unwrap() + // .keys + // .verification_key() + // .clone(); + // let index = controller.state.node_index().unwrap(); + // vks.push(vk); + // indices.push(index); + // } + // let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap(); + // assert_eq!(initial_master_vk, reshared_master_vk); } } diff --git a/nym-api/src/coconut/dkg/state/mod.rs b/nym-api/src/coconut/dkg/state/mod.rs index e82f2b4d57..3161f542ee 100644 --- a/nym-api/src/coconut/dkg/state/mod.rs +++ b/nym-api/src/coconut/dkg/state/mod.rs @@ -16,7 +16,7 @@ use nym_coconut_dkg_common::types::{ }; use nym_crypto::asymmetric::identity; use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; -use nym_dkg::{Dealing, NodeIndex, RecoveredVerificationKeys, Threshold}; +use nym_dkg::{bte, Dealing, NodeIndex, RecoveredVerificationKeys, Threshold}; use nym_validator_client::nyxd::{tx, Hash}; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -43,6 +43,13 @@ impl ParticipantState { matches!(self, ParticipantState::VerifiedKey(..)) } + pub fn public_key(&self) -> Option { + match self { + ParticipantState::Invalid(_) => None, + ParticipantState::VerifiedKey(key_with_proof) => Some(*key_with_proof.public_key()), + } + } + fn from_raw_encoded_key(raw: EncodedBTEPublicKeyWithProof) -> Self { // TODO: include more error information let Ok(bytes) = bs58::decode(raw).into_vec() else { @@ -353,7 +360,7 @@ impl State { } /// Obtain the list of dealers for the provided epoch that have submitted valid public keys. - pub fn valid_epoch_dealers( + pub fn valid_epoch_receivers( &self, epoch_id: EpochId, ) -> Result, CoconutError> { @@ -371,6 +378,18 @@ impl State { .collect()) } + pub fn valid_epoch_receivers_keys( + &self, + epoch_id: EpochId, + ) -> Result, CoconutError> { + Ok(self + .dkg_state(epoch_id)? + .dealers + .values() + .filter_map(|d| d.state.public_key().map(|k| (d.assigned_index, k))) + .collect()) + } + pub fn dkg_state(&self, epoch_id: EpochId) -> Result<&DkgState, CoconutError> { self.dkg_instances .get(&epoch_id) @@ -449,11 +468,11 @@ impl State { .ok_or(CoconutError::UnavailableThreshold { epoch_id }) } - // pub fn assigned_index(&self, epoch_id: EpochId) -> Result { - // self.registration_state(epoch_id)? - // .assigned_index - // .ok_or(CoconutError::UnavailableAssignedIndex { epoch_id }) - // } + pub fn assigned_index(&self, epoch_id: EpochId) -> Result { + self.registration_state(epoch_id)? + .assigned_index + .ok_or(CoconutError::UnavailableAssignedIndex { epoch_id }) + } pub fn receiver_index(&self, epoch_id: EpochId) -> Result { self.dealing_exchange_state(epoch_id)? @@ -509,7 +528,7 @@ impl State { pub async fn coconut_keypair( &self, - ) -> tokio::sync::RwLockReadGuard<'_, Option> { + ) -> Option>> { self.coconut_keypair.get().await } diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 2f81583114..4a86aafdb5 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -142,14 +142,14 @@ pub enum CoconutError { MissingDkgState { epoch_id: EpochId }, #[error( - "the node index value for {epoch_id} is not available - are you sure we are a dealer?" + "the node index value for epoch {epoch_id} is not available - are you sure we are a dealer?" )] UnavailableAssignedIndex { epoch_id: EpochId }, - #[error("the receiver index value for {epoch_id} is not available - are you sure we are a receiver?")] + #[error("the receiver index value for epoch {epoch_id} is not available - are you sure we are a receiver?")] UnavailableReceiverIndex { epoch_id: EpochId }, - #[error("the threshold value for {epoch_id} is not available")] + #[error("the threshold value for epoch {epoch_id} is not available")] UnavailableThreshold { epoch_id: EpochId }, #[error("insufficient number of dealings provided to derive the key")] diff --git a/nym-api/src/coconut/keys/mod.rs b/nym-api/src/coconut/keys/mod.rs index 1d51fd077f..605cc1e68a 100644 --- a/nym-api/src/coconut/keys/mod.rs +++ b/nym-api/src/coconut/keys/mod.rs @@ -50,22 +50,28 @@ impl KeyPair { self.keys.write().await.take() } - pub async fn get(&self) -> RwLockReadGuard<'_, Option> { - todo!() - // self.keys.read().await + pub async fn get(&self) -> Option>> { + if self.is_valid() { + Some(self.keys.read().await) + } else { + None + } } pub async fn set(&self, keypair: KeyPairWithEpoch) { - todo!() - // let mut w_lock = self.keys.write().await; - // *w_lock = Some(keypair); + let mut w_lock = self.keys.write().await; + *w_lock = Some(keypair); } pub fn is_valid(&self) -> bool { self.valid.load(Ordering::SeqCst) } - pub fn invalidate(&self) { + pub fn enable(&self) { self.valid.store(true, Ordering::SeqCst); } + + pub fn invalidate(&self) { + self.valid.store(false, Ordering::SeqCst); + } } diff --git a/nym-api/src/coconut/tests/fixtures.rs b/nym-api/src/coconut/tests/fixtures.rs index b67dc4c78b..720606fc61 100644 --- a/nym-api/src/coconut/tests/fixtures.rs +++ b/nym-api/src/coconut/tests/fixtures.rs @@ -19,6 +19,7 @@ use rand_chacha::{ }; use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Mutex}; +use tempfile::{tempdir, TempDir}; pub fn test_rng(seed: [u8; 32]) -> ChaCha20Rng { ChaCha20Rng::from_seed(seed) @@ -73,6 +74,8 @@ pub struct TestingDkgControllerBuilder { address: Option, threshold: Option, + chain_state: Option>>, + self_dealer: Option, dealers: Vec, } @@ -88,6 +91,11 @@ impl TestingDkgControllerBuilder { self } + pub fn with_shared_chain_state(mut self, fake_chain: Arc>) -> Self { + self.chain_state = Some(fake_chain); + self + } + pub fn with_as_dealer(mut self, dealer_details: DealerDetails) -> Self { self.self_dealer = Some(dealer_details); self @@ -141,8 +149,11 @@ impl TestingDkgControllerBuilder { } }); - let dummy_client = DummyClient::new(self_dealer.address.to_string().parse().unwrap()); - let chain_state = dummy_client.chain_state(); + let chain_state = self.chain_state.unwrap_or_default(); + let dummy_client = DummyClient::new( + self_dealer.address.to_string().parse().unwrap(), + chain_state.clone(), + ); // insert initial data into the chain state let mut state_guard = chain_state.lock().unwrap(); @@ -153,13 +164,14 @@ impl TestingDkgControllerBuilder { state_guard.dealers.insert(dealer.assigned_index, dealer); } - let epoch = state_guard.epoch.epoch_id; + let epoch = state_guard.dkg_epoch.epoch_id; drop(state_guard); let dummy_client = DkgClient::new(dummy_client); + let tmp_dir = tempdir().unwrap(); let mut state = State::new( - Default::default(), + tmp_dir.path().join("persistent_state.json"), Default::default(), self_dealer.announce_address.parse().unwrap(), // TODO: we might need to fix up the key here @@ -176,8 +188,14 @@ impl TestingDkgControllerBuilder { } TestingDkgController { - controller: DkgController::test_mock_new(rng, dummy_client, state), + controller: DkgController::test_mock_new( + rng, + dummy_client, + state, + tmp_dir.path().join("coconut_keypair.pem"), + ), chain_state, + _tmp_dir: tmp_dir, } } } @@ -190,6 +208,8 @@ pub(crate) struct TestingDkgController { pub(crate) controller: DkgController, pub(crate) chain_state: Arc>, + + _tmp_dir: TempDir, } impl Deref for TestingDkgController { diff --git a/nym-api/src/coconut/tests/helpers.rs b/nym-api/src/coconut/tests/helpers.rs index 820e9a1b6d..df0eae8f90 100644 --- a/nym-api/src/coconut/tests/helpers.rs +++ b/nym-api/src/coconut/tests/helpers.rs @@ -1,10 +1,132 @@ // Copyright 2024 - Nym Technologies SA // 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; +use nym_crypto::aes::cipher::crypto_common::rand_core::OsRng; use nym_dkg::bte::PublicKeyWithProof; use nym_dkg::Dealing; +use std::env::temp_dir; +use std::sync::{Arc, Mutex}; pub(crate) fn unchecked_decode_bte_key(raw: &str) -> PublicKeyWithProof { let bytes = bs58::decode(raw).into_vec().unwrap(); PublicKeyWithProof::try_from_bytes(&bytes).unwrap() } + +pub(crate) type SharedChainState = Arc>; + +pub(crate) fn init_chain() -> SharedChainState { + Default::default() +} + +pub(crate) fn initialise_controllers(amount: usize) -> Vec { + let chain = init_chain(); + + let mut controllers = Vec::with_capacity(amount); + assert!(amount <= u8::MAX as usize); + for rng_seed in 0..amount { + let controller = TestingDkgControllerBuilder::default() + .with_shared_chain_state(chain.clone()) + .with_magic_seed_val(rng_seed as u8) + .build(); + + controllers.push(controller) + } + + controllers +} + +pub(crate) fn initialise_dkg(controllers: &mut [TestingDkgController], resharing: bool) { + assert_eq!( + controllers[0].chain_state.lock().unwrap().dkg_epoch.state, + EpochState::WaitingInitialisation + ); + + controllers[0].chain_state.lock().unwrap().dkg_epoch.state = + EpochState::PublicKeySubmission { resharing } +} + +pub(crate) async fn submit_public_keys(controllers: &mut [TestingDkgController], resharing: bool) { + let epoch = controllers[0] + .chain_state + .lock() + .unwrap() + .dkg_epoch + .epoch_id; + + for controller in controllers.iter_mut() { + controller + .public_key_submission(epoch, resharing) + .await + .unwrap(); + } + + let threshold = (2 * controllers.len() as u64 + 3 - 1) / 3; + + let mut guard = controllers[0].chain_state.lock().unwrap(); + guard.dkg_epoch.state = EpochState::DealingExchange { resharing }; + guard.threshold = Some(threshold) +} + +pub(crate) async fn exchange_dealings(controllers: &mut [TestingDkgController], resharing: bool) { + let epoch = controllers[0] + .chain_state + .lock() + .unwrap() + .dkg_epoch + .epoch_id; + + for controller in controllers.iter_mut() { + controller.dealing_exchange(epoch, resharing).await.unwrap(); + } + + let mut guard = controllers[0].chain_state.lock().unwrap(); + guard.dkg_epoch.state = EpochState::VerificationKeySubmission { resharing }; +} + +pub(crate) async fn derive_keypairs(controllers: &mut [TestingDkgController], resharing: bool) { + let epoch = controllers[0] + .chain_state + .lock() + .unwrap() + .dkg_epoch + .epoch_id; + + for controller in controllers.iter_mut() { + controller + .verification_key_submission(epoch, resharing) + .await + .unwrap(); + } + + let mut guard = controllers[0].chain_state.lock().unwrap(); + guard.dkg_epoch.state = EpochState::VerificationKeyValidation { resharing } +} + +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 +} + +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 +} diff --git a/nym-api/src/coconut/tests/mod.rs b/nym-api/src/coconut/tests/mod.rs index 4e3fc4f3ca..cf5b8a5284 100644 --- a/nym-api/src/coconut/tests/mod.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -64,46 +64,47 @@ const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8g #[derive(Debug)] pub(crate) struct FakeChainState { - // new pub(crate) dealers: HashMap, + pub(crate) past_dealers: HashMap, + pub(crate) dealings: HashMap>>, + pub(crate) verification_shares: HashMap>, + pub(crate) dkg_epoch: Epoch, + pub(crate) dkg_contract_state: ContractState, + pub(crate) threshold: Option, + + pub(crate) node_index_counter: NodeIndex, + + pub(crate) proposals: HashMap, // old - txs: HashMap, - proposals: HashMap, - spent_credentials: HashMap, - - epoch: Epoch, - contract_state: ContractState, - old_dealers: HashMap, - threshold: Option, - - verification_shares: HashMap, - group: HashMap, - initial_dealers: Option, + // txs: HashMap, + // spent_credentials: HashMap, + // + // old_dealers: HashMap, + // + // group: HashMap, + // initial_dealers: Option, } impl Default for FakeChainState { fn default() -> Self { FakeChainState { dealers: HashMap::new(), + past_dealers: Default::default(), - txs: HashMap::new(), - proposals: HashMap::new(), - spent_credentials: HashMap::new(), - epoch: Epoch::default(), - contract_state: ContractState { + dkg_epoch: Epoch::default(), + dkg_contract_state: ContractState { mix_denom: TEST_COIN_DENOM.to_string(), multisig_addr: Addr::unchecked("dummy address"), group_addr: Cw4Contract::new(Addr::unchecked("dummy cw4")), key_size: 5, }, - old_dealers: HashMap::new(), - threshold: None, dealings: HashMap::new(), verification_shares: HashMap::new(), - group: HashMap::new(), - initial_dealers: None, + threshold: None, + node_index_counter: 0, + proposals: Default::default(), } } } @@ -116,10 +117,10 @@ pub(crate) struct DummyClient { } impl DummyClient { - pub fn new(validator_address: AccountId) -> Self { + pub fn new(validator_address: AccountId, state: Arc>) -> Self { Self { validator_address, - state: Arc::new(Mutex::new(FakeChainState::default())), + state, } } @@ -219,6 +220,26 @@ impl DummyClient { // self.initial_dealers_db = Arc::clone(initial_dealers); // self } + + async fn get_dealer_by_address(&self, address: &str) -> Option { + let guard = self.state.lock().unwrap(); + for dealer in guard.dealers.values() { + if dealer.address.as_str() == address { + return Some(dealer.clone()); + } + } + None + } + + async fn get_past_dealer_by_address(&self, address: &str) -> Option { + let guard = self.state.lock().unwrap(); + for dealer in guard.past_dealers.values() { + if dealer.address.as_str() == address { + return Some(dealer.clone()); + } + } + None + } } #[async_trait] @@ -228,71 +249,76 @@ impl super::client::Client for DummyClient { } async fn get_tx(&self, tx_hash: Hash) -> Result { - Ok(self - .state - .lock() - .unwrap() - .txs - .get(&tx_hash) - .cloned() - .unwrap()) + todo!() + // Ok(self + // .state + // .lock() + // .unwrap() + // .txs + // .get(&tx_hash) + // .cloned() + // .unwrap()) } async fn get_proposal(&self, proposal_id: u64) -> Result { - self.state - .lock() - .unwrap() - .proposals - .get(&proposal_id) - .cloned() - .ok_or(CoconutError::IncorrectProposal { - reason: String::from("proposal not found"), - }) + todo!() + // self.state + // .lock() + // .unwrap() + // .proposals + // .get(&proposal_id) + // .cloned() + // .ok_or(CoconutError::IncorrectProposal { + // reason: String::from("proposal not found"), + // }) } async fn list_proposals(&self) -> Result> { - Ok(self - .state - .lock() - .unwrap() - .proposals - .values() - .cloned() - .collect()) + todo!() + // Ok(self + // .state + // .lock() + // .unwrap() + // .proposals + // .values() + // .cloned() + // .collect()) } async fn get_spent_credential( &self, blinded_serial_number: String, ) -> Result { - self.state - .lock() - .unwrap() - .spent_credentials - .get(&blinded_serial_number) - .cloned() - .ok_or(CoconutError::InvalidCredentialStatus { - status: String::from("spent credential not found"), - }) + todo!() + // self.state + // .lock() + // .unwrap() + // .spent_credentials + // .get(&blinded_serial_number) + // .cloned() + // .ok_or(CoconutError::InvalidCredentialStatus { + // status: String::from("spent credential not found"), + // }) } async fn contract_state(&self) -> Result { - Ok(self.state.lock().unwrap().contract_state.clone()) + Ok(self.state.lock().unwrap().dkg_contract_state.clone()) } async fn get_current_epoch(&self) -> Result { - Ok(self.state.lock().unwrap().epoch) + Ok(self.state.lock().unwrap().dkg_epoch) } async fn group_member(&self, addr: String) -> Result { - Ok(self - .state - .lock() - .unwrap() - .group - .get(&addr) - .cloned() - .unwrap_or(MemberResponse { weight: None })) + todo!() + // Ok(self + // .state + // .lock() + // .unwrap() + // .group + // .get(&addr) + // .cloned() + // .unwrap_or(MemberResponse { weight: None })) } async fn get_current_epoch_threshold(&self) -> Result> { @@ -300,30 +326,30 @@ impl super::client::Client for DummyClient { } async fn get_initial_dealers(&self) -> Result> { - Ok(self.state.lock().unwrap().initial_dealers.clone()) + todo!() + // Ok(self.state.lock().unwrap().initial_dealers.clone()) } async fn get_self_registered_dealer_details(&self) -> Result { - let (details, dealer_type) = if let Some((details, current)) = self - .state - .lock() - .unwrap() - .old_dealers - .get(self.validator_address.as_ref()) - .cloned() - { - let dealer_type = if current { - DealerType::Current - } else { - DealerType::Past - }; - (Some(details), dealer_type) - } else { - (None, DealerType::Unknown) - }; + let address = self.validator_address.as_ref(); + + if let Some(details) = self.get_dealer_by_address(address).await { + return Ok(DealerDetailsResponse { + details: Some(details), + dealer_type: DealerType::Current, + }); + } + + if let Some(details) = self.get_past_dealer_by_address(address).await { + return Ok(DealerDetailsResponse { + details: Some(details), + dealer_type: DealerType::Past, + }); + } + Ok(DealerDetailsResponse { - details, - dealer_type, + details: None, + dealer_type: DealerType::Unknown, }) } @@ -332,7 +358,7 @@ impl super::client::Client for DummyClient { epoch_id: EpochId, dealer: String, dealing_index: DealingIndex, - ) -> crate::coconut::error::Result { + ) -> Result { let dealings = self.get_dealings(epoch_id, &dealer).await?; Ok(DealingStatusResponse { epoch_id, @@ -371,18 +397,28 @@ impl super::client::Client for DummyClient { .unwrap_or_default()) } - async fn get_verification_key_shares( + async fn get_verification_key_share( &self, - _epoch_id: EpochId, - ) -> Result> { - Ok(self - .state - .lock() - .unwrap() - .verification_shares - .values() - .cloned() - .collect()) + epoch_id: EpochId, + dealer: String, + ) -> Result> { + let guard = self.state.lock().unwrap(); + let epoch_shares = guard.verification_shares.get(&epoch_id); + + match epoch_shares { + None => Ok(None), + Some(epoch_shares) => Ok(epoch_shares.get(&dealer).cloned()), + } + } + + async fn get_verification_key_shares(&self, epoch_id: EpochId) -> Result> { + let guard = self.state.lock().unwrap(); + let epoch_shares = guard.verification_shares.get(&epoch_id); + + match epoch_shares { + None => Ok(Vec::new()), + Some(epoch_shares) => Ok(epoch_shares.values().cloned().collect()), + } } async fn vote_proposal( @@ -391,29 +427,31 @@ impl super::client::Client for DummyClient { vote_yes: bool, _fee: Option, ) -> Result<()> { - 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(()) + 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(()) } async fn execute_proposal(&self, proposal_id: u64) -> Result<()> { - self.state - .lock() - .unwrap() - .proposals - .entry(proposal_id) - .and_modify(|prop| { - if prop.status == cw3::Status::Passed { - prop.status = cw3::Status::Executed - } - }); - Ok(()) + todo!() + // self.state + // .lock() + // .unwrap() + // .proposals + // .entry(proposal_id) + // .and_modify(|prop| { + // if prop.status == cw3::Status::Passed { + // prop.status = cw3::Status::Executed + // } + // }); + // Ok(()) } async fn advance_epoch_state(&self) -> Result<()> { @@ -427,33 +465,39 @@ impl super::client::Client for DummyClient { announce_address: String, _resharing: bool, ) -> Result { - let mut guard = self.state.lock().unwrap(); - let assigned_index = if let Some((details, active)) = - guard.old_dealers.get_mut(self.validator_address.as_ref()) + let assigned_index = if let Some(already_registered) = self + .get_dealer_by_address(self.validator_address.as_ref()) + .await { - *active = true; - details.assigned_index + // current dealer + already_registered.assigned_index + } else if let Some(registered_in_the_past) = self + .get_past_dealer_by_address(self.validator_address.as_ref()) + .await + { + // past dealer + let index = registered_in_the_past.assigned_index; + let mut guard = self.state.lock().unwrap(); + guard.dealers.insert(index, registered_in_the_past); + + index } else { + // new dealer + let mut guard = self.state.lock().unwrap(); + // let assigned_index = OsRng.gen(); - let assigned_index = guard - .old_dealers - .values() - .map(|(d, _)| d.assigned_index) - .max() - .unwrap_or(0) - + 1; - guard.old_dealers.insert( - self.validator_address.to_string(), - ( - DealerDetails { - address: Addr::unchecked(self.validator_address.to_string()), - bte_public_key_with_proof, - ed25519_identity: identity_key, - announce_address, - assigned_index, - }, - true, - ), + guard.node_index_counter += 1; + let assigned_index = guard.node_index_counter; + + guard.dealers.insert( + assigned_index, + DealerDetails { + address: Addr::unchecked(self.validator_address.to_string()), + bte_public_key_with_proof, + ed25519_identity: identity_key, + announce_address, + assigned_index, + }, ); assigned_index }; @@ -475,7 +519,7 @@ impl super::client::Client for DummyClient { _resharing: bool, ) -> Result { let mut guard = self.state.lock().unwrap(); - let current_epoch = guard.epoch.epoch_id; + let current_epoch = guard.dkg_epoch.epoch_id; let epoch_dealings = guard.dealings.entry(current_epoch).or_default(); let existing_dealings = epoch_dealings @@ -496,33 +540,36 @@ impl super::client::Client for DummyClient { share: VerificationKeyShare, resharing: bool, ) -> Result { - let (dealer_details, active) = self - .state - .lock() - .unwrap() - .old_dealers - .get(self.validator_address.as_ref()) - .unwrap() - .clone(); - if !active { + let address = self.validator_address.to_string(); + + let Some(dealer_details) = self.get_dealer_by_address(&address).await else { // Just throw some error, not really the correct one return Err(CoconutError::DepositEncrKeyNotFound); - } - self.state.lock().unwrap().verification_shares.insert( - self.validator_address.to_string(), - ContractVKShare { - share, - announce_address: dealer_details.announce_address.clone(), - node_index: dealer_details.assigned_index, - owner: Addr::unchecked(self.validator_address.to_string()), - epoch_id: 0, - verified: false, - }, - ); + }; + + let mut guard = self.state.lock().unwrap(); + let epoch_id = guard.dkg_epoch.epoch_id; + + guard + .verification_shares + .entry(epoch_id) + .or_default() + .insert( + self.validator_address.to_string(), + ContractVKShare { + share, + announce_address: dealer_details.announce_address.clone(), + node_index: dealer_details.assigned_index, + owner: Addr::unchecked(&address), + epoch_id, + verified: false, + }, + ); + let proposal_id = OsRng.gen(); let verify_vk_share_req = nym_coconut_dkg_common::msg::ExecuteMsg::VerifyVerificationKeyShare { - owner: Addr::unchecked(self.validator_address.as_ref()), + owner: Addr::unchecked(&address), resharing, }; let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute { @@ -544,11 +591,7 @@ impl super::client::Client for DummyClient { proposer: Addr::unchecked(self.validator_address.as_ref()), deposit: None, }; - self.state - .lock() - .unwrap() - .proposals - .insert(proposal_id, proposal); + guard.proposals.insert(proposal_id, proposal); Ok(ExecuteResult { logs: vec![Log { msg_index: 0,