diff --git a/nym-api/src/coconut/dkg/complaints.rs b/nym-api/src/coconut/dkg/complaints.rs deleted file mode 100644 index 79953e8f5a..0000000000 --- a/nym-api/src/coconut/dkg/complaints.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: GPL-3.0-only - -use serde::{Deserialize, Serialize}; -use std::fmt::{Display, Formatter}; - -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub(crate) enum ComplaintReason { - MalformedBTEPublicKey, - InvalidBTEPublicKey, - MissingDealing, - MalformedDealing, - DealingVerificationError, -} - -impl Display for ComplaintReason { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ComplaintReason::MalformedBTEPublicKey => { - write!(f, "provided BTE Public key is malformed") - } - ComplaintReason::InvalidBTEPublicKey => { - write!(f, "provided BTE public key does not verify correctly") - } - ComplaintReason::MissingDealing => write!(f, "one of the dealings is missing"), - ComplaintReason::MalformedDealing => { - write!(f, "one of the provided dealings is malformed") - } - ComplaintReason::DealingVerificationError => { - write!(f, "failed to verify one of the dealings") - } - } - } -} diff --git a/nym-api/src/coconut/dkg/controller/error.rs b/nym-api/src/coconut/dkg/controller/error.rs index 800148b4c3..2ac15d71f6 100644 --- a/nym-api/src/coconut/dkg/controller/error.rs +++ b/nym-api/src/coconut/dkg/controller/error.rs @@ -1,9 +1,11 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::coconut::dkg::dealing::DealingGenerationError; use crate::coconut::dkg::key_derivation::KeyDerivationError; use crate::coconut::dkg::key_finalization::KeyFinalizationError; use crate::coconut::dkg::key_validation::KeyValidationError; +use crate::coconut::dkg::public_key::PublicKeySubmissionError; use crate::coconut::error::CoconutError; use std::path::PathBuf; use thiserror::Error; @@ -35,13 +37,13 @@ pub enum DkgError { #[error("failed to submit public keys to the DKG contract: {source}")] PublicKeySubmissionFailure { #[source] - source: CoconutError, + source: PublicKeySubmissionError, }, #[error("failed to submit DKG dealings to the DKG contract: {source}")] DealingExchangeFailure { #[source] - source: CoconutError, + source: DealingGenerationError, }, #[error("failed to submit verification keys to the DKG contract: {source}")] diff --git a/nym-api/src/coconut/dkg/controller/keys.rs b/nym-api/src/coconut/dkg/controller/keys.rs index f002caf8a1..ce0d16930a 100644 --- a/nym-api/src/coconut/dkg/controller/keys.rs +++ b/nym-api/src/coconut/dkg/controller/keys.rs @@ -1,10 +1,11 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::coconut::client::Client; use crate::coconut::keys::KeyPairWithEpoch; -use crate::support::config; +use crate::support::{config, nyxd}; use anyhow::{anyhow, bail, Context}; -use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_dkg_common::types::{EpochId, EpochState}; use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use rand::{CryptoRng, RngCore}; use std::path::Path; @@ -45,6 +46,29 @@ pub(crate) fn load_coconut_keypair_if_exists( .map(Some) } +// the keys can be considered valid if they were generated for the current dkg epoch +// and we're either in the "in progress" or "key finalization" states of the DKG +pub(crate) async fn can_validate_coconut_keys( + nyxd_client: &nyxd::Client, + issued_for: EpochId, +) -> anyhow::Result { + // validate the keys if they were generated for the current dkg epoch + // and we're either in the "in progress" or "key finalization" states of the DKG + let current_dkg_epoch = nyxd_client.get_current_epoch().await?; + if issued_for != current_dkg_epoch.epoch_id { + warn!("managed to load coconut keys, but they were generated for epoch {issued_for}. The current epoch is {}. the keys won't be used for credential issuance", current_dkg_epoch.epoch_id); + Ok(false) + } else if !matches!( + current_dkg_epoch.state, + EpochState::InProgress | EpochState::VerificationKeyFinalization { .. } + ) { + warn!("managed to load coconut keys, but the current DKG epoch is at {}. the keys won't (yet) be used for credential issuance", current_dkg_epoch.state); + Ok(false) + } else { + Ok(true) + } +} + pub(crate) fn persist_coconut_keypair>( keys: &KeyPairWithEpoch, store_path: P, diff --git a/nym-api/src/coconut/dkg/controller/mod.rs b/nym-api/src/coconut/dkg/controller/mod.rs index a6d616c7f0..8ddac1e80c 100644 --- a/nym-api/src/coconut/dkg/controller/mod.rs +++ b/nym-api/src/coconut/dkg/controller/mod.rs @@ -3,7 +3,7 @@ use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::controller::error::DkgError; -use crate::coconut::dkg::state::{ConsistentState, PersistentState, State}; +use crate::coconut::dkg::state::{PersistentState, State}; use crate::coconut::keys::KeyPair as CoconutKeyPair; use crate::nyxd; use crate::support::config; @@ -13,7 +13,7 @@ use nym_crypto::asymmetric::identity; use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use nym_task::{TaskClient, TaskManager}; use rand::rngs::OsRng; -use rand::{CryptoRng, RngCore}; +use rand::{CryptoRng, Rng, RngCore}; use std::path::PathBuf; use std::time::Duration; use time::OffsetDateTime; @@ -43,9 +43,12 @@ impl DkgController { bail!("can't start a DKG controller without specifying an announce address!") }; - let persistent_state = - PersistentState::load_from_file(&config.storage_paths.dkg_persistent_state_path) - .unwrap_or_default(); + let persistent_state = PersistentState::load_from_file( + &config.storage_paths.dkg_persistent_state_path, + ).unwrap_or_else(|err| { + warn!("could not load an existing persistent state from the file. a fresh state will be used: {err}"); + Default::default() + }); Ok(DkgController { dkg_client: DkgClient::new(nyxd_client), @@ -64,11 +67,6 @@ impl DkgController { } fn persist_state(&self) -> Result<(), DkgError> { - // if !self.state.coconut_keypair_is_some().await { - // // Delete the files just in case the process is killed before the new keys are generated - // std::fs::remove_file(&self.secret_key_path).ok(); - // std::fs::remove_file(&self.verification_key_path).ok(); - // } let persistent_state = PersistentState::from(&self.state); let save_path = self.state.persistent_state_path(); persistent_state.save_to_file(save_path).map_err(|source| { @@ -107,14 +105,6 @@ impl DkgController { Ok(()) } - async fn ensure_state_consistency(&self) -> Result<(), DkgError> { - todo!() - // if let Err(err) = self.state.is_consistent(epoch.state).await { - // warn!("Epoch state is corrupted - {err}. Awaiting for a DKG restart."); - // return; - // } - } - async fn handle_awaiting_initialisation(&mut self) -> Result<(), DkgError> { info!("DKG hasn't been initialised yet - nothing to do"); Ok(()) @@ -128,7 +118,8 @@ impl DkgController { debug!("DKG: public key submission (resharing: {resharing})"); self.public_key_submission(epoch_id, resharing) .await - .map_err(|source| DkgError::PublicKeySubmissionFailure { source }) + .map_err(|source| DkgError::PublicKeySubmissionFailure { source })?; + self.persist_state() } async fn handle_dealing_exchange( @@ -139,7 +130,8 @@ impl DkgController { debug!("DKG: dealing exchange (resharing: {resharing})"); self.dealing_exchange(epoch_id, resharing) .await - .map_err(|source| DkgError::DealingExchangeFailure { source }) + .map_err(|source| DkgError::DealingExchangeFailure { source })?; + self.persist_state() } async fn handle_verification_key_submission( @@ -150,7 +142,8 @@ impl DkgController { debug!("DKG: verification key submission (resharing: {resharing})"); self.verification_key_submission(epoch_id, resharing) .await - .map_err(|source| DkgError::VerificationKeySubmissionFailure { source }) + .map_err(|source| DkgError::VerificationKeySubmissionFailure { source })?; + self.persist_state() } async fn handle_verification_key_validation( @@ -162,7 +155,8 @@ impl DkgController { self.verification_key_validation(epoch_id) .await - .map_err(|source| DkgError::VerificationKeyValidationFailure { source }) + .map_err(|source| DkgError::VerificationKeyValidationFailure { source })?; + self.persist_state() } async fn handle_verification_key_finalization( @@ -174,13 +168,29 @@ impl DkgController { self.verification_key_finalization(epoch_id) .await - .map_err(|source| DkgError::VerificationKeyFinalizationFailure { source }) + .map_err(|source| DkgError::VerificationKeyFinalizationFailure { source })?; + self.persist_state() } - async fn handle_in_progress(&mut self) -> Result<(), DkgError> { + async fn handle_in_progress(&mut self, epoch_id: EpochId) -> Result<(), DkgError> { debug!("DKG: epoch in progress"); - self.state.set_was_in_progress(); + let Ok(state) = self.state.in_progress_state(epoch_id) else { + // we probably just started up the api while the DKG has already finished and we're waiting for new round to join + debug!("the DKG has finished without our participation"); + return Ok(()); + }; + + if !state.entered { + info!("this is the first time this node is in the in progress state - going to clear state from the PREVIOUS epoch..."); + // if we finished dkg for epoch 123, we no longer care about anything from epoch 122 + // (but keep track of data from 123 for the future reference) + self.state.clear_previous_epoch(epoch_id); + + // SAFETY: we just accessed this item in an immutable way, thus it MUST exist so the unwrap is fine + self.state.in_progress_state_mut(epoch_id).unwrap().entered = true; + } + Ok(()) } @@ -194,12 +204,8 @@ impl DkgController { } pub(crate) async fn handle_epoch_state(&mut self) -> Result<(), DkgError> { - self.ensure_state_consistency().await?; self.ensure_group_member().await?; - // make sure to always persist our state before continuing in case of failures - self.persist_state()?; - let epoch = self.current_epoch().await?; match epoch.state { @@ -225,17 +231,14 @@ impl DkgController { .await? } // Just wait, in case we need to redo dkg at some point - EpochState::InProgress => self.handle_in_progress().await?, + EpochState::InProgress => self.handle_in_progress(epoch.epoch_id).await?, }; - // persist the state after the successful update - // (sure, we might be doing this unnecessarily for each "InProgress", - // but that's just one write every polling interval, which in the grand scheme of things is nothing - self.persist_state()?; - + // add a bit of variance so that all apis wouldn't attempt to trigger it at the same time + let variance = self.rng.gen_range(0..=60); if let Some(epoch_finish) = epoch.finish_timestamp { let now = OffsetDateTime::now_utc(); - if now.unix_timestamp() > epoch_finish.seconds() as i64 { + if now.unix_timestamp() > epoch_finish.seconds() as i64 + variance { // TODO: make sure to not overload validator in case its running slow // i.e. send it once at most every X seconds self.try_advance_dkg_state().await? @@ -247,9 +250,26 @@ impl DkgController { pub(crate) async fn run(mut self, mut shutdown: TaskClient) { let mut interval = interval(self.polling_rate); + + // sometimes when the process is running behind, the ticker resolves multiple times in quick succession + // so explicitly track those instances and make sure we don't overload the validator with contract calls + let mut last_polled = OffsetDateTime::now_utc(); + let mut last_tick_duration = Default::default(); + while !shutdown.is_shutdown() { tokio::select! { _ = interval.tick() => { + let now = OffsetDateTime::now_utc(); + let tick_duration = now - last_polled; + last_polled = now; + + if tick_duration < self.polling_rate { + warn!("it seems the process is running behind. The current tick rate is lower than the polling rate. rate: {:?}, current tick: {}, previous tick: {}", self.polling_rate, tick_duration, last_tick_duration); + last_tick_duration = tick_duration; + continue + } + last_tick_duration = tick_duration; + if let Err(err) = self.handle_epoch_state().await { error!("failed to update the DKG state: {err}") } @@ -289,17 +309,21 @@ impl DkgController { #[cfg(test)] impl DkgController { - pub(crate) fn test_mock(dkg_client: DkgClient, state: State) -> DkgController { + #[allow(dead_code)] + pub(crate) fn default_test_mock( + dkg_client: DkgClient, + state: State, + ) -> DkgController { DkgController { dkg_client, coconut_key_path: Default::default(), state, - rng: OsRng, + rng: crate::coconut::tests::fixtures::test_rng([1u8; 32]), polling_rate: Default::default(), } } - pub(crate) fn test_mock_new( + pub(crate) fn test_mock( rng: rand_chacha::ChaCha20Rng, dkg_client: DkgClient, state: State, diff --git a/nym-api/src/coconut/dkg/dealing.rs b/nym-api/src/coconut/dkg/dealing.rs index cd93d06b70..26cc098cb6 100644 --- a/nym-api/src/coconut/dkg/dealing.rs +++ b/nym-api/src/coconut/dkg/dealing.rs @@ -4,7 +4,6 @@ use crate::coconut::dkg; use crate::coconut::dkg::controller::keys::archive_coconut_keypair; use crate::coconut::dkg::controller::DkgController; -use crate::coconut::dkg::state::ParticipantState; use crate::coconut::error::CoconutError; use crate::coconut::keys::KeyPairWithEpoch; use log::debug; @@ -13,8 +12,10 @@ use nym_coconut_dkg_common::types::{ }; use nym_dkg::{Dealing, Scalar}; use rand::{CryptoRng, RngCore}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::fmt::{Debug, Formatter}; +use std::path::PathBuf; +use thiserror::Error; enum DealingGeneration { Fresh { number: u32 }, @@ -36,12 +37,37 @@ impl Debug for DealingGeneration { } } +#[derive(Debug, Error)] +pub enum DealingGenerationError { + #[error(transparent)] + CoconutError(#[from] CoconutError), + + #[error("can't complete dealing exchange without registering public keys")] + IncompletePublicKeyRegistration, + + #[error("contract state failure - the DKG threshold is unavailable even though dealing exchange has been initiated")] + UnavailableContractThreshold, + + #[error("could not establish receiver index for epoch {epoch_id} even though we're a dealer!")] + UnavailableReceiverIndex { epoch_id: EpochId }, + + #[error("failed to archive coconut key for epoch {epoch_id} using path {}: {source}", path.display())] + KeyArchiveFailure { + epoch_id: EpochId, + path: PathBuf, + + // I hate that we're using anyhow error source here, but changing that would require bigger refactoring + #[source] + source: anyhow::Error, + }, +} + impl DkgController { async fn generate_dealings( &mut self, epoch_id: EpochId, spec: DealingGeneration, - ) -> Result, CoconutError> { + ) -> Result, DealingGenerationError> { let threshold = self.dkg_client.get_current_epoch_threshold().await?.ok_or( CoconutError::UnrecoverableState { reason: String::from("Threshold should have been set"), @@ -107,7 +133,7 @@ impl DkgController { &mut self, epoch_id: EpochId, number: u32, - ) -> Result, CoconutError> { + ) -> Result, DealingGenerationError> { self.generate_dealings(epoch_id, DealingGeneration::Fresh { number }) .await } @@ -116,7 +142,7 @@ impl DkgController { &mut self, epoch_id: EpochId, prior_secrets: Vec, - ) -> Result, CoconutError> { + ) -> Result, DealingGenerationError> { self.generate_dealings(epoch_id, DealingGeneration::Resharing { prior_secrets }) .await } @@ -125,7 +151,7 @@ impl DkgController { &self, epoch_id: EpochId, resharing: bool, - ) -> Result<(), CoconutError> { + ) -> Result<(), DealingGenerationError> { let dealing_state = self.state.dealing_exchange_state(epoch_id)?; for (dealing_index, dealing) in &dealing_state.generated_dealings { @@ -154,7 +180,7 @@ impl DkgController { /// Check whether this dealer can participate in the resharing /// by looking into the contract and ensuring it's in the list of initial dealers for this epoch - async fn can_reshare(&self) -> Result { + async fn can_reshare(&self) -> Result { let Some(initial_data) = self.dkg_client.get_initial_dealers().await? else { return Ok(false); }; @@ -173,7 +199,7 @@ impl DkgController { epoch_id: EpochId, expected_key_size: u32, old_keypair: KeyPairWithEpoch, - ) -> Result<(), CoconutError> { + ) -> Result<(), DealingGenerationError> { // make sure we're allowed to participate in resharing if !self.can_reshare().await? { // we have to wait for other dealers to give us the dealings (hopefully) @@ -222,7 +248,7 @@ impl DkgController { &mut self, epoch_id: EpochId, resharing: bool, - ) -> Result<(), CoconutError> { + ) -> Result<(), DealingGenerationError> { let dealing_state = self.state.dealing_exchange_state(epoch_id)?; // check if we have already submitted the dealings @@ -233,6 +259,10 @@ impl DkgController { return Ok(()); } + if !self.state.registration_state(epoch_id)?.completed() { + return Err(DealingGenerationError::IncompletePublicKeyRegistration); + } + // FAILURE CASE: // check if we have already generated the dealings, but they failed to get sent to the contract for whatever reason if !dealing_state.generated_dealings.is_empty() { @@ -267,8 +297,8 @@ impl DkgController { // 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") + // if we're in the dealing exchange phase, the threshold must have been already established + return Err(DealingGenerationError::UnavailableContractThreshold); }; self.state .key_derivation_state_mut(epoch_id)? @@ -280,7 +310,7 @@ impl DkgController { 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 }); + return Err(DealingGenerationError::UnavailableReceiverIndex { epoch_id }); }; self.state .dealing_exchange_state_mut(epoch_id)? @@ -314,7 +344,7 @@ impl DkgController { self.state.persist()?; // archive the keypair if let Err(source) = archive_coconut_keypair(&self.coconut_key_path, keypair_epoch) { - return Err(CoconutError::KeyArchiveFailure { + return Err(DealingGenerationError::KeyArchiveFailure { epoch_id, path: self.coconut_key_path.clone(), source, diff --git a/nym-api/src/coconut/dkg/key_derivation.rs b/nym-api/src/coconut/dkg/key_derivation.rs index 2da6106a4b..7ffef28fc4 100644 --- a/nym-api/src/coconut/dkg/key_derivation.rs +++ b/nym-api/src/coconut/dkg/key_derivation.rs @@ -31,12 +31,12 @@ pub enum KeyDerivationError { #[error(transparent)] CoconutError(#[from] CoconutError), - #[error("the initial, zeroth, epoch is set to be in resharing mode - this is illegal and should have been impossible!")] - ZerothEpochResharing, - #[error("can't complete key derivation without dealing exchange")] IncompleteDealingExchange, + #[error("the initial, zeroth, epoch is set to be in resharing mode - this is illegal and should have been impossible!")] + ZerothEpochResharing, + #[error("could not recover our own proposal id from the submitted share")] UnrecoverableProposalId, @@ -555,6 +555,9 @@ impl DkgController { "we have already generated key for this epoch and submitted validation proposal" ); return Ok(()); + } else if let Some(failure) = key_generation_state.completion_failure() { + error!("key derivation failed with unrecoverable failure: {failure}"); + return Ok(()); } if !self.state.dealing_exchange_state(epoch_id)?.completed { diff --git a/nym-api/src/coconut/dkg/key_validation.rs b/nym-api/src/coconut/dkg/key_validation.rs index 9b1fa2c94f..f69068852a 100644 --- a/nym-api/src/coconut/dkg/key_validation.rs +++ b/nym-api/src/coconut/dkg/key_validation.rs @@ -5,7 +5,7 @@ use crate::coconut::dkg::controller::DkgController; use crate::coconut::error::CoconutError; use crate::coconut::state::bandwidth_voucher_params; use cosmwasm_std::Addr; -use cw3::{Status, Vote}; +use cw3::Vote; use nym_coconut::{check_vk_pairing, Base58, VerificationKey}; use nym_coconut_dkg_common::types::EpochId; use nym_coconut_dkg_common::verification_key::ContractVKShare; diff --git a/nym-api/src/coconut/dkg/mod.rs b/nym-api/src/coconut/dkg/mod.rs index 56f53b8087..e1cbdc5849 100644 --- a/nym-api/src/coconut/dkg/mod.rs +++ b/nym-api/src/coconut/dkg/mod.rs @@ -9,7 +9,6 @@ pub(crate) fn params() -> &'static nym_dkg::bte::Params { } pub(crate) mod client; -pub(crate) mod complaints; pub(crate) mod controller; pub(crate) mod dealing; pub(crate) mod key_derivation; diff --git a/nym-api/src/coconut/dkg/public_key.rs b/nym-api/src/coconut/dkg/public_key.rs index 8c0ee74a86..f6463785ff 100644 --- a/nym-api/src/coconut/dkg/public_key.rs +++ b/nym-api/src/coconut/dkg/public_key.rs @@ -6,6 +6,13 @@ use crate::coconut::error::CoconutError; use log::debug; use nym_coconut_dkg_common::types::EpochId; use rand::{CryptoRng, RngCore}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum PublicKeySubmissionError { + #[error(transparent)] + CoconutError(#[from] CoconutError), +} impl DkgController { /// First step of the DKG process during which the nym api will register for the key exchange @@ -24,7 +31,7 @@ impl DkgController { &mut self, epoch_id: EpochId, resharing: bool, - ) -> Result<(), CoconutError> { + ) -> Result<(), PublicKeySubmissionError> { self.state.maybe_init_dkg_state(epoch_id); let registration_state = self.state.registration_state(epoch_id)?; diff --git a/nym-api/src/coconut/dkg/state/dealing_exchange.rs b/nym-api/src/coconut/dkg/state/dealing_exchange.rs index ef467a0afd..9721de86fd 100644 --- a/nym-api/src/coconut/dkg/state/dealing_exchange.rs +++ b/nym-api/src/coconut/dkg/state/dealing_exchange.rs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: GPL-3.0-only use super::serde_helpers::generated_dealings; +use crate::coconut::dkg::state::DkgParticipant; use nym_coconut_dkg_common::types::DealingIndex; use nym_dkg::{Dealing, NodeIndex}; use serde_derive::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; -use crate::coconut::dkg::state::DkgParticipant; -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub struct DealingExchangeState { pub(crate) dealers: BTreeMap, diff --git a/nym-api/src/coconut/dkg/state/in_progress.rs b/nym-api/src/coconut/dkg/state/in_progress.rs new file mode 100644 index 0000000000..7cca1d1c2c --- /dev/null +++ b/nym-api/src/coconut/dkg/state/in_progress.rs @@ -0,0 +1,11 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub struct InProgressState { + // indicate whether this node has been in this state before and performed any one-off tasks + pub(crate) entered: bool, +} diff --git a/nym-api/src/coconut/dkg/state/key_derivation.rs b/nym-api/src/coconut/dkg/state/key_derivation.rs index 3aa7612c59..40cd270f61 100644 --- a/nym-api/src/coconut/dkg/state/key_derivation.rs +++ b/nym-api/src/coconut/dkg/state/key_derivation.rs @@ -4,7 +4,6 @@ use super::serde_helpers::recovered_keys; use cosmwasm_std::Addr; use nym_coconut_dkg_common::types::{DealingIndex, EpochId}; -use nym_dkg::error::DkgError; use nym_dkg::{G2Projective, NodeIndex, RecoveredVerificationKeys, Threshold}; use serde_derive::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; @@ -69,7 +68,8 @@ pub enum DealerRejectionReason { }, } -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub struct KeyDerivationState { pub(crate) expected_threshold: Option, @@ -97,10 +97,6 @@ impl KeyDerivationState { Some(recovered) } - pub fn completed(&self) -> bool { - self.completed.is_some() - } - pub fn completed_with_success(&self) -> bool { matches!(self.completed, Some(Ok(_))) } diff --git a/nym-api/src/coconut/dkg/state/key_finalization.rs b/nym-api/src/coconut/dkg/state/key_finalization.rs index 40cd865ed5..625bcf706b 100644 --- a/nym-api/src/coconut/dkg/state/key_finalization.rs +++ b/nym-api/src/coconut/dkg/state/key_finalization.rs @@ -3,7 +3,8 @@ use serde_derive::{Deserialize, Serialize}; -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub struct FinalizationState { pub(crate) completed: bool, } diff --git a/nym-api/src/coconut/dkg/state/key_validation.rs b/nym-api/src/coconut/dkg/state/key_validation.rs index 68877c3f0e..473a39556e 100644 --- a/nym-api/src/coconut/dkg/state/key_validation.rs +++ b/nym-api/src/coconut/dkg/state/key_validation.rs @@ -6,7 +6,8 @@ use std::collections::HashMap; type ProposalId = u64; -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub struct ValidationState { pub votes: HashMap, diff --git a/nym-api/src/coconut/dkg/state/mod.rs b/nym-api/src/coconut/dkg/state/mod.rs index 42a52fe3aa..67cc6f3703 100644 --- a/nym-api/src/coconut/dkg/state/mod.rs +++ b/nym-api/src/coconut/dkg/state/mod.rs @@ -1,214 +1,50 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::coconut::dkg::complaints::ComplaintReason; -use crate::coconut::dkg::controller::keys::archive_coconut_keypair; use crate::coconut::dkg::state::dealing_exchange::DealingExchangeState; +use crate::coconut::dkg::state::in_progress::InProgressState; use crate::coconut::dkg::state::key_derivation::KeyDerivationState; use crate::coconut::dkg::state::key_finalization::FinalizationState; use crate::coconut::dkg::state::key_validation::ValidationState; -use crate::coconut::dkg::state::registration::RegistrationState; +use crate::coconut::dkg::state::registration::{ + DkgParticipant, ParticipantState, RegistrationState, +}; use crate::coconut::error::CoconutError; use crate::coconut::keys::{KeyPair as CoconutKeyPair, KeyPairWithEpoch}; use cosmwasm_std::Addr; use log::debug; use nym_coconut_dkg_common::dealer::DealerDetails; -use nym_coconut_dkg_common::types::{ - DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, EpochState, -}; +use nym_coconut_dkg_common::types::EpochId; use nym_crypto::asymmetric::identity; -use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; -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}; -use serde_helpers::{bte_pk_serde, generated_dealings_old, vks_serde}; +use nym_dkg::bte::keys::KeyPair as DkgKeyPair; +use nym_dkg::{bte, NodeIndex, Threshold}; +use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; use std::path::{Path, PathBuf}; use time::OffsetDateTime; -use tokio::sync::RwLockReadGuard; use url::Url; pub(crate) mod dealing_exchange; +pub(crate) mod in_progress; pub(crate) mod key_derivation; pub(crate) mod key_finalization; pub(crate) mod key_validation; pub(crate) mod registration; pub(crate) mod serde_helpers; -#[derive(Clone, Deserialize, Debug, Serialize)] -pub(crate) enum ParticipantState { - Invalid(ComplaintReason), - VerifiedKey(#[serde(with = "bte_pk_serde")] PublicKeyWithProof), -} - -impl ParticipantState { - pub fn is_valid(&self) -> bool { - 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 { - return ParticipantState::Invalid(ComplaintReason::MalformedBTEPublicKey); - }; - - let Ok(key) = PublicKeyWithProof::try_from_bytes(&bytes) else { - return ParticipantState::Invalid(ComplaintReason::MalformedBTEPublicKey); - }; - - if !key.verify() { - return ParticipantState::Invalid(ComplaintReason::InvalidBTEPublicKey); - } - - ParticipantState::VerifiedKey(key) - } -} - -// note: each dealer is also a receiver which simplifies some logic significantly -#[derive(Clone, Deserialize, Debug, Serialize)] -pub(crate) struct DkgParticipant { - pub(crate) address: Addr, - pub(crate) assigned_index: NodeIndex, - pub(crate) state: ParticipantState, -} - -impl DkgParticipant { - #[cfg(test)] - pub(crate) fn unwrap_key(&self) -> PublicKeyWithProof { - if let ParticipantState::VerifiedKey(key) = &self.state { - return key.clone(); - } - panic!("no key") - } -} - -impl From for DkgParticipant { - fn from(dealer: DealerDetails) -> Self { - DkgParticipant { - address: dealer.address, - state: ParticipantState::from_raw_encoded_key(dealer.bte_public_key_with_proof), - assigned_index: dealer.assigned_index, - } - } -} - -#[async_trait] -pub(crate) trait ConsistentState { - fn node_index_value(&self) -> Result; - fn receiver_index_value(&self) -> Result; - fn threshold(&self) -> Result; - async fn coconut_keypair_is_some(&self) -> Result<(), CoconutError>; - fn proposal_id_value(&self) -> Result; - async fn is_consistent(&self, epoch_state: EpochState) -> Result<(), CoconutError> { - match epoch_state { - EpochState::WaitingInitialisation => {} - EpochState::PublicKeySubmission { .. } => {} - EpochState::DealingExchange { .. } => { - self.node_index_value()?; - } - EpochState::VerificationKeySubmission { .. } => { - self.receiver_index_value()?; - self.threshold()?; - } - EpochState::VerificationKeyValidation { .. } => { - self.coconut_keypair_is_some().await?; - } - EpochState::VerificationKeyFinalization { .. } => { - self.proposal_id_value()?; - } - EpochState::InProgress => {} - } - Ok(()) - } -} - -#[async_trait] -impl ConsistentState for State { - fn node_index_value(&self) -> Result { - self.node_index.ok_or(CoconutError::UnrecoverableState { - reason: String::from("Node index should have been set"), - }) - } - - fn receiver_index_value(&self) -> Result { - self.receiver_index.ok_or(CoconutError::UnrecoverableState { - reason: String::from("Receiver index should have been set"), - }) - } - - fn threshold(&self) -> Result { - let threshold = self.threshold.ok_or(CoconutError::UnrecoverableState { - reason: String::from("Threshold should have been set"), - })?; - if self.current_dealers_by_idx().len() < threshold as usize { - Err(CoconutError::UnrecoverableState { - reason: String::from( - "Not enough good dealers in the signer set to achieve threshold", - ), - }) - } else { - Ok(threshold) - } - } - - async fn coconut_keypair_is_some(&self) -> Result<(), CoconutError> { - if self.coconut_keypair_is_some().await { - Ok(()) - } else { - Err(CoconutError::UnrecoverableState { - reason: String::from("Coconut keypair should have been set"), - }) - } - } - - fn proposal_id_value(&self) -> Result { - self.proposal_id.ok_or(CoconutError::UnrecoverableState { - reason: String::from("Proposal id should have been set"), - }) - } -} - #[derive(Deserialize, Serialize)] pub(crate) struct PersistentState { timestamp: OffsetDateTime, - // - node_index: Option, - dealers: BTreeMap>, - #[serde(with = "generated_dealings_old")] - generated_dealings: HashMap>, - receiver_index: Option, - threshold: Option, - #[serde(with = "vks_serde")] - recovered_vks: Vec, - proposal_id: Option, - voted_vks: bool, - executed_proposal: bool, - was_in_progress: bool, + dkg_instances: HashMap, } impl Default for PersistentState { fn default() -> Self { PersistentState { timestamp: OffsetDateTime::now_utc(), - node_index: None, - dealers: Default::default(), - generated_dealings: Default::default(), - receiver_index: None, - threshold: None, - recovered_vks: vec![], - proposal_id: None, - voted_vks: false, - executed_proposal: false, - was_in_progress: false, + + dkg_instances: Default::default(), } } } @@ -217,16 +53,8 @@ impl From<&State> for PersistentState { fn from(s: &State) -> Self { PersistentState { timestamp: OffsetDateTime::now_utc(), - node_index: s.node_index, - dealers: s.dealers.clone(), - generated_dealings: s.generated_dealings.clone(), - receiver_index: s.receiver_index, - threshold: s.threshold, - recovered_vks: s.recovered_vks.clone(), - proposal_id: s.proposal_id, - voted_vks: s.voted_vks, - executed_proposal: s.executed_proposal, - was_in_progress: s.was_in_progress, + + dkg_instances: s.dkg_instances.clone(), } } } @@ -243,7 +71,7 @@ impl PersistentState { } } -#[derive(Default)] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] pub(crate) struct DkgState { pub(crate) registration: RegistrationState, @@ -254,6 +82,8 @@ pub(crate) struct DkgState { pub(crate) key_validation: ValidationState, pub(crate) key_finalization: FinalizationState, + + pub(crate) in_progress: InProgressState, } impl DkgState { @@ -261,11 +91,9 @@ impl DkgState { assert!(self.dealing_exchange.dealers.is_empty()); for raw_dealer in raw_dealers { let dkg_participant = DkgParticipant::from(raw_dealer); - if let ParticipantState::Invalid(complaint) = &dkg_participant.state { - warn!( - "{} dealer is malformed: {complaint}", - dkg_participant.address - ) + let address = &dkg_participant.address; + if let ParticipantState::Invalid(rejection) = &dkg_participant.state { + warn!("{address} dealer is malformed: {rejection}",) } self.dealing_exchange .dealers @@ -280,35 +108,13 @@ pub(crate) struct State { dkg_instances: HashMap, - // - #[deprecated] announce_address: Url, - #[deprecated] + identity_key: identity::PublicKey, - #[deprecated] + dkg_keypair: DkgKeyPair, - #[deprecated] + coconut_keypair: CoconutKeyPair, - #[deprecated] - node_index: Option, - #[deprecated] - dealers: BTreeMap>, - #[deprecated] - generated_dealings: HashMap>, - #[deprecated] - receiver_index: Option, - #[deprecated] - threshold: Option, - #[deprecated] - recovered_vks: Vec, - #[deprecated] - proposal_id: Option, - #[deprecated] - voted_vks: bool, - #[deprecated] - executed_proposal: bool, - #[deprecated] - was_in_progress: bool, } impl State { @@ -322,21 +128,11 @@ impl State { ) -> Self { State { persistent_state_path, - dkg_instances: Default::default(), + dkg_instances: persistent_state.dkg_instances, announce_address, identity_key, dkg_keypair, coconut_keypair, - node_index: persistent_state.node_index, - dealers: persistent_state.dealers, - generated_dealings: persistent_state.generated_dealings, - receiver_index: persistent_state.receiver_index, - threshold: persistent_state.threshold, - recovered_vks: persistent_state.recovered_vks, - proposal_id: persistent_state.proposal_id, - voted_vks: persistent_state.voted_vks, - executed_proposal: persistent_state.executed_proposal, - was_in_progress: persistent_state.was_in_progress, } } @@ -344,22 +140,15 @@ impl State { PersistentState::from(self).save_to_file(self.persistent_state_path()) } - pub async fn reset_persistent(&mut self, reset_coconut_keypair: bool) { - if reset_coconut_keypair { - self.coconut_keypair.invalidate(); + pub fn clear_previous_epoch(&mut self, current_epoch: EpochId) { + if let Some(previous) = current_epoch.checked_sub(1) { + self.dkg_instances.remove(&previous); } - self.node_index = Default::default(); - self.dealers = Default::default(); - self.receiver_index = Default::default(); - self.threshold = Default::default(); - self.recovered_vks = Default::default(); - self.proposal_id = Default::default(); - self.voted_vks = Default::default(); - self.executed_proposal = Default::default(); - self.was_in_progress = Default::default(); } pub fn maybe_init_dkg_state(&mut self, epoch_id: EpochId) { + // given we're not using that entry here, I think the explicit check and insert is more readable + #[allow(clippy::map_entry)] if !self.dkg_instances.contains_key(&epoch_id) { self.dkg_instances.insert(epoch_id, Default::default()); } @@ -428,6 +217,23 @@ impl State { .ok_or(CoconutError::MissingDkgState { epoch_id }) } + pub fn in_progress_state(&self, epoch_id: EpochId) -> Result<&InProgressState, CoconutError> { + self.dkg_instances + .get(&epoch_id) + .map(|state| &state.in_progress) + .ok_or(CoconutError::MissingDkgState { epoch_id }) + } + + pub fn in_progress_state_mut( + &mut self, + epoch_id: EpochId, + ) -> Result<&mut InProgressState, CoconutError> { + self.dkg_instances + .get_mut(&epoch_id) + .map(|state| &mut state.in_progress) + .ok_or(CoconutError::MissingDkgState { epoch_id }) + } + pub fn dealing_exchange_state( &self, epoch_id: EpochId, @@ -564,142 +370,13 @@ impl State { self.coconut_keypair.validate() } - pub fn get_dealing(&self, epoch_id: EpochId, dealing_index: DealingIndex) -> Option<&Dealing> { - self.generated_dealings - .get(&epoch_id) - .and_then(|epoch_dealings| epoch_dealings.get(&dealing_index)) - } - - pub fn store_dealing( - &mut self, - epoch_id: EpochId, - dealing_index: DealingIndex, - dealing: Dealing, - ) { - self.generated_dealings - .entry(epoch_id) - .or_default() - .insert(dealing_index, dealing); - } - - pub async fn coconut_keypair( - &self, - ) -> Option>> { - self.coconut_keypair.get().await - } - pub async fn unchecked_coconut_keypair( &self, ) -> tokio::sync::RwLockReadGuard<'_, Option> { self.coconut_keypair.read_keys().await } - pub fn node_index(&self) -> Option { - self.node_index - } - - pub fn current_dealers_by_addr(&self) -> BTreeMap { - self.dealers - .iter() - .filter_map(|(addr, dealer)| { - dealer - .as_ref() - .ok() - .map(|participant| (addr.clone(), participant.assigned_index)) - }) - .collect() - } - - // FIXME: BUG: if we remove dealers, we won't be able to verify shares of other parties... - pub fn current_dealers_by_idx(&self) -> BTreeMap { - todo!() - // self.dealers - // .iter() - // .filter_map(|(_, dealer)| { - // dealer.as_ref().ok().map(|participant| { - // ( - // participant.assigned_index, - // *participant.bte_public_key_with_proof.public_key(), - // ) - // }) - // }) - // .collect() - } - - pub fn recovered_vks(&self) -> &Vec { - &self.recovered_vks - } - - pub fn voted_vks(&self) -> bool { - self.voted_vks - } - - pub fn executed_proposal(&self) -> bool { - self.executed_proposal - } - - pub fn was_in_progress(&self) -> bool { - self.was_in_progress - } - - pub fn set_recovered_vks(&mut self, recovered_vks: Vec) { - self.recovered_vks = recovered_vks; - } - pub async fn set_coconut_keypair(&mut self, coconut_keypair: KeyPairWithEpoch) { self.coconut_keypair.set(coconut_keypair).await } - - pub fn set_node_index(&mut self, node_index: Option) { - self.node_index = node_index; - } - - // pub fn set_dealers(&mut self, dealers: Vec) { - // self.dealers = BTreeMap::from_iter( - // dealers - // .into_iter() - // .map(|details| (details.address.clone(), DkgParticipant::try_from(details))), - // ) - // } - - pub fn mark_bad_dealer(&mut self, dealer_addr: &Addr, reason: ComplaintReason) { - if let Some((_, value)) = self - .dealers - .iter_mut() - .find(|(addr, _)| *addr == dealer_addr) - { - debug!( - "Dealer {dealer_addr} misbehaved: {reason:?}. It will be marked locally as bad dealer and ignored", - ); - *value = Err(reason); - } - } - - pub fn set_receiver_index(&mut self, receiver_index: Option) { - self.receiver_index = receiver_index; - } - - pub fn set_threshold(&mut self, threshold: Option) { - self.threshold = threshold; - } - - pub fn set_proposal_id(&mut self, proposal_id: u64) { - self.proposal_id = Some(proposal_id); - } - - pub fn set_voted_vks(&mut self) { - self.voted_vks = true; - } - - pub fn set_executed_proposal(&mut self) { - self.executed_proposal = true; - } - - pub fn set_was_in_progress(&mut self) { - self.was_in_progress = true; - } - - pub fn all_dealers(&self) -> &BTreeMap> { - &self.dealers - } } diff --git a/nym-api/src/coconut/dkg/state/registration.rs b/nym-api/src/coconut/dkg/state/registration.rs index 713a45f980..1314438115 100644 --- a/nym-api/src/coconut/dkg/state/registration.rs +++ b/nym-api/src/coconut/dkg/state/registration.rs @@ -1,10 +1,105 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_dkg::NodeIndex; +use crate::coconut::dkg::state::serde_helpers::bte_pk_serde; +use cosmwasm_std::Addr; +use nym_coconut_dkg_common::dealer::DealerDetails; +use nym_coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; +use nym_dkg::bte::PublicKeyWithProof; +use nym_dkg::{bte, NodeIndex}; use serde_derive::{Deserialize, Serialize}; +use thiserror::Error; -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Deserialize, Debug, Serialize)] +pub(crate) enum ParticipantState { + Invalid(KeyRejectionReason), + VerifiedKey(#[serde(with = "bte_pk_serde")] Box), +} + +impl ParticipantState { + pub fn is_valid(&self) -> bool { + 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 { + let bytes = match bs58::decode(raw).into_vec() { + Ok(bytes) => bytes, + Err(err) => { + return ParticipantState::Invalid( + KeyRejectionReason::MalformedBTEPublicKeyEncoding { + err_msg: err.to_string(), + }, + ); + } + }; + + let key = match PublicKeyWithProof::try_from_bytes(&bytes) { + Ok(key) => key, + Err(err) => { + return ParticipantState::Invalid(KeyRejectionReason::MalformedBTEPublicKey { + err_msg: err.to_string(), + }); + } + }; + + if !key.verify() { + return ParticipantState::Invalid(KeyRejectionReason::InvalidBTEPublicKey); + } + + ParticipantState::VerifiedKey(Box::new(key)) + } +} + +// note: each dealer is also a receiver which simplifies some logic significantly +#[derive(Clone, Deserialize, Debug, Serialize)] +pub(crate) struct DkgParticipant { + pub(crate) address: Addr, + pub(crate) assigned_index: NodeIndex, + pub(crate) state: ParticipantState, +} + +impl From for DkgParticipant { + fn from(dealer: DealerDetails) -> Self { + DkgParticipant { + address: dealer.address, + state: ParticipantState::from_raw_encoded_key(dealer.bte_public_key_with_proof), + assigned_index: dealer.assigned_index, + } + } +} + +impl DkgParticipant { + #[cfg(test)] + pub(crate) fn unwrap_key(&self) -> PublicKeyWithProof { + if let ParticipantState::VerifiedKey(key) = &self.state { + return *key.clone(); + } + panic!("no key") + } +} + +#[derive(Clone, Error, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub(crate) enum KeyRejectionReason { + #[error("provided BTE Public key encoding is malformed: {err_msg}")] + MalformedBTEPublicKeyEncoding { err_msg: String }, + + #[error("provided BTE Public key has invalid byte representation: {err_msg}")] + MalformedBTEPublicKey { err_msg: String }, + + #[error("provided BTE public key does not verify correctly")] + InvalidBTEPublicKey, +} + +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub struct RegistrationState { pub(crate) assigned_index: Option, } diff --git a/nym-api/src/coconut/dkg/state/serde_helpers.rs b/nym-api/src/coconut/dkg/state/serde_helpers.rs index 8980b47668..8057ef5d20 100644 --- a/nym-api/src/coconut/dkg/state/serde_helpers.rs +++ b/nym-api/src/coconut/dkg/state/serde_helpers.rs @@ -13,40 +13,14 @@ pub(super) mod bte_pk_serde { val.to_bytes().serialize(serializer) } - pub fn deserialize<'de, D>(deserializer: D) -> Result + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let vec: Vec = Deserialize::deserialize(deserializer)?; PublicKeyWithProof::try_from_bytes(&vec) .map_err(|err| Error::custom(format_args!("{:?}", err))) - } -} - -pub(super) mod vks_serde { - use nym_dkg::RecoveredVerificationKeys; - use serde::de::Error; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - pub fn serialize( - val: &[RecoveredVerificationKeys], - serializer: S, - ) -> Result { - let vec: Vec> = val.iter().map(|vk| vk.to_bytes()).collect(); - vec.serialize(serializer) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let vec: Vec> = Deserialize::deserialize(deserializer)?; - vec.into_iter() - .map(|b| { - RecoveredVerificationKeys::try_from_bytes(&b) - .map_err(|err| D::Error::custom(format_args!("{:?}", err))) - }) - .collect() + .map(Box::new) } } @@ -55,7 +29,7 @@ pub(super) mod recovered_keys { use nym_dkg::RecoveredVerificationKeys; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use std::collections::{BTreeMap, HashMap}; + use std::collections::BTreeMap; type Helper = BTreeMap>; @@ -120,45 +94,3 @@ pub(super) mod generated_dealings { .collect() } } - -pub(super) mod generated_dealings_old { - use nym_coconut_dkg_common::types::{DealingIndex, EpochId}; - use nym_dkg::Dealing; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use std::collections::HashMap; - - type Helper = HashMap>>; - - pub fn serialize( - dealings: &HashMap>, - serializer: S, - ) -> Result { - let mut helper = HashMap::new(); - for (epoch, dealings) in dealings { - let mut inner = HashMap::new(); - for (dealing_index, dealing) in dealings { - inner.insert(*dealing_index, dealing.to_bytes()); - } - helper.insert(*epoch, inner); - } - helper.serialize(serializer) - } - - pub fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result>, D::Error> { - let helper = ::deserialize(deserializer)?; - - let mut epoch_dealings = HashMap::with_capacity(helper.len()); - for (epoch, dealings) in helper { - let mut inner = HashMap::with_capacity(dealings.len()); - for (dealing_index, raw_dealing) in dealings { - let dealing = - Dealing::try_from_bytes(&raw_dealing).map_err(serde::de::Error::custom)?; - inner.insert(dealing_index, dealing); - } - epoch_dealings.insert(epoch, inner); - } - Ok(epoch_dealings) - } -} diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 2b6eb98ffb..40359a4eb3 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -1,24 +1,20 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::node_status_api::models::NymApiStorageError; use nym_coconut_dkg_common::types::EpochId; -use rocket::http::{ContentType, Status}; -use rocket::response::Responder; -use rocket::{response, Request, Response}; -use std::io::Cursor; -use std::path::PathBuf; -use thiserror::Error; - use nym_crypto::asymmetric::{ encryption::KeyRecoveryError, identity::{Ed25519RecoveryError, SignatureError}, }; use nym_dkg::error::DkgError; -use nym_pemstore::KeyPairPath; use nym_validator_client::coconut::CoconutApiError; use nym_validator_client::nyxd::error::{NyxdError, TendermintError}; - -use crate::node_status_api::models::NymApiStorageError; +use rocket::http::{ContentType, Status}; +use rocket::response::Responder; +use rocket::{response, Request, Response}; +use std::io::Cursor; +use thiserror::Error; pub type Result = std::result::Result; @@ -121,16 +117,6 @@ pub enum CoconutError { #[error("the coconut keypair is corrupted")] CorruptedCoconutKeyPair, - #[error("failed to archive coconut key for epoch {epoch_id} using path {}: {source}", path.display())] - KeyArchiveFailure { - epoch_id: EpochId, - path: PathBuf, - - // I hate that we're using anyhow error source here, but changing that would require bigger refactoring - #[source] - source: anyhow::Error, - }, - #[error("there was a problem with the proposal id: {reason}")] ProposalIdError { reason: String }, diff --git a/nym-api/src/coconut/keys/mod.rs b/nym-api/src/coconut/keys/mod.rs index 1dadbbae0f..caef1fa89b 100644 --- a/nym-api/src/coconut/keys/mod.rs +++ b/nym-api/src/coconut/keys/mod.rs @@ -1,16 +1,14 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -mod persistence; - use nym_coconut_dkg_common::types::EpochId; use nym_dkg::Scalar; -use std::collections::HashMap; -use std::ops::Deref; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::sync::{RwLock, RwLockReadGuard}; +mod persistence; + #[derive(Clone, Debug)] pub struct KeyPair { // keys: Arc>>, diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs index 8eb8a117e0..de38ca614f 100644 --- a/nym-api/src/coconut/state.rs +++ b/nym-api/src/coconut/state.rs @@ -8,7 +8,6 @@ use crate::coconut::error::Result; use crate::coconut::keys::KeyPair; use crate::coconut::storage::CoconutStorageExt; use crate::support::storage::NymApiStorage; -use lazy_static::lazy_static; use nym_api_requests::coconut::helpers::issued_credential_plaintext; use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut::Parameters; diff --git a/nym-api/src/coconut/tests/fixtures.rs b/nym-api/src/coconut/tests/fixtures.rs index 720606fc61..4264ceff26 100644 --- a/nym-api/src/coconut/tests/fixtures.rs +++ b/nym-api/src/coconut/tests/fixtures.rs @@ -188,7 +188,7 @@ impl TestingDkgControllerBuilder { } TestingDkgController { - controller: DkgController::test_mock_new( + controller: DkgController::test_mock( rng, dummy_client, state, diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs index c5cfb53454..5b09e3612b 100644 --- a/nym-api/src/main.rs +++ b/nym-api/src/main.rs @@ -4,7 +4,9 @@ #[macro_use] extern crate rocket; -use crate::coconut::dkg::controller::keys::{load_bte_keypair, load_coconut_keypair_if_exists}; +use crate::coconut::dkg::controller::keys::{ + can_validate_coconut_keys, load_bte_keypair, load_coconut_keypair_if_exists, +}; use crate::epoch_operations::RewardedSetUpdater; use crate::network::models::NetworkDetails; use crate::node_describe_cache::DescribedNodes; @@ -16,7 +18,6 @@ use crate::support::storage; use crate::support::storage::NymApiStorage; use ::ephemera::configuration::Configuration as EphemeraConfiguration; use ::nym_config::defaults::setup_env; -use anyhow::Result; use circulating_supply_api::cache::CirculatingSupplyCache; use clap::Parser; use coconut::dkg::controller::DkgController; @@ -73,8 +74,12 @@ async fn start_nym_api_tasks(config: Config) -> anyhow::Result // if the keypair doesnt exist (because say this API is running in the caching mode), nothing will happen if let Some(loaded_keys) = load_coconut_keypair_if_exists(&config.coconut_signer)? { - todo!() - // coconut_keypair_wrapper.set(loaded_keys).await + let issued_for = loaded_keys.issued_for_epoch; + coconut_keypair_wrapper.set(loaded_keys).await; + + if can_validate_coconut_keys(&nyxd_client, issued_for).await? { + coconut_keypair_wrapper.validate() + } } let identity_keypair = config.base.storage_paths.load_identity()?;