From a7dca2f07cb65aacfeac18df11b788ca9e873ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 23 Jan 2024 17:45:56 +0000 Subject: [PATCH] happy path for key finalization --- .../verification_key_shares/transactions.rs | 1 + nym-api/src/coconut/client.rs | 3 +- nym-api/src/coconut/dkg/client.rs | 9 +- nym-api/src/coconut/dkg/controller/mod.rs | 5 +- nym-api/src/coconut/dkg/key_derivation.rs | 12 +- nym-api/src/coconut/dkg/key_finalization.rs | 116 +++++++++++++----- nym-api/src/coconut/dkg/key_validation.rs | 16 +-- .../src/coconut/dkg/state/key_derivation.rs | 5 +- .../src/coconut/dkg/state/key_finalization.rs | 8 +- nym-api/src/coconut/dkg/state/mod.rs | 4 + nym-api/src/coconut/error.rs | 3 + nym-api/src/coconut/tests/helpers.rs | 9 +- nym-api/src/coconut/tests/mod.rs | 48 ++++---- nym-api/src/support/nyxd/mod.rs | 10 +- 14 files changed, 158 insertions(+), 91 deletions(-) diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index 617874e113..e455ac3430 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -54,6 +54,7 @@ pub fn try_commit_verification_key_share( resharing, env.contract.address.to_string(), STATE.load(deps.storage)?.multisig_addr.to_string(), + // TODO: make this value configurable env.block .time .plus_seconds(BLOCK_TIME_FOR_VERIFICATION_SECS), diff --git a/nym-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs index f3317c2266..d27d5defe5 100644 --- a/nym-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::coconut::error::Result; -use cw3::ProposalResponse; +use cw3::{ProposalResponse, VoteResponse}; use cw4::MemberResponse; use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse, DealingStatusResponse}; @@ -24,6 +24,7 @@ pub trait Client { async fn get_tx(&self, tx_hash: Hash) -> Result; async fn get_proposal(&self, proposal_id: u64) -> Result; async fn list_proposals(&self) -> Result>; + async fn get_vote(&self, proposal_id: u64, voter: String) -> Result; async fn get_spent_credential( &self, blinded_serial_number: String, diff --git a/nym-api/src/coconut/dkg/client.rs b/nym-api/src/coconut/dkg/client.rs index b9b0d4d2a3..716e4dece6 100644 --- a/nym-api/src/coconut/dkg/client.rs +++ b/nym-api/src/coconut/dkg/client.rs @@ -3,7 +3,7 @@ use crate::coconut::client::Client; use crate::coconut::error::CoconutError; -use cw3::ProposalResponse; +use cw3::{ProposalResponse, Status}; use cw4::MemberResponse; use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse}; use nym_coconut_dkg_common::types::{ @@ -119,6 +119,13 @@ impl DkgClient { self.inner.list_proposals().await } + pub(crate) async fn get_proposal_status( + &self, + proposal_id: u64, + ) -> Result { + self.inner.get_proposal(proposal_id).await.map(|p| p.status) + } + pub(crate) async fn advance_epoch_state(&self) -> Result<(), CoconutError> { self.inner.advance_epoch_state().await } diff --git a/nym-api/src/coconut/dkg/controller/mod.rs b/nym-api/src/coconut/dkg/controller/mod.rs index 36b1c93323..fc739243f3 100644 --- a/nym-api/src/coconut/dkg/controller/mod.rs +++ b/nym-api/src/coconut/dkg/controller/mod.rs @@ -1,9 +1,8 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::controller::error::DkgError; -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; @@ -173,7 +172,7 @@ impl DkgController { ) -> Result<(), DkgError> { debug!("DKG: verification key finalization (resharing: {resharing})"); - verification_key_finalization(&self.dkg_client, &mut self.state, resharing) + self.verification_key_finalization(epoch_id, resharing) .await .map_err(|source| DkgError::VerificationKeyFinalizationFailure { source }) } diff --git a/nym-api/src/coconut/dkg/key_derivation.rs b/nym-api/src/coconut/dkg/key_derivation.rs index 274ab43d3a..e2da91fd3d 100644 --- a/nym-api/src/coconut/dkg/key_derivation.rs +++ b/nym-api/src/coconut/dkg/key_derivation.rs @@ -450,7 +450,7 @@ impl DkgController { &mut self, key: &VerificationKey, resharing: bool, - ) -> Result<(), CoconutError> { + ) -> Result { debug!("submitting derived partial verification key to the contract"); let res = self .dkg_client @@ -468,8 +468,7 @@ impl DkgController { })?; debug!("Submitted own verification key share, proposal id {proposal_id} is attached to it. tx hash: {hash}"); - // TODO: state.set_proposal_id(proposal_id); - Ok(()) + Ok(proposal_id) } /// Third step of the DKG process during which the nym api will generate its Coconut keypair @@ -528,11 +527,14 @@ impl DkgController { todo!() } - self.submit_partial_verification_key(coconut_keypair.keys.verification_key(), resharing) + let proposal_id = self.submit_partial_verification_key(coconut_keypair.keys.verification_key(), resharing) .await?; self.state.set_coconut_keypair(coconut_keypair).await; - self.state.key_derivation_state_mut(epoch_id)?.completed = true; + let derivation_state = self.state.key_derivation_state_mut(epoch_id)?; + + derivation_state.completed = true; + derivation_state.proposal_id = Some(proposal_id); info!("DKG: Finished key derivation"); Ok(()) diff --git a/nym-api/src/coconut/dkg/key_finalization.rs b/nym-api/src/coconut/dkg/key_finalization.rs index 61de87fbd8..9135d01939 100644 --- a/nym-api/src/coconut/dkg/key_finalization.rs +++ b/nym-api/src/coconut/dkg/key_finalization.rs @@ -5,50 +5,100 @@ 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 cw3::Status; +use nym_coconut_dkg_common::types::EpochId; use rand::{CryptoRng, RngCore}; -impl DkgController {} +impl DkgController { + pub(crate) async fn verification_key_finalization( + &mut self, + epoch_id: EpochId, + _resharing: bool, + ) -> Result<(), CoconutError> { + let key_finalization_state = self.state.key_finalization_state(epoch_id)?; -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(()); + // check if we have already executed our own proposal + if key_finalization_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!("our key has already been verified"); + return Ok(()); + } + + let proposal_id = self.state.proposal_id(epoch_id)?; + + // check whether our key has already been verified with executed proposal, + // either by us in previous iteration after a timeout + // or by another party + let status = self.dkg_client.get_proposal_status(proposal_id).await?; + match status { + Status::Pending | Status::Open => { + warn!("our proposal ({proposal_id}) still hasn't received enough votes to get accepted"); + todo!(); + } + Status::Rejected => { + // TODO: technically there's nothing enforcing this... + error!("our key verification proposal ({proposal_id}) has been rejected - we can't use our derived keys!"); + todo!() + } + Status::Passed => { + self.dkg_client + .execute_verification_key_share(proposal_id) + .await?; + } + Status::Executed => { + debug!("our dkg proposal has already been executed"); + } + } + + self.state.key_finalization_state_mut(epoch_id)?.completed = true; + info!("DKG: Finalized own verification key on chain"); + + 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(()) } +// each dealer is responsible for executing its own proposals + #[cfg(test)] mod tests { use super::*; + use crate::coconut::tests::helpers::{ + derive_keypairs, exchange_dealings, initialise_controllers, initialise_dkg, + submit_public_keys, validate_keys, + }; #[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); - // } + async fn finalize_verification_key() -> anyhow::Result<()> { + let validators = 4; + + 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; + validate_keys(&mut controllers, false).await; + + for controller in controllers.iter_mut() { + let res = controller.verification_key_finalization(epoch, false).await; + assert!(res.is_ok()); + + assert!(controller.state.key_finalization_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::Executed, proposal.status) + } + + Ok(()) } } diff --git a/nym-api/src/coconut/dkg/key_validation.rs b/nym-api/src/coconut/dkg/key_validation.rs index 198fb5dba9..ca7ce5ab29 100644 --- a/nym-api/src/coconut/dkg/key_validation.rs +++ b/nym-api/src/coconut/dkg/key_validation.rs @@ -269,8 +269,7 @@ mod tests { #[tokio::test] #[ignore] // expensive test async fn validate_verification_key() -> anyhow::Result<()> { - let unused = 42; - let validators = 1; + let validators = 4; let mut controllers = initialise_controllers(validators); let chain = controllers[0].chain_state.clone(); @@ -298,19 +297,6 @@ mod tests { } 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] diff --git a/nym-api/src/coconut/dkg/state/key_derivation.rs b/nym-api/src/coconut/dkg/state/key_derivation.rs index 3af4984668..2237875839 100644 --- a/nym-api/src/coconut/dkg/state/key_derivation.rs +++ b/nym-api/src/coconut/dkg/state/key_derivation.rs @@ -16,8 +16,11 @@ pub struct KeyDerivationState { #[serde(with = "recovered_keys")] pub(crate) derived_partials: BTreeMap, + pub(crate) proposal_id: Option, + pub(crate) completed: bool, - // because we couldn't decrypt shares or there were no shares, etc + + // TODO: because we couldn't decrypt shares or there were no shares, etc // failed: } diff --git a/nym-api/src/coconut/dkg/state/key_finalization.rs b/nym-api/src/coconut/dkg/state/key_finalization.rs index c5f7c939e3..40cd865ed5 100644 --- a/nym-api/src/coconut/dkg/state/key_finalization.rs +++ b/nym-api/src/coconut/dkg/state/key_finalization.rs @@ -4,11 +4,13 @@ use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Default, Serialize, Deserialize)] -pub struct FinalizationState {} +pub struct FinalizationState { + pub(crate) completed: bool, +} impl FinalizationState { - /// Specifies whether this dealer has already registered in the particular DKG epoch + /// Specifies whether this (or another) dealer has already executed its verification proposal pub fn completed(&self) -> bool { - todo!() + self.completed } } diff --git a/nym-api/src/coconut/dkg/state/mod.rs b/nym-api/src/coconut/dkg/state/mod.rs index 240b27cb08..ecd41024ae 100644 --- a/nym-api/src/coconut/dkg/state/mod.rs +++ b/nym-api/src/coconut/dkg/state/mod.rs @@ -524,6 +524,10 @@ impl State { .receiver_index .ok_or(CoconutError::UnavailableReceiverIndex { epoch_id }) } + + pub fn proposal_id(&self, epoch_id: EpochId) -> Result { + self.key_derivation_state(epoch_id)?.proposal_id.ok_or(CoconutError::UnavailableProposalId {epoch_id}) + } pub fn persistent_state_path(&self) -> &Path { self.persistent_state_path.as_path() diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 4a86aafdb5..2b6eb98ffb 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -152,6 +152,9 @@ pub enum CoconutError { #[error("the threshold value for epoch {epoch_id} is not available")] UnavailableThreshold { epoch_id: EpochId }, + #[error("the proposal id value for epoch {epoch_id} is not available")] + UnavailableProposalId { epoch_id: EpochId }, + #[error("insufficient number of dealings provided to derive the key")] InsufficientDealings { // TODO: details diff --git a/nym-api/src/coconut/tests/helpers.rs b/nym-api/src/coconut/tests/helpers.rs index 6dfb902724..165ac46e2b 100644 --- a/nym-api/src/coconut/tests/helpers.rs +++ b/nym-api/src/coconut/tests/helpers.rs @@ -134,11 +134,10 @@ pub(crate) async fn finalize(controllers: &mut [TestingDkgController], resharing .epoch_id; for controller in controllers.iter_mut() { - todo!() - // controller - // .verification_key_finalization(epoch, resharing) - // .await - // .unwrap(); + controller + .verification_key_finalization(epoch, resharing) + .await + .unwrap(); } let mut guard = controllers[0].chain_state.lock().unwrap(); diff --git a/nym-api/src/coconut/tests/mod.rs b/nym-api/src/coconut/tests/mod.rs index a9a38f7b35..03dabe7761 100644 --- a/nym-api/src/coconut/tests/mod.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -7,7 +7,7 @@ use crate::coconut::{self, State}; use crate::support::storage::NymApiStorage; use async_trait::async_trait; use cosmwasm_std::{coin, to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; -use cw3::ProposalResponse; +use cw3::{ProposalResponse, VoteResponse}; use cw4::{Cw4Contract, MemberResponse}; use nym_api_requests::coconut::models::{IssuedCredentialBody, IssuedCredentialResponse}; use nym_api_requests::coconut::{ @@ -268,16 +268,15 @@ impl super::client::Client for DummyClient { } async fn get_proposal(&self, proposal_id: u64) -> Result { - todo!() - // self.state - // .lock() - // .unwrap() - // .proposals - // .get(&proposal_id) - // .cloned() - // .ok_or(CoconutError::IncorrectProposal { - // reason: String::from("proposal not found"), - // }) + 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> { @@ -291,6 +290,10 @@ impl super::client::Client for DummyClient { .collect()) } + async fn get_vote(&self, proposal_id: u64, voter: String) -> Result { + todo!() + } + async fn get_spent_credential( &self, blinded_serial_number: String, @@ -445,18 +448,17 @@ impl super::client::Client for DummyClient { } async fn execute_proposal(&self, proposal_id: u64) -> Result<()> { - todo!() - // self.state - // .lock() - // .unwrap() - // .proposals - // .entry(proposal_id) - // .and_modify(|prop| { - // if prop.status == cw3::Status::Passed { - // prop.status = cw3::Status::Executed - // } - // }); - // Ok(()) + 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<()> { diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index d1a43d87ce..e73edd8cf7 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -6,7 +6,7 @@ use crate::epoch_operations::MixnodeWithPerformance; use crate::support::config::Config; use anyhow::Result; use async_trait::async_trait; -use cw3::ProposalResponse; +use cw3::{ProposalResponse, VoteResponse}; use cw4::MemberResponse; use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; use nym_coconut_dkg_common::dealer::DealingStatusResponse; @@ -375,6 +375,14 @@ impl crate::coconut::client::Client for Client { Ok(nyxd_query!(self, get_all_proposals().await?)) } + async fn get_vote( + &self, + proposal_id: u64, + voter: String, + ) -> crate::coconut::error::Result { + Ok(nyxd_query!(self, query_vote(proposal_id, voter).await?)) + } + async fn get_spent_credential( &self, blinded_serial_number: String,