[wip] dealing exchange
This commit is contained in:
@@ -23,6 +23,12 @@ impl Deref for ContractSafeBytes {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for ContractSafeBytes {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
ContractSafeBytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContractSafeBytes {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if !self.0.is_empty() {
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod dealing;
|
||||
pub(crate) mod share;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub use bls12_381::Scalar;
|
||||
pub use dealing::*;
|
||||
pub use share::*;
|
||||
|
||||
|
||||
@@ -99,7 +99,9 @@ impl SecretKey {
|
||||
Self { x, ys }
|
||||
}
|
||||
|
||||
pub fn into_raw(&self) -> (Scalar, Vec<Scalar>) {
|
||||
/// Extract the Scalar copy of the underlying secrets.
|
||||
/// The caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized
|
||||
pub fn hazmat_to_raw(&self) -> (Scalar, Vec<Scalar>) {
|
||||
(self.x, self.ys.clone())
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ type Dealer<'a> = &'a Addr;
|
||||
|
||||
pub(crate) struct StoredDealing;
|
||||
|
||||
// part of `StoredDealing` to make existence lookup cheaper
|
||||
pub(crate) struct UNIMPLEMENTED_DealingLookup;
|
||||
|
||||
impl StoredDealing {
|
||||
const NAMESPACE: &'static [u8] = b"dealing";
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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 {
|
||||
@@ -11,3 +12,23 @@ pub(crate) enum ComplaintReason {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,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::{
|
||||
verification_key_finalization, verification_key_validation,
|
||||
};
|
||||
use crate::coconut::dkg::{
|
||||
dealing::dealing_exchange, verification_key::verification_key_submission,
|
||||
};
|
||||
use crate::coconut::keys::KeyPair as CoconutKeyPair;
|
||||
use crate::nyxd;
|
||||
use crate::support::config;
|
||||
@@ -32,7 +30,7 @@ pub(crate) struct DkgController<R = OsRng> {
|
||||
pub(crate) dkg_client: DkgClient,
|
||||
pub(crate) coconut_key_path: PathBuf,
|
||||
pub(crate) state: State,
|
||||
rng: R,
|
||||
pub(super) rng: R,
|
||||
polling_rate: Duration,
|
||||
}
|
||||
|
||||
@@ -137,16 +135,15 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
.map_err(|source| DkgError::PublicKeySubmissionFailure { source })
|
||||
}
|
||||
|
||||
async fn handle_dealing_exchange(&mut self, resharing: bool) -> Result<(), DkgError> {
|
||||
async fn handle_dealing_exchange(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), DkgError> {
|
||||
debug!("DKG: dealing exchange (resharing: {resharing})");
|
||||
dealing_exchange(
|
||||
&self.dkg_client,
|
||||
&mut self.state,
|
||||
self.rng.clone(),
|
||||
resharing,
|
||||
)
|
||||
.await
|
||||
.map_err(|source| DkgError::DealingExchangeFailure { source })
|
||||
self.dealing_exchange(epoch_id, resharing)
|
||||
.await
|
||||
.map_err(|source| DkgError::DealingExchangeFailure { source })
|
||||
}
|
||||
|
||||
async fn handle_verification_key_submission(
|
||||
@@ -221,7 +218,8 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
.await?
|
||||
}
|
||||
EpochState::DealingExchange { resharing } => {
|
||||
self.handle_dealing_exchange(resharing).await?
|
||||
self.handle_dealing_exchange(epoch.epoch_id, resharing)
|
||||
.await?
|
||||
}
|
||||
EpochState::VerificationKeySubmission { resharing } => {
|
||||
self.handle_verification_key_submission(epoch.epoch_id, resharing)
|
||||
|
||||
+305
-104
@@ -3,130 +3,331 @@
|
||||
|
||||
use crate::coconut::dkg;
|
||||
use crate::coconut::dkg::client::DkgClient;
|
||||
use crate::coconut::dkg::state::{ConsistentState, State};
|
||||
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::error::CoconutError;
|
||||
use crate::coconut::keys::KeyPairWithEpoch;
|
||||
use log::debug;
|
||||
use nym_coconut_dkg_common::types::{ContractDealing, PartialContractDealing};
|
||||
use nym_dkg::Dealing;
|
||||
use rand::RngCore;
|
||||
use std::collections::VecDeque;
|
||||
use nym_coconut_dkg_common::types::{
|
||||
ContractDealing, DealingIndex, EpochId, PartialContractDealing,
|
||||
};
|
||||
use nym_dkg::bte::{PublicKey, PublicKeyWithProof};
|
||||
use nym_dkg::{Dealing, NodeIndex, Scalar, Threshold};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use rocket::form::validate::Len;
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub(crate) async fn dealing_exchange(
|
||||
dkg_client: &DkgClient,
|
||||
state: &mut State,
|
||||
rng: impl RngCore + Clone,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
if state.receiver_index().is_some() {
|
||||
debug!("Receiver index was set previously, nothing to do");
|
||||
return Ok(());
|
||||
enum DealingGeneration {
|
||||
Fresh { number: u32 },
|
||||
Resharing { prior_secrets: Vec<Scalar> },
|
||||
}
|
||||
|
||||
impl Debug for DealingGeneration {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
DealingGeneration::Fresh { number } => f
|
||||
.debug_struct("DealingGeneration::Fresh")
|
||||
.field("number", number)
|
||||
.finish(),
|
||||
DealingGeneration::Resharing { prior_secrets } => f
|
||||
.debug_struct("DealingGeneration::Resharing")
|
||||
.field("number", &prior_secrets.len())
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
async fn generate_dealings(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
spec: DealingGeneration,
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, CoconutError> {
|
||||
let threshold = self.dkg_client.get_current_epoch_threshold().await?.ok_or(
|
||||
CoconutError::UnrecoverableState {
|
||||
reason: String::from("Threshold should have been set"),
|
||||
},
|
||||
)?;
|
||||
|
||||
let dealer_index = self
|
||||
.state
|
||||
.registration_state(epoch_id)?
|
||||
.assigned_index
|
||||
.ok_or(CoconutError::UnrecoverableState {
|
||||
reason: String::from("Node index should have been set"),
|
||||
})?;
|
||||
|
||||
// in our case every dealer is also a receiver
|
||||
|
||||
// ASSUMPTION: all dealers see the same contract data, i.e. if one fails to decode and verify the receiver's key,
|
||||
// all of them will
|
||||
let filtered_receivers: BTreeMap<_, _> = self
|
||||
.state
|
||||
.dkg_state(epoch_id)?
|
||||
.dealers
|
||||
.iter()
|
||||
.filter_map(|(index, dealer)| match &dealer.state {
|
||||
ParticipantState::Invalid(_) => None,
|
||||
ParticipantState::VerifiedKey(key) => Some((*index, *key.public_key())),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let dbg_receivers = filtered_receivers.keys().collect::<Vec<_>>();
|
||||
debug!("generating dealings with threshold {threshold} for receivers: {dbg_receivers:?} with the following spec: {spec:?}. Our index is {dealer_index}");
|
||||
|
||||
let mut dealings = HashMap::new();
|
||||
match spec {
|
||||
DealingGeneration::Fresh { number } => {
|
||||
for i in 0..number {
|
||||
let dealing = Dealing::create(
|
||||
&mut self.rng,
|
||||
dkg::params(),
|
||||
dealer_index,
|
||||
threshold,
|
||||
&filtered_receivers,
|
||||
None,
|
||||
);
|
||||
dealings.insert(i as DealingIndex, dealing.0);
|
||||
}
|
||||
}
|
||||
DealingGeneration::Resharing { prior_secrets } => {
|
||||
for (i, secret) in prior_secrets.into_iter().enumerate() {
|
||||
let dealing = Dealing::create(
|
||||
&mut self.rng,
|
||||
dkg::params(),
|
||||
dealer_index,
|
||||
threshold,
|
||||
&filtered_receivers,
|
||||
Some(secret),
|
||||
);
|
||||
dealings.insert(i as DealingIndex, dealing.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the state with the dealing information
|
||||
self.state
|
||||
.dealing_exchange_state_mut(epoch_id)?
|
||||
.generated_dealings = dealings.clone();
|
||||
|
||||
Ok(dealings)
|
||||
}
|
||||
|
||||
let contract_state = dkg_client.get_contract_state().await?;
|
||||
let expected_key_size = contract_state.key_size;
|
||||
async fn generate_fresh_dealings(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
number: u32,
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, CoconutError> {
|
||||
self.generate_dealings(epoch_id, DealingGeneration::Fresh { number })
|
||||
.await
|
||||
}
|
||||
|
||||
let epoch_id = dkg_client.get_current_epoch().await?.epoch_id;
|
||||
async fn generate_resharing_dealings(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
prior_secrets: Vec<Scalar>,
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, CoconutError> {
|
||||
self.generate_dealings(epoch_id, DealingGeneration::Resharing { prior_secrets })
|
||||
.await
|
||||
}
|
||||
|
||||
let dealers = dkg_client.get_current_dealers().await?;
|
||||
let threshold = dkg_client.get_current_epoch_threshold().await?;
|
||||
let initial_dealers = dkg_client
|
||||
.get_initial_dealers()
|
||||
.await?
|
||||
.map(|d| d.initial_dealers)
|
||||
.unwrap_or_default();
|
||||
let own_address = dkg_client.get_address().await.as_ref().to_string();
|
||||
state.set_dealers(dealers);
|
||||
state.set_threshold(threshold);
|
||||
let receivers = state.current_dealers_by_idx();
|
||||
let dealer_index = state.node_index_value()?;
|
||||
let receiver_index = receivers
|
||||
.keys()
|
||||
.position(|node_index| *node_index == dealer_index);
|
||||
async fn resubmit_pregenerated_dealings(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
let dealing_state = self.state.dealing_exchange_state(epoch_id)?;
|
||||
|
||||
let prior_resharing_secrets = if let Some(mut keypair) = state.take_coconut_keypair().await {
|
||||
// Double check that we are in resharing mode
|
||||
if resharing {
|
||||
todo!()
|
||||
// let sk = keypair.secret_key();
|
||||
// if sk.size() + 1 != expected_key_size as usize {
|
||||
// return Err(CoconutError::CorruptedCoconutKeyPair);
|
||||
// }
|
||||
//
|
||||
// let (x, mut scalars) = sk.into_raw();
|
||||
//
|
||||
// // We can now erase the keypair from memory
|
||||
// debug!("Removing coconut keypair from memory");
|
||||
// keypair.zeroize();
|
||||
// scalars.push(x);
|
||||
// scalars
|
||||
} else {
|
||||
log::warn!("Coconut key hasn't been reset in memory. The state might be corrupt");
|
||||
vec![]
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let mut prior_resharing_secrets = VecDeque::from(prior_resharing_secrets);
|
||||
if !resharing || initial_dealers.iter().any(|d| *d == own_address) {
|
||||
let params = dkg::params();
|
||||
for dealing_index in 0..expected_key_size {
|
||||
debug!(
|
||||
"dealing {dealing_index} ({} out of {expected_key_size})",
|
||||
dealing_index + 1
|
||||
);
|
||||
for (dealing_index, dealing) in &dealing_state.generated_dealings {
|
||||
// check which dealing is actually present on the chain (some might have gotten stuck in the mempool for quite a while)
|
||||
let dealing_submitted = self
|
||||
.dkg_client
|
||||
.get_dealing_status(epoch_id, *dealing_index)
|
||||
.await?;
|
||||
|
||||
// see if we have already submitted this one (we might have crashed)
|
||||
if dkg_client
|
||||
.get_dealing_status(epoch_id, dealing_index)
|
||||
.await?
|
||||
{
|
||||
warn!("we have already submitted dealing {dealing_index} before - we probably crashed!");
|
||||
if dealing_submitted {
|
||||
warn!("we have already submitted dealing {dealing_index} before - we probably crashed or the chain timed out!");
|
||||
continue;
|
||||
}
|
||||
|
||||
// see if we have already generated, but not submitted this one before (we might have crashed or validator might have had a problem)
|
||||
let contract_dealing = if let Some(prior_dealing) =
|
||||
state.get_dealing(epoch_id, dealing_index)
|
||||
{
|
||||
warn!("we have already generated dealing {dealing_index} before, but failed to submit it");
|
||||
PartialContractDealing::new(dealing_index, ContractDealing::from(prior_dealing))
|
||||
} else {
|
||||
// generate fresh dealing
|
||||
let (dealing, _) = Dealing::create(
|
||||
rng.clone(),
|
||||
params,
|
||||
dealer_index,
|
||||
state.threshold()?,
|
||||
&receivers,
|
||||
prior_resharing_secrets.pop_front(),
|
||||
);
|
||||
|
||||
let contract_dealing =
|
||||
PartialContractDealing::new(dealing_index, ContractDealing::from(&dealing));
|
||||
state.store_dealing(epoch_id, dealing_index, dealing);
|
||||
|
||||
contract_dealing
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Submitting dealing for indexes {:?} with resharing: {}",
|
||||
receivers.keys().collect::<Vec<_>>(),
|
||||
prior_resharing_secrets.front().is_some()
|
||||
warn!(
|
||||
"we have already generated dealing {dealing_index} before, but failed to submit it"
|
||||
);
|
||||
let contract_dealing =
|
||||
PartialContractDealing::new(*dealing_index, ContractDealing::from(dealing));
|
||||
|
||||
dkg_client
|
||||
self.dkg_client
|
||||
.submit_dealing(contract_dealing, resharing)
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
debug!("Nothing to do, waiting for initial dealers to submit dealings");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
info!("DKG: Finished dealing exchange");
|
||||
state.set_receiver_index(receiver_index);
|
||||
/// 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> {
|
||||
let Some(initial_data) = self.dkg_client.get_initial_dealers().await? else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
Ok(())
|
||||
let address = self.dkg_client.get_address().await;
|
||||
Ok(initial_data
|
||||
.initial_dealers
|
||||
.iter()
|
||||
.any(|d| d.as_str() == address.as_ref()))
|
||||
}
|
||||
|
||||
async fn handle_resharing_with_prior_key(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
expected_key_size: u32,
|
||||
old_keypair: KeyPairWithEpoch,
|
||||
) -> Result<(), CoconutError> {
|
||||
// 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)
|
||||
warn!("we we have an existing coconut keypair, but we're not allowed to participate in resharing");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// EDGE CASE:
|
||||
// make sure our keypair is from strictly the previous epoch
|
||||
// because our node might have been offline for multiple epochs and while we do have a coconut keypair,
|
||||
// it could be outdated and we can't use it for resharing
|
||||
let previous = epoch_id.saturating_sub(1);
|
||||
if old_keypair.issued_for_epoch != previous {
|
||||
warn!("our existing coconut keypair has been generated for an distant epoch ({} vs expected {previous} for resharing)", old_keypair.issued_for_epoch);
|
||||
// don't participate in resharing
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// EDGE CASE:
|
||||
// we have changed the key size (because we wanted to add new attribute to credentials)
|
||||
// in this instance we can't reuse our key and have to generate brand new dealings
|
||||
if expected_key_size != 1 + old_keypair.keys.secret_key().size() as u32 {
|
||||
warn!("our existing coconut keypair has different size than the currently expected value ({expected_key_size} vs {})", old_keypair.keys.secret_key().size() as u32);
|
||||
self.generate_fresh_dealings(epoch_id, expected_key_size)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// generate resharing dealings
|
||||
let prior_secrets = old_keypair.hazmat_into_secrets();
|
||||
// safety:
|
||||
// the prior secrets will be immediately converted into `Polynomial` with the specified coefficient
|
||||
// that does implement `ZeroizeOnDrop`
|
||||
self.generate_resharing_dealings(epoch_id, prior_secrets)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn dealing_exchange(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
let dealing_state = self.state.dealing_exchange_state(epoch_id)?;
|
||||
|
||||
// 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,
|
||||
// but I don't think we have to worry about that
|
||||
debug!("we have already submitted all the dealings for this epoch");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 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() {
|
||||
debug!("we have already generated the dealings for this epoch");
|
||||
self.resubmit_pregenerated_dealings(epoch_id, resharing)
|
||||
.await?;
|
||||
|
||||
// if we managed to resubmit the dealings (i.e. we didn't return an error),
|
||||
// it means the state is complete now.
|
||||
self.state.dealing_exchange_state_mut(epoch_id)?.completed = true;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// we don't have any prior information - grab, parse and cache it since we will need it in next steps
|
||||
// and it's not going to change during the epoch
|
||||
let dealers = self.dkg_client.get_current_dealers().await?;
|
||||
self.state.dkg_state_mut(epoch_id)?.set_dealers(dealers);
|
||||
|
||||
// get the expected key size which will determine the number of dealings we need to construct
|
||||
let contract_state = self.dkg_client.get_contract_state().await?;
|
||||
let expected_key_size = contract_state.key_size;
|
||||
|
||||
// there are few cases to cover here based on the resharing status and presence of coconut keypair:
|
||||
// - resharing + we have a key => we should use the prior secrets for the resharing dealings generation
|
||||
// - resharing + we don't have a key => we are a new party that joined the existing setup and we have to wait for others to give us the shares
|
||||
// - no resharing + we have a key => whole DKG has been restarted (probably enough new parties joined / old parties left) to trigger it
|
||||
// - no resharing + we don't have a key => either as above (but we're a new party) or it's the very first instance of the DKG
|
||||
if let Some(old_keypair) = self.state.take_coconut_keypair().await {
|
||||
let keypair_epoch = old_keypair.issued_for_epoch;
|
||||
|
||||
if resharing {
|
||||
debug!("resharing + prior key");
|
||||
self.handle_resharing_with_prior_key(epoch_id, expected_key_size, old_keypair)
|
||||
.await?;
|
||||
} else {
|
||||
debug!("no resharing + prior key");
|
||||
self.generate_fresh_dealings(epoch_id, expected_key_size)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// EDGE CASE:
|
||||
// make sure to persist the state after possibly generating the resharing dealings as we're going to be archiving the keypair
|
||||
// (so we won't be able to create resharing dealings again if we crashed since we won't be able to load the keys)
|
||||
self.state.persist()?;
|
||||
// archive the keypair
|
||||
if let Err(source) = archive_coconut_keypair(&self.coconut_key_path, keypair_epoch) {
|
||||
return Err(CoconutError::KeyArchiveFailure {
|
||||
epoch_id,
|
||||
path: self.coconut_key_path.clone(),
|
||||
source,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// sure, the if statements could be collapsed, but i prefer to explicitly repeat the block for readability
|
||||
if resharing {
|
||||
debug!("resharing + no prior key -> nothing to do");
|
||||
if self.can_reshare().await? {
|
||||
warn!("this dealer was expected to participate in resharing but it doesn't have any prior keys to use");
|
||||
}
|
||||
} else {
|
||||
debug!("no resharing + no prior key");
|
||||
self.generate_fresh_dealings(epoch_id, expected_key_size)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
for (i, (dealing_index, dealing)) in dealings.iter().enumerate() {
|
||||
let i = i + 1;
|
||||
debug!("submitting dealing index {dealing_index} ({i}/{total})");
|
||||
|
||||
let contract_dealing =
|
||||
PartialContractDealing::new(*dealing_index, ContractDealing::from(dealing));
|
||||
|
||||
self.dkg_client
|
||||
.submit_dealing(contract_dealing, resharing)
|
||||
.await?;
|
||||
}
|
||||
|
||||
self.state.dealing_exchange_state_mut(epoch_id)?.completed = true;
|
||||
info!("DKG: Finished dealing exchange");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -8,14 +8,14 @@ use log::debug;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
pub(crate) async fn public_key_submission(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
resharing: bool,
|
||||
) -> Result<(), CoconutError> {
|
||||
self.state.init_dkg_state(epoch_id);
|
||||
let registration_state = self.state.registration_state(epoch_id);
|
||||
self.state.maybe_init_dkg_state(epoch_id);
|
||||
let registration_state = self.state.registration_state(epoch_id)?;
|
||||
|
||||
// check if we have already submitted the key
|
||||
if registration_state.completed() {
|
||||
@@ -25,20 +25,20 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// if we have coconut keys available, it means we have already completed the DKG before (in previous epoch)
|
||||
// in which case, archive and reset those keys
|
||||
if let Some(old_keypair) = self.state.take_coconut_keypair().await {
|
||||
debug!("resetting and archiving old coconut keypair");
|
||||
if let Err(source) =
|
||||
archive_coconut_keypair(&self.coconut_key_path, old_keypair.issued_for_epoch)
|
||||
{
|
||||
return Err(CoconutError::KeyArchiveFailure {
|
||||
epoch_id,
|
||||
path: self.coconut_key_path.clone(),
|
||||
source,
|
||||
});
|
||||
}
|
||||
}
|
||||
// // if we have coconut keys available, it means we have already completed the DKG before (in previous epoch)
|
||||
// // in which case, archive and reset those keys
|
||||
// if let Some(old_keypair) = self.state.take_coconut_keypair().await {
|
||||
// debug!("resetting and archiving old coconut keypair");
|
||||
// if let Err(source) =
|
||||
// archive_coconut_keypair(&self.coconut_key_path, old_keypair.issued_for_epoch)
|
||||
// {
|
||||
// return Err(CoconutError::KeyArchiveFailure {
|
||||
// epoch_id,
|
||||
// path: self.coconut_key_path.clone(),
|
||||
// source,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// FAILURE CASE:
|
||||
// check if we have already sent the registration transaction, but it timed out or got stuck in the mempool and
|
||||
@@ -48,7 +48,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
if dealer_details.dealer_type.is_current() {
|
||||
if let Some(details) = dealer_details.details {
|
||||
// the tx did actually go through
|
||||
self.state.registration_state_mut(epoch_id).assigned_index =
|
||||
self.state.registration_state_mut(epoch_id)?.assigned_index =
|
||||
Some(details.assigned_index);
|
||||
info!("DKG: recovered node index: {}", details.assigned_index);
|
||||
return Ok(());
|
||||
@@ -64,7 +64,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
.dkg_client
|
||||
.register_dealer(bte_key, identity_key, announce_address, resharing)
|
||||
.await?;
|
||||
self.state.registration_state_mut(epoch_id).assigned_index = Some(assigned_index);
|
||||
self.state.registration_state_mut(epoch_id)?.assigned_index = Some(assigned_index);
|
||||
info!("DKG: Using node index {assigned_index}");
|
||||
|
||||
Ok(())
|
||||
@@ -90,8 +90,7 @@ pub(crate) mod tests {
|
||||
const TEST_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // expensive test
|
||||
async fn submit_public_key() {
|
||||
async fn submit_public_key() -> anyhow::Result<()> {
|
||||
let dkg_client = DkgClient::new(DummyClient::new(
|
||||
AccountId::from_str(TEST_VALIDATOR_ADDRESS).unwrap(),
|
||||
));
|
||||
@@ -110,26 +109,21 @@ pub(crate) mod tests {
|
||||
assert!(controller
|
||||
.dkg_client
|
||||
.get_self_registered_dealer_details()
|
||||
.await
|
||||
.unwrap()
|
||||
.await?
|
||||
.details
|
||||
.is_none());
|
||||
controller
|
||||
.public_key_submission(epoch, false)
|
||||
.await
|
||||
.unwrap();
|
||||
controller.public_key_submission(epoch, false).await?;
|
||||
let client_idx = controller
|
||||
.dkg_client
|
||||
.get_self_registered_dealer_details()
|
||||
.await
|
||||
.unwrap()
|
||||
.await?
|
||||
.details
|
||||
.unwrap()
|
||||
.assigned_index;
|
||||
assert_eq!(
|
||||
controller
|
||||
.state
|
||||
.registration_state(epoch)
|
||||
.registration_state(epoch)?
|
||||
.assigned_index
|
||||
.unwrap(),
|
||||
client_idx
|
||||
@@ -138,19 +132,18 @@ pub(crate) mod tests {
|
||||
// keeps the same index from chain, not calling register_dealer again
|
||||
controller
|
||||
.state
|
||||
.registration_state_mut(epoch)
|
||||
.registration_state_mut(epoch)?
|
||||
.assigned_index = None;
|
||||
controller
|
||||
.public_key_submission(epoch, false)
|
||||
.await
|
||||
.unwrap();
|
||||
controller.public_key_submission(epoch, false).await?;
|
||||
assert_eq!(
|
||||
controller
|
||||
.state
|
||||
.registration_state(epoch)
|
||||
.registration_state(epoch)?
|
||||
.assigned_index
|
||||
.unwrap(),
|
||||
client_idx
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::serde_helpers::generated_dealings;
|
||||
use nym_coconut_dkg_common::types::DealingIndex;
|
||||
use nym_dkg::{Dealing, NodeIndex};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct DealingExchangeState {
|
||||
// pub(crate) assigned_index: Option<NodeIndex>,
|
||||
#[serde(with = "generated_dealings")]
|
||||
pub(crate) generated_dealings: HashMap<DealingIndex, Dealing>,
|
||||
|
||||
pub(crate) completed: bool,
|
||||
}
|
||||
|
||||
impl DealingExchangeState {
|
||||
/// Specifies whether this dealer has already shared dealings in this DKG epoch
|
||||
pub fn completed(&self) -> bool {
|
||||
self.completed
|
||||
}
|
||||
}
|
||||
@@ -2,57 +2,78 @@
|
||||
// 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::registration::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, EpochId, EpochState};
|
||||
use nym_coconut_dkg_common::types::{
|
||||
DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, EpochState,
|
||||
};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
|
||||
use nym_dkg::{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, vks_serde};
|
||||
use serde_helpers::{bte_pk_serde, generated_dealings_old, vks_serde};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::{Path, PathBuf};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLockReadGuard;
|
||||
use url::Url;
|
||||
|
||||
mod dealing_exchange;
|
||||
mod registration;
|
||||
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(..))
|
||||
}
|
||||
|
||||
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,
|
||||
#[serde(with = "bte_pk_serde")]
|
||||
pub(crate) bte_public_key_with_proof: PublicKeyWithProof,
|
||||
pub(crate) address: Addr,
|
||||
pub(crate) assigned_index: NodeIndex,
|
||||
pub(crate) state: ParticipantState,
|
||||
}
|
||||
|
||||
impl TryFrom<DealerDetails> for DkgParticipant {
|
||||
type Error = ComplaintReason;
|
||||
|
||||
fn try_from(dealer: DealerDetails) -> Result<Self, Self::Error> {
|
||||
let bte_public_key_with_proof = bs58::decode(dealer.bte_public_key_with_proof)
|
||||
.into_vec()
|
||||
.map(|bytes| PublicKeyWithProof::try_from_bytes(&bytes))
|
||||
.map_err(|_| ComplaintReason::MalformedBTEPublicKey)?
|
||||
.map_err(|_| ComplaintReason::MalformedBTEPublicKey)?;
|
||||
|
||||
if !bte_public_key_with_proof.verify() {
|
||||
return Err(ComplaintReason::InvalidBTEPublicKey);
|
||||
}
|
||||
|
||||
Ok(DkgParticipant {
|
||||
_address: dealer.address,
|
||||
bte_public_key_with_proof,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +160,7 @@ pub(crate) struct PersistentState {
|
||||
//
|
||||
node_index: Option<NodeIndex>,
|
||||
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
|
||||
#[serde(with = "generated_dealings")]
|
||||
#[serde(with = "generated_dealings_old")]
|
||||
generated_dealings: HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
|
||||
receiver_index: Option<usize>,
|
||||
threshold: Option<Threshold>,
|
||||
@@ -189,6 +210,7 @@ impl From<&State> for PersistentState {
|
||||
|
||||
impl PersistentState {
|
||||
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), CoconutError> {
|
||||
debug!("persisting the dkg state");
|
||||
std::fs::write(path, serde_json::to_string(self)?)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -201,6 +223,28 @@ impl PersistentState {
|
||||
#[derive(Default)]
|
||||
pub(crate) struct DkgState {
|
||||
pub(crate) registration: RegistrationState,
|
||||
|
||||
// available after registration is finished:
|
||||
pub(crate) dealers: BTreeMap<NodeIndex, DkgParticipant>,
|
||||
|
||||
pub(crate) dealing_exchange: DealingExchangeState,
|
||||
}
|
||||
|
||||
impl DkgState {
|
||||
pub(crate) fn set_dealers(&mut self, raw_dealers: Vec<DealerDetails>) {
|
||||
assert!(self.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
|
||||
)
|
||||
}
|
||||
self.dealers
|
||||
.insert(dkg_participant.assigned_index, dkg_participant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct State {
|
||||
@@ -288,24 +332,62 @@ impl State {
|
||||
self.was_in_progress = Default::default();
|
||||
}
|
||||
|
||||
pub fn init_dkg_state(&mut self, epoch_id: EpochId) {
|
||||
pub fn maybe_init_dkg_state(&mut self, epoch_id: EpochId) {
|
||||
if !self.dkg_instances.contains_key(&epoch_id) {
|
||||
self.dkg_instances.insert(epoch_id, Default::default());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registration_state(&self, epoch_id: EpochId) -> &RegistrationState {
|
||||
// safety: before any accessors are called, `init_dkg_state` is used at the beginning of submission handler
|
||||
&self.dkg_instances[&epoch_id].registration
|
||||
pub fn dkg_state(&self, epoch_id: EpochId) -> Result<&DkgState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get(&epoch_id)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn registration_state_mut(&mut self, epoch_id: EpochId) -> &mut RegistrationState {
|
||||
// safety: before any accessors are called, `init_dkg_state` is used at the beginning of submission handler
|
||||
&mut self.dkg_instances.get_mut(&epoch_id).unwrap().registration
|
||||
pub fn dkg_state_mut(&mut self, epoch_id: EpochId) -> Result<&mut DkgState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get_mut(&epoch_id)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn already_registered(&self, epoch_id: EpochId) -> bool {
|
||||
self.registration_state(epoch_id).completed()
|
||||
pub fn registration_state(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&RegistrationState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get(&epoch_id)
|
||||
.map(|state| &state.registration)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn registration_state_mut(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&mut RegistrationState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get_mut(&epoch_id)
|
||||
.map(|state| &mut state.registration)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn dealing_exchange_state(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&DealingExchangeState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get(&epoch_id)
|
||||
.map(|state| &state.dealing_exchange)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn dealing_exchange_state_mut(
|
||||
&mut self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<&mut DealingExchangeState, CoconutError> {
|
||||
self.dkg_instances
|
||||
.get_mut(&epoch_id)
|
||||
.map(|state| &mut state.dealing_exchange)
|
||||
.ok_or(CoconutError::MissingDkgState { epoch_id })
|
||||
}
|
||||
|
||||
pub fn persistent_state_path(&self) -> &Path {
|
||||
@@ -379,17 +461,18 @@ impl State {
|
||||
|
||||
// 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> {
|
||||
self.dealers
|
||||
.iter()
|
||||
.filter_map(|(_, dealer)| {
|
||||
dealer.as_ref().ok().map(|participant| {
|
||||
(
|
||||
participant.assigned_index,
|
||||
*participant.bte_public_key_with_proof.public_key(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
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> {
|
||||
@@ -424,13 +507,13 @@ impl State {
|
||||
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 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
|
||||
|
||||
@@ -51,6 +51,39 @@ pub(super) mod vks_serde {
|
||||
}
|
||||
|
||||
pub(super) mod generated_dealings {
|
||||
use nym_coconut_dkg_common::types::DealingIndex;
|
||||
use nym_dkg::Dealing;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn serialize<S: Serializer>(
|
||||
dealings: &HashMap<DealingIndex, Dealing>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let mut helper = HashMap::new();
|
||||
|
||||
for (dealing_index, dealing) in dealings {
|
||||
helper.insert(*dealing_index, dealing.to_bytes());
|
||||
}
|
||||
|
||||
helper.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<HashMap<DealingIndex, Dealing>, D::Error> {
|
||||
<HashMap<DealingIndex, Vec<u8>>>::deserialize(deserializer)?
|
||||
.into_iter()
|
||||
.map(|(index, raw_dealing)| {
|
||||
Dealing::try_from_bytes(&raw_dealing)
|
||||
.map_err(serde::de::Error::custom)
|
||||
.map(|dealing| (index, dealing))
|
||||
})
|
||||
.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};
|
||||
|
||||
@@ -138,6 +138,9 @@ pub enum CoconutError {
|
||||
#[error("the provided query arguments were invalid")]
|
||||
InvalidQueryArguments,
|
||||
|
||||
#[error("the internal dkg state for epoch {epoch_id} is missing - we might have joined mid exchange")]
|
||||
MissingDkgState { epoch_id: EpochId },
|
||||
|
||||
#[error("insufficient number of dealings provided to derive the key")]
|
||||
InsufficientDealings {
|
||||
// TODO: details
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
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;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
@@ -13,6 +15,7 @@ use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
pub struct KeyPair {
|
||||
// keys: Arc<RwLock<HashMap<EpochId, nym_coconut_interface::KeyPair>>>,
|
||||
keys: Arc<RwLock<Option<KeyPairWithEpoch>>>,
|
||||
valid: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -21,10 +24,25 @@ pub struct KeyPairWithEpoch {
|
||||
pub(crate) issued_for_epoch: EpochId,
|
||||
}
|
||||
|
||||
impl KeyPairWithEpoch {
|
||||
// extract underlying secrets from the coconut's secret key.
|
||||
// the caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized
|
||||
// `KeyPair` and `SecretKey` implement ZeroizeOnDrop; `Scalar` does not (it implements `Copy` -> important to keep in mind)
|
||||
pub(crate) fn hazmat_into_secrets(self) -> Vec<Scalar> {
|
||||
let (x, mut secrets) = self.keys.secret_key().hazmat_to_raw();
|
||||
|
||||
secrets.insert(0, x);
|
||||
secrets
|
||||
// since `nym_coconut_interface::KeyPair` implements `ZeroizeOnDrop` and we took ownership of the keypair,
|
||||
// it will get zeroized after we exit this scope
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
keys: Arc::new(RwLock::new(None)),
|
||||
valid: Arc::new(Default::default()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user