happy path for key derivation

This commit is contained in:
Jędrzej Stuczyński
2024-01-22 14:07:26 +00:00
parent 7081076842
commit faffdf9b2f
17 changed files with 556 additions and 95 deletions
@@ -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>,
+1 -1
View File
@@ -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::*;
+6
View File
@@ -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<()>;
+13 -1
View File
@@ -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,
+9 -16
View File
@@ -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(),
+27 -11
View File
@@ -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})");
@@ -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)> {
+1 -1
View File
@@ -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;
+12
View File
@@ -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
}
}
+65 -12
View File
@@ -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>) {
+11
View File
@@ -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
+1 -1
View File
@@ -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);
+10 -2
View File
@@ -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?