cleanup
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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}")]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<bool> {
|
||||
// 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<P: AsRef<Path>>(
|
||||
keys: &KeyPairWithEpoch,
|
||||
store_path: P,
|
||||
|
||||
@@ -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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
}
|
||||
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
}
|
||||
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
.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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
|
||||
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<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
|
||||
#[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<rand_chacha::ChaCha20Rng> {
|
||||
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,
|
||||
|
||||
@@ -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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
async fn generate_dealings(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
spec: DealingGeneration,
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, CoconutError> {
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, 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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
number: u32,
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, CoconutError> {
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, DealingGenerationError> {
|
||||
self.generate_dealings(epoch_id, DealingGeneration::Fresh { number })
|
||||
.await
|
||||
}
|
||||
@@ -116,7 +142,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
prior_secrets: Vec<Scalar>,
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, CoconutError> {
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, DealingGenerationError> {
|
||||
self.generate_dealings(epoch_id, DealingGeneration::Resharing { prior_secrets })
|
||||
.await
|
||||
}
|
||||
@@ -125,7 +151,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
&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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
|
||||
/// 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<bool, CoconutError> {
|
||||
async fn can_reshare(&self) -> Result<bool, DealingGenerationError> {
|
||||
let Some(initial_data) = self.dkg_client.get_initial_dealers().await? else {
|
||||
return Ok(false);
|
||||
};
|
||||
@@ -173,7 +199,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
&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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
|
||||
// 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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
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,
|
||||
|
||||
@@ -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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
"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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<R: RngCore + CryptoRng> DkgController<R> {
|
||||
/// First step of the DKG process during which the nym api will register for the key exchange
|
||||
@@ -24,7 +31,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
&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)?;
|
||||
|
||||
|
||||
@@ -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<NodeIndex, DkgParticipant>,
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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,
|
||||
}
|
||||
@@ -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<Threshold>,
|
||||
|
||||
@@ -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(_)))
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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<ProposalId, bool>,
|
||||
|
||||
|
||||
@@ -1,214 +1,50 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<bte::PublicKey> {
|
||||
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<DealerDetails> 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<NodeIndex, CoconutError>;
|
||||
fn receiver_index_value(&self) -> Result<usize, CoconutError>;
|
||||
fn threshold(&self) -> Result<Threshold, CoconutError>;
|
||||
async fn coconut_keypair_is_some(&self) -> Result<(), CoconutError>;
|
||||
fn proposal_id_value(&self) -> Result<u64, CoconutError>;
|
||||
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<NodeIndex, CoconutError> {
|
||||
self.node_index.ok_or(CoconutError::UnrecoverableState {
|
||||
reason: String::from("Node index should have been set"),
|
||||
})
|
||||
}
|
||||
|
||||
fn receiver_index_value(&self) -> Result<usize, CoconutError> {
|
||||
self.receiver_index.ok_or(CoconutError::UnrecoverableState {
|
||||
reason: String::from("Receiver index should have been set"),
|
||||
})
|
||||
}
|
||||
|
||||
fn threshold(&self) -> Result<Threshold, CoconutError> {
|
||||
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<u64, CoconutError> {
|
||||
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<NodeIndex>,
|
||||
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
|
||||
#[serde(with = "generated_dealings_old")]
|
||||
generated_dealings: HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
|
||||
receiver_index: Option<usize>,
|
||||
threshold: Option<Threshold>,
|
||||
#[serde(with = "vks_serde")]
|
||||
recovered_vks: Vec<RecoveredVerificationKeys>,
|
||||
proposal_id: Option<u64>,
|
||||
voted_vks: bool,
|
||||
executed_proposal: bool,
|
||||
was_in_progress: bool,
|
||||
dkg_instances: HashMap<EpochId, DkgState>,
|
||||
}
|
||||
|
||||
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<EpochId, DkgState>,
|
||||
|
||||
//
|
||||
#[deprecated]
|
||||
announce_address: Url,
|
||||
#[deprecated]
|
||||
|
||||
identity_key: identity::PublicKey,
|
||||
#[deprecated]
|
||||
|
||||
dkg_keypair: DkgKeyPair,
|
||||
#[deprecated]
|
||||
|
||||
coconut_keypair: CoconutKeyPair,
|
||||
#[deprecated]
|
||||
node_index: Option<NodeIndex>,
|
||||
#[deprecated]
|
||||
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
|
||||
#[deprecated]
|
||||
generated_dealings: HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
|
||||
#[deprecated]
|
||||
receiver_index: Option<usize>,
|
||||
#[deprecated]
|
||||
threshold: Option<Threshold>,
|
||||
#[deprecated]
|
||||
recovered_vks: Vec<RecoveredVerificationKeys>,
|
||||
#[deprecated]
|
||||
proposal_id: Option<u64>,
|
||||
#[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<tokio::sync::RwLockReadGuard<'_, Option<KeyPairWithEpoch>>> {
|
||||
self.coconut_keypair.get().await
|
||||
}
|
||||
|
||||
pub async fn unchecked_coconut_keypair(
|
||||
&self,
|
||||
) -> tokio::sync::RwLockReadGuard<'_, Option<KeyPairWithEpoch>> {
|
||||
self.coconut_keypair.read_keys().await
|
||||
}
|
||||
|
||||
pub fn node_index(&self) -> Option<NodeIndex> {
|
||||
self.node_index
|
||||
}
|
||||
|
||||
pub fn current_dealers_by_addr(&self) -> BTreeMap<Addr, NodeIndex> {
|
||||
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<NodeIndex, PublicKey> {
|
||||
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<RecoveredVerificationKeys> {
|
||||
&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<RecoveredVerificationKeys>) {
|
||||
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<NodeIndex>) {
|
||||
self.node_index = node_index;
|
||||
}
|
||||
|
||||
// pub fn set_dealers(&mut self, dealers: Vec<DealerDetails>) {
|
||||
// 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<usize>) {
|
||||
self.receiver_index = receiver_index;
|
||||
}
|
||||
|
||||
pub fn set_threshold(&mut self, threshold: Option<Threshold>) {
|
||||
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<Addr, Result<DkgParticipant, ComplaintReason>> {
|
||||
&self.dealers
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,105 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<PublicKeyWithProof>),
|
||||
}
|
||||
|
||||
impl ParticipantState {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
matches!(self, ParticipantState::VerifiedKey(..))
|
||||
}
|
||||
|
||||
pub fn public_key(&self) -> Option<bte::PublicKey> {
|
||||
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<DealerDetails> 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<NodeIndex>,
|
||||
}
|
||||
|
||||
@@ -13,40 +13,14 @@ pub(super) mod bte_pk_serde {
|
||||
val.to_bytes().serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<PublicKeyWithProof, D::Error>
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Box<PublicKeyWithProof>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let vec: Vec<u8> = 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<S: Serializer>(
|
||||
val: &[RecoveredVerificationKeys],
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let vec: Vec<Vec<u8>> = val.iter().map(|vk| vk.to_bytes()).collect();
|
||||
vec.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<RecoveredVerificationKeys>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let vec: Vec<Vec<u8>> = 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<DealingIndex, Vec<u8>>;
|
||||
|
||||
@@ -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<EpochId, HashMap<DealingIndex, Vec<u8>>>;
|
||||
|
||||
pub fn serialize<S: Serializer>(
|
||||
dealings: &HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
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<HashMap<EpochId, HashMap<DealingIndex, Dealing>>, D::Error> {
|
||||
let helper = <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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<T> = std::result::Result<T, CoconutError>;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<RwLock<HashMap<EpochId, nym_coconut_interface::KeyPair>>>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -188,7 +188,7 @@ impl TestingDkgControllerBuilder {
|
||||
}
|
||||
|
||||
TestingDkgController {
|
||||
controller: DkgController::test_mock_new(
|
||||
controller: DkgController::test_mock(
|
||||
rng,
|
||||
dummy_client,
|
||||
state,
|
||||
|
||||
+9
-4
@@ -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<ShutdownHandles>
|
||||
|
||||
// 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()?;
|
||||
|
||||
Reference in New Issue
Block a user