happy path for key derivation
This commit is contained in:
@@ -19,7 +19,7 @@ pub use nym_coconut_dkg_common::{
|
||||
DealerDetails, DealingIndex, Epoch, EpochId, InitialReplacementData,
|
||||
PartialContractDealing, State,
|
||||
},
|
||||
verification_key::{ContractVKShare, PagedVKSharesResponse},
|
||||
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
|
||||
};
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -123,6 +123,15 @@ pub trait DkgQueryClient {
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_vk_share(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
owner: String,
|
||||
) -> Result<VkShareResponse, NyxdError> {
|
||||
let request = DkgQueryMsg::GetVerificationKey { epoch_id, owner };
|
||||
self.query_dkg_contract(request).await
|
||||
}
|
||||
|
||||
async fn get_vk_shares_paged(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
@@ -240,6 +249,9 @@ mod tests {
|
||||
} => client
|
||||
.get_dealer_dealings_paged(epoch_id, &dealer, limit, start_after)
|
||||
.ignore(),
|
||||
DkgQueryMsg::GetVerificationKey { epoch_id, owner } => {
|
||||
client.get_vk_share(epoch_id, owner).ignore()
|
||||
}
|
||||
DkgQueryMsg::GetVerificationKeys {
|
||||
epoch_id,
|
||||
limit,
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
PagedDealingsResponse,
|
||||
},
|
||||
types::{Epoch, InitialReplacementData, State},
|
||||
verification_key::PagedVKSharesResponse,
|
||||
verification_key::{PagedVKSharesResponse, VkShareResponse},
|
||||
};
|
||||
use contracts_common::IdentityKey;
|
||||
#[cfg(feature = "schema")]
|
||||
@@ -117,6 +117,9 @@ pub enum QueryMsg {
|
||||
start_after: Option<DealingIndex>,
|
||||
},
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(VkShareResponse))]
|
||||
GetVerificationKey { epoch_id: EpochId, owner: String },
|
||||
|
||||
#[cfg_attr(feature = "schema", returns(PagedVKSharesResponse))]
|
||||
GetVerificationKeys {
|
||||
epoch_id: EpochId,
|
||||
|
||||
@@ -20,6 +20,13 @@ pub struct ContractVKShare {
|
||||
pub verified: bool,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct VkShareResponse {
|
||||
pub owner: Addr,
|
||||
pub epoch_id: EpochId,
|
||||
pub share: Option<ContractVKShare>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct PagedVKSharesResponse {
|
||||
pub shares: Vec<ContractVKShare>,
|
||||
|
||||
@@ -13,7 +13,7 @@ pub mod dealing;
|
||||
pub(crate) mod share;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub use bls12_381::Scalar;
|
||||
pub use bls12_381::{Scalar, G2Projective};
|
||||
pub use dealing::*;
|
||||
pub use share::*;
|
||||
|
||||
|
||||
@@ -45,6 +45,12 @@ pub trait Client {
|
||||
epoch_id: EpochId,
|
||||
dealer: &str,
|
||||
) -> Result<Vec<PartialContractDealing>>;
|
||||
|
||||
async fn get_verification_key_share(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> Result<Option<ContractVKShare>>;
|
||||
async fn get_verification_key_shares(&self, epoch_id: EpochId) -> Result<Vec<ContractVKShare>>;
|
||||
async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option<Fee>)
|
||||
-> Result<()>;
|
||||
|
||||
@@ -79,7 +79,7 @@ impl DkgClient {
|
||||
let address = self.inner.address().await.to_string();
|
||||
|
||||
self.inner
|
||||
.get_dealing_status(epoch_id, address.clone(), dealing_index)
|
||||
.get_dealing_status(epoch_id, address, dealing_index)
|
||||
.await
|
||||
.map(|r| r.dealing_submitted)
|
||||
}
|
||||
@@ -92,6 +92,18 @@ impl DkgClient {
|
||||
self.inner.get_dealings(epoch_id, &dealer).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_verification_key_share_status(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<bool>, CoconutError> {
|
||||
let address = self.inner.address().await.to_string();
|
||||
|
||||
self.inner
|
||||
.get_verification_key_share(epoch_id, address)
|
||||
.await
|
||||
.map(|maybe_share| maybe_share.map(|s| s.verified))
|
||||
}
|
||||
|
||||
pub(crate) async fn get_verification_key_shares(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
|
||||
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::verification_key::verification_key_submission;
|
||||
use crate::coconut::dkg::verification_key::{
|
||||
use crate::coconut::dkg::key_derivation::{
|
||||
verification_key_finalization, verification_key_validation,
|
||||
};
|
||||
use crate::coconut::dkg::state::{ConsistentState, PersistentState, State};
|
||||
use crate::coconut::keys::KeyPair as CoconutKeyPair;
|
||||
use crate::nyxd;
|
||||
use crate::support::config;
|
||||
@@ -18,7 +17,6 @@ use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
@@ -153,16 +151,9 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
resharing: bool,
|
||||
) -> Result<(), DkgError> {
|
||||
debug!("DKG: verification key submission (resharing: {resharing})");
|
||||
|
||||
verification_key_submission(
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
epoch_id,
|
||||
&self.coconut_key_path,
|
||||
resharing,
|
||||
)
|
||||
.await
|
||||
.map_err(|source| DkgError::VerificationKeySubmissionFailure { source })
|
||||
self.verification_key_submission(epoch_id, resharing)
|
||||
.await
|
||||
.map_err(|source| DkgError::VerificationKeySubmissionFailure { source })
|
||||
}
|
||||
|
||||
async fn handle_verification_key_validation(
|
||||
@@ -244,6 +235,8 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
if let Some(epoch_finish) = epoch.finish_timestamp {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if now.unix_timestamp() > epoch_finish.seconds() as i64 {
|
||||
// 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?
|
||||
}
|
||||
}
|
||||
@@ -306,10 +299,10 @@ impl DkgController {
|
||||
}
|
||||
|
||||
pub(crate) fn test_mock_new(
|
||||
rng: ChaCha20Rng,
|
||||
rng: rand_chacha::ChaCha20Rng,
|
||||
dkg_client: DkgClient,
|
||||
state: State,
|
||||
) -> DkgController<ChaCha20Rng> {
|
||||
) -> DkgController<rand_chacha::ChaCha20Rng> {
|
||||
DkgController {
|
||||
dkg_client,
|
||||
coconut_key_path: Default::default(),
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut::dkg;
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::complaints::ComplaintReason;
|
||||
use crate::coconut::dkg::controller::keys::archive_coconut_keypair;
|
||||
use crate::coconut::dkg::controller::DkgController;
|
||||
use crate::coconut::dkg::state::{ConsistentState, ParticipantState, State};
|
||||
use crate::coconut::dkg::state::ParticipantState;
|
||||
use crate::coconut::error::CoconutError;
|
||||
use crate::coconut::keys::KeyPairWithEpoch;
|
||||
use log::debug;
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ContractDealing, DealingIndex, EpochId, PartialContractDealing,
|
||||
};
|
||||
use nym_dkg::bte::{PublicKey, PublicKeyWithProof};
|
||||
use nym_dkg::{Dealing, NodeIndex, Scalar, Threshold};
|
||||
use nym_dkg::{Dealing, Scalar};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rocket::form::validate::Len;
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
enum DealingGeneration {
|
||||
Fresh { number: u32 },
|
||||
@@ -180,6 +175,8 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
.any(|d| d.as_str() == address.as_ref()))
|
||||
}
|
||||
|
||||
/// Deal with the dealing generation case where the system requests resharing
|
||||
/// and this node contains an already derived coconut keypair from some previous epoch.
|
||||
async fn handle_resharing_with_prior_key(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
@@ -225,6 +222,11 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Second step of the DKG process during which the nym api will generate appropriate [Dealing] for
|
||||
/// other parties as indicated by public key registration from the previous step.
|
||||
///
|
||||
/// Before submitting any dealings to the system, the node will persist them locally so that if any failure
|
||||
/// occurs, it will be possible to recover.
|
||||
pub(crate) async fn dealing_exchange(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
@@ -232,6 +234,13 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
) -> Result<(), CoconutError> {
|
||||
let dealing_state = self.state.dealing_exchange_state(epoch_id)?;
|
||||
|
||||
// TODO:
|
||||
/*
|
||||
let receiver_index = receivers
|
||||
.keys()
|
||||
.position(|node_index| *node_index == dealer_index);
|
||||
*/
|
||||
|
||||
// check if we have already submitted the dealings
|
||||
if dealing_state.completed() {
|
||||
// the only way this could be a false positive is if the chain forked and blocks got reverted,
|
||||
@@ -258,7 +267,8 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
let dealers = self.dkg_client.get_current_dealers().await?;
|
||||
|
||||
// EDGE CASE:
|
||||
// if there are no dealers for some reason, don't attempt to generate dealings as this will fail with a panic
|
||||
// if there are no receivers(dealers) in this epoch for some reason,
|
||||
// don't attempt to generate dealings as this will fail with a panic
|
||||
if dealers.is_empty() {
|
||||
warn!("there are no active dealers/receivers to generate dealings for");
|
||||
self.state.dealing_exchange_state_mut(epoch_id)?.completed = true;
|
||||
@@ -315,12 +325,18 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
}
|
||||
}
|
||||
|
||||
// if we have generated any dealings => submit them, otherwise we're done
|
||||
let dealings = &self
|
||||
.state
|
||||
.dealing_exchange_state(epoch_id)?
|
||||
.generated_dealings;
|
||||
let total = dealings.len();
|
||||
|
||||
// if we have generated any dealings persist the state in case we crash so that we would still have the data on hand
|
||||
// for resubmission upon getting back up
|
||||
if total > 0 {
|
||||
self.state.persist()?;
|
||||
}
|
||||
|
||||
for (i, (dealing_index, dealing)) in dealings.iter().enumerate() {
|
||||
let i = i + 1;
|
||||
debug!("submitting dealing index {dealing_index} ({i}/{total})");
|
||||
|
||||
+354
-48
@@ -1,12 +1,15 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut::dkg;
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::complaints::ComplaintReason;
|
||||
use crate::coconut::dkg::controller::keys::persist_coconut_keypair;
|
||||
use crate::coconut::dkg::controller::DkgController;
|
||||
use crate::coconut::dkg::state::{ConsistentState, State};
|
||||
use crate::coconut::error::CoconutError;
|
||||
use crate::coconut::helpers::accepted_vote_err;
|
||||
use crate::coconut::keys::KeyPairWithEpoch;
|
||||
use crate::coconut::state::BANDWIDTH_CREDENTIAL_PARAMS;
|
||||
use cosmwasm_std::Addr;
|
||||
use cw3::{ProposalResponse, Status};
|
||||
@@ -14,14 +17,15 @@ use log::debug;
|
||||
use nym_coconut::tests::helpers::transpose_matrix;
|
||||
use nym_coconut::{check_vk_pairing, Base58, KeyPair, SecretKey, VerificationKey};
|
||||
use nym_coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID;
|
||||
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
|
||||
use nym_coconut_dkg_common::types::{DealingIndex, EpochId, NodeIndex, PartialContractDealing};
|
||||
use nym_coconut_dkg_common::verification_key::owner_from_cosmos_msgs;
|
||||
use nym_coconut_interface::KeyPair as CoconutKeyPair;
|
||||
use nym_dkg::bte::decrypt_share;
|
||||
use nym_dkg::bte::{decrypt_share, PublicKey};
|
||||
use nym_dkg::error::DkgError;
|
||||
use nym_dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold};
|
||||
use nym_dkg::{bte, combine_shares, try_recover_verification_keys, Dealing, Threshold};
|
||||
use nym_pemstore::KeyPairPath;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::logs::find_attribute;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -38,6 +42,9 @@ async fn deterministic_filter_dealers(
|
||||
threshold: Threshold,
|
||||
resharing: bool,
|
||||
) -> Result<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, CoconutError> {
|
||||
// if we're in resharing mode, the contract itself will forbid submission of dealings from
|
||||
// parties that were not "initial" dealers, so we don't have to worry about it
|
||||
|
||||
let mut dealings_maps = Vec::new();
|
||||
let initial_dealers_by_addr = state.current_dealers_by_addr();
|
||||
let initial_receivers = state.current_dealers_by_idx();
|
||||
@@ -204,52 +211,351 @@ fn derive_partial_keypair(
|
||||
Ok(CoconutKeyPair::from_keys(sk, vk))
|
||||
}
|
||||
|
||||
pub(crate) async fn verification_key_submission(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
epoch_id: EpochId,
|
||||
key_path: &PathBuf,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.coconut_keypair_is_some().await {
|
||||
debug!("Coconut keypair was set previously, nothing to do");
|
||||
return Ok(());
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
fn verified_dealer_dealings(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: Addr,
|
||||
epoch_receivers: &BTreeMap<NodeIndex, bte::PublicKey>,
|
||||
raw_dealings: Vec<PartialContractDealing>,
|
||||
prior_public_key: Option<VerificationKey>,
|
||||
) -> Result<Vec<(DealingIndex, Dealing)>, CoconutError> {
|
||||
let threshold = self.state.threshold(epoch_id)?;
|
||||
|
||||
// extract G2 elements from the old verification key of the dealer for checking the resharing dealings
|
||||
let prior_public_components = match prior_public_key {
|
||||
Some(vk) => {
|
||||
if vk.beta_g2().len() != raw_dealings.len().saturating_sub(1) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
std::iter::once(Some(*vk.alpha()))
|
||||
.chain(vk.beta_g2().iter().copied().map(Some))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
None => vec![None; raw_dealings.len()],
|
||||
};
|
||||
|
||||
let mut temp_verified = Vec::with_capacity(raw_dealings.len());
|
||||
// make sure ALL of them verify correctly, we can't have a situation where dealing 2 is valid but dealing 3 is not
|
||||
for (raw_dealing, prior_public) in raw_dealings
|
||||
.into_iter()
|
||||
.zip(prior_public_components.into_iter())
|
||||
{
|
||||
let index = raw_dealing.index;
|
||||
|
||||
// recover the actual dealing from its submitted bytes representation
|
||||
let dealing = match Dealing::try_from_bytes(&raw_dealing.data) {
|
||||
Ok(dealing) => dealing,
|
||||
Err(err) => {
|
||||
warn!("failed to recover dealing {index} from {dealer}: {err}",);
|
||||
todo!("blacklist")
|
||||
}
|
||||
};
|
||||
|
||||
// make sure the cryptographic material embedded inside is actually valid
|
||||
if let Err(err) =
|
||||
dealing.verify(dkg::params(), threshold, epoch_receivers, prior_public)
|
||||
{
|
||||
warn!("dealing {index} from {dealer} is invalid: {err}");
|
||||
todo!("blacklist")
|
||||
}
|
||||
|
||||
temp_verified.push((index, dealing))
|
||||
}
|
||||
|
||||
Ok(temp_verified)
|
||||
}
|
||||
|
||||
todo!()
|
||||
//
|
||||
// let threshold = state.threshold()?;
|
||||
// let dealings_maps =
|
||||
// deterministic_filter_dealers(dkg_client, state, epoch_id, threshold, resharing).await?;
|
||||
// debug!(
|
||||
// "Filtered dealers to {:?}",
|
||||
// dealings_maps[0].keys().collect::<Vec<_>>()
|
||||
// );
|
||||
// let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?;
|
||||
// debug!("Derived own coconut keypair");
|
||||
// let vk_share = coconut_keypair.verification_key().to_bs58();
|
||||
// nym_pemstore::store_keypair(&coconut_keypair, keypair_path)?;
|
||||
// let res = dkg_client
|
||||
// .submit_verification_key_share(vk_share, resharing)
|
||||
// .await?;
|
||||
// let proposal_id = find_attribute(&res.logs, "wasm", DKG_PROPOSAL_ID)
|
||||
// .ok_or(CoconutError::ProposalIdError {
|
||||
// reason: String::from("proposal id not found"),
|
||||
// })?
|
||||
// .value
|
||||
// .parse::<u64>()
|
||||
// .map_err(|_| CoconutError::ProposalIdError {
|
||||
// reason: String::from("proposal id could not be parsed to u64"),
|
||||
// })?;
|
||||
// debug!(
|
||||
// "Submitted own verification key share, proposal id {} is attached to it",
|
||||
// proposal_id
|
||||
// );
|
||||
// state.set_proposal_id(proposal_id);
|
||||
// state.set_coconut_keypair(epoch_id, coconut_keypair).await;
|
||||
// info!("DKG: Submitted own verification key");
|
||||
//
|
||||
// Ok(())
|
||||
/// Attempt to retrieve valid dealings submitted this epoch.
|
||||
///
|
||||
/// For each dealer that submitted a valid public key, query its dealings.
|
||||
/// Then for each of those dealings, make sure they're cryptographically consistent
|
||||
pub(crate) async fn get_valid_dealings(
|
||||
&mut self,
|
||||
epoch_receivers: &BTreeMap<NodeIndex, bte::PublicKey>,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<BTreeMap<DealingIndex, BTreeMap<NodeIndex, Dealing>>, CoconutError> {
|
||||
let expected_key_size = self.dkg_client.get_contract_state().await?.key_size;
|
||||
|
||||
let mut valid_dealings: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new();
|
||||
|
||||
// 1. for every valid dealer in this epoch, obtain its dealings
|
||||
for (dealer, dealer_index) in self.state.valid_epoch_dealers(epoch_id)? {
|
||||
// if we're in resharing mode, the contract itself will forbid submission of dealings from
|
||||
// parties that were not "initial" dealers, so we don't have to worry about it
|
||||
|
||||
// TODO: introduce caching here in case we crash because those queries are EXPENSIVE
|
||||
let raw_dealings = self
|
||||
.dkg_client
|
||||
.get_dealings(epoch_id, dealer.to_string())
|
||||
.await?;
|
||||
|
||||
if raw_dealings.is_empty() {
|
||||
// we might be in resharing mode and this was not in "initial" set
|
||||
todo!()
|
||||
}
|
||||
|
||||
// no point in verifying any dealings if we don't have all of them
|
||||
if raw_dealings.len() != expected_key_size as usize {
|
||||
todo!("blacklist dealer - incomplete dealing exchange")
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// if this is resharing DKG, get the public key of this dealer from the previous epoch
|
||||
// and use it for dealing(s) verification
|
||||
let old_public_key = if resharing {
|
||||
// 1. check state from previous epoch
|
||||
// 2. if that failed, query the chain
|
||||
None
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Ok(verified_dealings) = self.verified_dealer_dealings(
|
||||
epoch_id,
|
||||
dealer,
|
||||
&epoch_receivers,
|
||||
raw_dealings,
|
||||
old_public_key,
|
||||
) {
|
||||
// if we managed to verify ALL the dealings from this dealer, insert them into the map
|
||||
for (dealing_index, dealing) in verified_dealings {
|
||||
valid_dealings
|
||||
.entry(dealing_index)
|
||||
.or_default()
|
||||
.insert(dealer_index, dealing);
|
||||
}
|
||||
} else {
|
||||
todo!("blacklist dealer")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(valid_dealings)
|
||||
}
|
||||
|
||||
fn derive_partial_keypair(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
epoch_receivers: BTreeMap<NodeIndex, PublicKey>,
|
||||
dealings: BTreeMap<DealingIndex, BTreeMap<NodeIndex, Dealing>>,
|
||||
) -> Result<KeyPairWithEpoch, CoconutError> {
|
||||
debug!("attempting to derive coconut keypair for epoch {epoch_id}");
|
||||
|
||||
let threshold = self.state.threshold(epoch_id)?;
|
||||
let receiver_index = self.state.receiver_index(epoch_id)?;
|
||||
|
||||
// TODO: make sure that each receiver received its dealings
|
||||
|
||||
// SAFETY:
|
||||
// we have ensured before calling this function that the dealings map is non-empty
|
||||
// and has exactly 'expected key size' number of entries;
|
||||
// furthermore each entry has the same number of sub-entries (ALL dealings from given node must be valid)
|
||||
//
|
||||
// SAFETY2:
|
||||
// dealing indexing starts from 0
|
||||
if dealings[&0].len() < threshold as usize {
|
||||
// make sure we have sufficient number of dealings to derive keys for the provided threshold
|
||||
todo!("fail - can't reach threshold")
|
||||
}
|
||||
|
||||
let TEMP_TODO_all_indices = Vec::new();
|
||||
|
||||
let decryption_key = self.state.dkg_keypair().private_key();
|
||||
|
||||
let mut derived_x = None;
|
||||
let mut derived_secrets = Vec::new();
|
||||
|
||||
let total = dealings.len();
|
||||
|
||||
// for every part of the key
|
||||
for (dealing_index, dealings) in dealings {
|
||||
let human_index = dealing_index + 1;
|
||||
debug!("recovering part {human_index}/{total} of the keys");
|
||||
|
||||
debug!("recovering the partial verification keys");
|
||||
let recovered = try_recover_verification_keys(&[], threshold, &epoch_receivers)?;
|
||||
// TODO:
|
||||
|
||||
debug!("decrypting received shares");
|
||||
// for every received share of the key
|
||||
let mut shares = Vec::with_capacity(dealings.len());
|
||||
for (node_index, dealing) in dealings {
|
||||
// attempt to decrypt our portion
|
||||
let share = match decrypt_share(
|
||||
decryption_key,
|
||||
receiver_index,
|
||||
&dealing.ciphertexts,
|
||||
None,
|
||||
) {
|
||||
Ok(share) => share,
|
||||
Err(err) => {
|
||||
error!("failed to decrypt share {human_index}/{total} generated from dealer {node_index}: {err} - can't generate the full key");
|
||||
todo!("do something about it")
|
||||
}
|
||||
};
|
||||
shares.push(share)
|
||||
}
|
||||
|
||||
debug!("combining the shares into part {human_index}/{total} of the epoch key");
|
||||
|
||||
// SAFETY: combining shares can only fail if we have different number shares and indices
|
||||
// however, we returned an error if decryption of any share failed and thus we know those values must match
|
||||
let secret = combine_shares(shares, &TEMP_TODO_all_indices).unwrap();
|
||||
if derived_x.is_none() {
|
||||
derived_x = Some(secret)
|
||||
} else {
|
||||
derived_secrets.push(secret)
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY:
|
||||
// we know we had a non-empty map of dealings and thus, at the very least, we must have derived a single secret
|
||||
// (i.e. the x-element)
|
||||
let sk = SecretKey::create_from_raw(derived_x.unwrap(), derived_secrets);
|
||||
let derived_vk = sk.verification_key(&BANDWIDTH_CREDENTIAL_PARAMS);
|
||||
|
||||
// TODO: make sure derived_vk matches recovered VK
|
||||
|
||||
Ok(KeyPairWithEpoch {
|
||||
keys: CoconutKeyPair::from_keys(sk, derived_vk),
|
||||
issued_for_epoch: epoch_id,
|
||||
})
|
||||
}
|
||||
|
||||
async fn submit_partial_verification_key(
|
||||
&mut self,
|
||||
key: &VerificationKey,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
debug!("submitting derived partial verification key to the contract");
|
||||
let res = self
|
||||
.dkg_client
|
||||
.submit_verification_key_share(key.to_bs58(), resharing)
|
||||
.await?;
|
||||
let hash = res.transaction_hash;
|
||||
let proposal_id = find_attribute(&res.logs, "wasm", DKG_PROPOSAL_ID)
|
||||
.ok_or(CoconutError::ProposalIdError {
|
||||
reason: String::from("proposal id not found"),
|
||||
})?
|
||||
.value
|
||||
.parse::<u64>()
|
||||
.map_err(|_| CoconutError::ProposalIdError {
|
||||
reason: String::from("proposal id could not be parsed to u64"),
|
||||
})?;
|
||||
debug!("Submitted own verification key share, proposal id {proposal_id} is attached to it. tx hash: {hash}");
|
||||
|
||||
// TODO: state.set_proposal_id(proposal_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Third step of the DKG process during which the nym api will generate its Coconut keypair
|
||||
/// with the [Dealing] received from other dealers. It will then submit its verification key
|
||||
/// to the system so that it could be validated by other participants.
|
||||
pub(crate) async fn verification_key_submission(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
let key_generation_state = self.state.key_derivation_state(epoch_id)?;
|
||||
|
||||
// check if we have already generated the new keys and submitted verification proposal
|
||||
if key_generation_state.completed() {
|
||||
// TODO: ASSERT: we have a VALID key
|
||||
|
||||
// the only way this could be a false positive is if the chain forked and blocks got reverted,
|
||||
// but I don't think we have to worry about that
|
||||
debug!(
|
||||
"we have already generated key for this epoch and submitted validation proposal"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// TODO: make sure we have keys for that lol
|
||||
// if we have keys and we're still here it means validation failed blah blah
|
||||
|
||||
// FAILURE CASE:
|
||||
// check if we have already sent the verification key transaction, but it timed out or got stuck in the mempool and
|
||||
// eventually got executed without us knowing about it, because it's illegal to recommit the key
|
||||
let maybe_share = self
|
||||
.dkg_client
|
||||
.get_verification_key_share_status(epoch_id)
|
||||
.await?;
|
||||
if maybe_share.is_some() {
|
||||
todo!("finalize, we're done")
|
||||
}
|
||||
|
||||
let epoch_receivers = BTreeMap::new();
|
||||
|
||||
let dealings = self
|
||||
.get_valid_dealings(&epoch_receivers, epoch_id, resharing)
|
||||
.await?;
|
||||
if dealings.is_empty() {
|
||||
todo!("not a failure per se but something along the lines: can't continue, won't continue")
|
||||
}
|
||||
|
||||
let dbg_dealers = dealings[&0].keys().collect::<Vec<_>>();
|
||||
debug!("going to use dealings generated by {dbg_dealers:?}");
|
||||
|
||||
let coconut_keypair = self.derive_partial_keypair(epoch_id, epoch_receivers, dealings)?;
|
||||
|
||||
if let Err(err) = persist_coconut_keypair(&coconut_keypair, &self.coconut_key_path) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
self.submit_partial_verification_key(coconut_keypair.keys.verification_key(), resharing)
|
||||
.await?;
|
||||
|
||||
self.state.set_coconut_keypair(coconut_keypair).await;
|
||||
self.state.key_derivation_state_mut(epoch_id)?.completed = true;
|
||||
|
||||
info!("DKG: Finished key derivation");
|
||||
Ok(())
|
||||
|
||||
// TODO: set completed etc.
|
||||
|
||||
// let dealings = self.dkg_client.get_dealings();
|
||||
|
||||
// if state.coconut_keypair_is_some().await {
|
||||
// debug!("Coconut keypair was set previously, nothing to do");
|
||||
// return Ok(());
|
||||
// }
|
||||
//
|
||||
//
|
||||
// let threshold = state.threshold()?;
|
||||
// let dealings_maps =
|
||||
// deterministic_filter_dealers(dkg_client, state, epoch_id, threshold, resharing).await?;
|
||||
// debug!(
|
||||
// "Filtered dealers to {:?}",
|
||||
// dealings_maps[0].keys().collect::<Vec<_>>()
|
||||
// );
|
||||
// let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?;
|
||||
// debug!("Derived own coconut keypair");
|
||||
// let vk_share = coconut_keypair.verification_key().to_bs58();
|
||||
// nym_pemstore::store_keypair(&coconut_keypair, keypair_path)?;
|
||||
// let res = dkg_client
|
||||
// .submit_verification_key_share(vk_share, resharing)
|
||||
// .await?;
|
||||
// let proposal_id = find_attribute(&res.logs, "wasm", DKG_PROPOSAL_ID)
|
||||
// .ok_or(CoconutError::ProposalIdError {
|
||||
// reason: String::from("proposal id not found"),
|
||||
// })?
|
||||
// .value
|
||||
// .parse::<u64>()
|
||||
// .map_err(|_| CoconutError::ProposalIdError {
|
||||
// reason: String::from("proposal id could not be parsed to u64"),
|
||||
// })?;
|
||||
// debug!(
|
||||
// "Submitted own verification key share, proposal id {} is attached to it",
|
||||
// proposal_id
|
||||
// );
|
||||
// state.set_proposal_id(proposal_id);
|
||||
// state.set_coconut_keypair(epoch_id, coconut_keypair).await;
|
||||
// info!("DKG: Submitted own verification key");
|
||||
//
|
||||
// Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_proposal(proposal: &ProposalResponse) -> Option<(Addr, u64)> {
|
||||
@@ -12,6 +12,6 @@ pub(crate) mod client;
|
||||
pub(crate) mod complaints;
|
||||
pub(crate) mod controller;
|
||||
pub(crate) mod dealing;
|
||||
pub(crate) mod key_derivation;
|
||||
pub(crate) mod public_key;
|
||||
pub(crate) mod state;
|
||||
pub(crate) mod verification_key;
|
||||
|
||||
@@ -8,6 +8,18 @@ use nym_coconut_dkg_common::types::EpochId;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
/// First step of the DKG process during which the nym api will register for the key exchange
|
||||
/// by submitting its:
|
||||
/// - BTE public key (alongside the proof of discrete log)
|
||||
/// - ed25519 public key
|
||||
/// - announce address to be used by clients for obtaining credentials
|
||||
/// Upon successful registration, the node will receive a unique "NodeIndex"
|
||||
/// which is the x-coordinate of the to be derived keys.
|
||||
///
|
||||
/// During this step any prior coconut keys will be invalidated, i.e. keys from the previous epoch
|
||||
/// won't be used for issuing new credentials.
|
||||
///
|
||||
/// Furthermore, if the node experienced any failures during this step, a recovery will be attempted.
|
||||
pub(crate) async fn public_key_submission(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
|
||||
@@ -13,6 +13,8 @@ pub struct DealingExchangeState {
|
||||
#[serde(with = "generated_dealings")]
|
||||
pub(crate) generated_dealings: HashMap<DealingIndex, Dealing>,
|
||||
|
||||
pub(crate) receiver_index: Option<usize>,
|
||||
|
||||
pub(crate) completed: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_dkg::Threshold;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct KeyDerivationState {
|
||||
pub(crate) expected_threshold: Option<Threshold>, // pub(crate) completed: bool,
|
||||
|
||||
pub(crate) completed: bool,
|
||||
// because we couldn't decrypt shares or there were no shares, etc
|
||||
// failed:
|
||||
}
|
||||
|
||||
impl KeyDerivationState {
|
||||
pub fn completed(&self) -> bool {
|
||||
self.completed
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
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::key_derivation::KeyDerivationState;
|
||||
use crate::coconut::dkg::state::registration::RegistrationState;
|
||||
use crate::coconut::error::CoconutError;
|
||||
use crate::coconut::keys::{KeyPair as CoconutKeyPair, KeyPairWithEpoch};
|
||||
@@ -27,6 +28,7 @@ use tokio::sync::RwLockReadGuard;
|
||||
use url::Url;
|
||||
|
||||
mod dealing_exchange;
|
||||
mod key_derivation;
|
||||
mod registration;
|
||||
mod serde_helpers;
|
||||
|
||||
@@ -71,8 +73,8 @@ impl DkgParticipant {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn unwrap_key(&self) -> PublicKeyWithProof {
|
||||
if let ParticipantState::VerifiedKey(key) = &self.state {
|
||||
return key.clone()
|
||||
}
|
||||
return key.clone();
|
||||
}
|
||||
panic!("no key")
|
||||
}
|
||||
}
|
||||
@@ -238,6 +240,8 @@ pub(crate) struct DkgState {
|
||||
pub(crate) dealers: BTreeMap<NodeIndex, DkgParticipant>,
|
||||
|
||||
pub(crate) dealing_exchange: DealingExchangeState,
|
||||
|
||||
pub(crate) key_generation: KeyDerivationState,
|
||||
}
|
||||
|
||||
impl DkgState {
|
||||
@@ -348,6 +352,25 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain the list of dealers for the provided epoch that have submitted valid public keys.
|
||||
pub fn valid_epoch_dealers(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Vec<(Addr, NodeIndex)>, CoconutError> {
|
||||
Ok(self
|
||||
.dkg_state(epoch_id)?
|
||||
.dealers
|
||||
.values()
|
||||
.filter_map(|d| {
|
||||
if d.state.is_valid() {
|
||||
Some((d.address.clone(), d.assigned_index))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn dkg_state(&self, epoch_id: EpochId) -> Result<&DkgState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get(&epoch_id)
|
||||
@@ -400,6 +423,44 @@ impl State {
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn key_derivation_state(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&KeyDerivationState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get(&epoch_id)
|
||||
.map(|state| &state.key_generation)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn key_derivation_state_mut(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&mut KeyDerivationState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get_mut(&epoch_id)
|
||||
.map(|state| &mut state.key_generation)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn threshold(&self, epoch_id: EpochId) -> Result<Threshold, CoconutError> {
|
||||
self.key_derivation_state(epoch_id)?
|
||||
.expected_threshold
|
||||
.ok_or(CoconutError::UnavailableThreshold { epoch_id })
|
||||
}
|
||||
|
||||
// pub fn assigned_index(&self, epoch_id: EpochId) -> Result<NodeIndex, CoconutError> {
|
||||
// self.registration_state(epoch_id)?
|
||||
// .assigned_index
|
||||
// .ok_or(CoconutError::UnavailableAssignedIndex { epoch_id })
|
||||
// }
|
||||
|
||||
pub fn receiver_index(&self, epoch_id: EpochId) -> Result<usize, CoconutError> {
|
||||
self.dealing_exchange_state(epoch_id)?
|
||||
.receiver_index
|
||||
.ok_or(CoconutError::UnavailableReceiverIndex { epoch_id })
|
||||
}
|
||||
|
||||
pub fn persistent_state_path(&self) -> &Path {
|
||||
self.persistent_state_path.as_path()
|
||||
}
|
||||
@@ -456,10 +517,6 @@ impl State {
|
||||
self.node_index
|
||||
}
|
||||
|
||||
pub fn receiver_index(&self) -> Option<usize> {
|
||||
self.receiver_index
|
||||
}
|
||||
|
||||
pub fn current_dealers_by_addr(&self) -> BTreeMap<Addr, NodeIndex> {
|
||||
self.dealers
|
||||
.iter()
|
||||
@@ -508,12 +565,8 @@ impl State {
|
||||
self.recovered_vks = recovered_vks;
|
||||
}
|
||||
|
||||
pub async fn set_coconut_keypair(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
coconut_keypair: nym_coconut_interface::KeyPair,
|
||||
) {
|
||||
self.coconut_keypair.set(epoch_id, coconut_keypair).await
|
||||
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>) {
|
||||
|
||||
@@ -141,6 +141,17 @@ pub enum CoconutError {
|
||||
#[error("the internal dkg state for epoch {epoch_id} is missing - we might have joined mid exchange")]
|
||||
MissingDkgState { epoch_id: EpochId },
|
||||
|
||||
#[error(
|
||||
"the node index value for {epoch_id} is not available - are you sure we are a dealer?"
|
||||
)]
|
||||
UnavailableAssignedIndex { epoch_id: EpochId },
|
||||
|
||||
#[error("the receiver index value for {epoch_id} is not available - are you sure we are a receiver?")]
|
||||
UnavailableReceiverIndex { epoch_id: EpochId },
|
||||
|
||||
#[error("the threshold value for {epoch_id} is not available")]
|
||||
UnavailableThreshold { epoch_id: EpochId },
|
||||
|
||||
#[error("insufficient number of dealings provided to derive the key")]
|
||||
InsufficientDealings {
|
||||
// TODO: details
|
||||
|
||||
@@ -55,7 +55,7 @@ impl KeyPair {
|
||||
// self.keys.read().await
|
||||
}
|
||||
|
||||
pub async fn set(&self, epoch_id: EpochId, keypair: nym_coconut_interface::KeyPair) {
|
||||
pub async fn set(&self, keypair: KeyPairWithEpoch) {
|
||||
todo!()
|
||||
// let mut w_lock = self.keys.write().await;
|
||||
// *w_lock = Some(keypair);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut::error::CoconutError;
|
||||
@@ -434,10 +434,18 @@ impl crate::coconut::client::Client for Client {
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_verification_key_share(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
dealer: String,
|
||||
) -> Result<Option<ContractVKShare>, CoconutError> {
|
||||
Ok(nyxd_query!(self, get_vk_share(epoch_id, dealer).await?).share)
|
||||
}
|
||||
|
||||
async fn get_verification_key_shares(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> crate::coconut::error::Result<Vec<ContractVKShare>> {
|
||||
) -> Result<Vec<ContractVKShare>, CoconutError> {
|
||||
Ok(nyxd_query!(
|
||||
self,
|
||||
get_all_verification_key_shares(epoch_id).await?
|
||||
|
||||
Reference in New Issue
Block a user