storing epoch id alongside coconut key

This commit is contained in:
Jędrzej Stuczyński
2024-01-17 10:32:07 +00:00
parent 7a9b989db9
commit 94d3bf087a
16 changed files with 336 additions and 247 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ pub use nym_coconut::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar,
prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest,
BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute,
PublicAttribute, Signature, SignatureShare, Theta, VerificationKey,
PublicAttribute, Signature, SignatureShare, Theta, VerificationKey, SecretKey
};
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)]
+1 -1
View File
@@ -75,7 +75,7 @@ pub async fn post_blind_sign(
// produce the partial signature
debug!("producing the partial credential");
let blinded_signature = blind_sign(&blind_sign_request_body, signing_key.secret_key())?;
let blinded_signature = blind_sign(&blind_sign_request_body, signing_key.keys.secret_key())?;
// store the information locally
debug!("storing the issued credential in the database");
+22 -40
View File
@@ -1,11 +1,13 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::keys::KeyPairWithEpoch;
use crate::support::config;
use anyhow::{anyhow, bail, Context};
use nym_coconut_dkg_common::types::EpochId;
use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
use rand::{CryptoRng, RngCore};
use std::path::Path;
use thiserror::__private::AsDisplay;
pub(crate) fn init_bte_keypair<R: RngCore + CryptoRng>(
@@ -34,61 +36,41 @@ pub(crate) fn load_bte_keypair(config: &config::CoconutSigner) -> anyhow::Result
pub(crate) fn load_coconut_keypair_if_exists(
config: &config::CoconutSigner,
) -> anyhow::Result<Option<nym_coconut_interface::KeyPair>> {
if !config.storage_paths.secret_key_path.exists() {
) -> anyhow::Result<Option<KeyPairWithEpoch>> {
if !config.storage_paths.coconut_key_path.exists() {
return Ok(None);
}
nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
&config.storage_paths.secret_key_path,
&config.storage_paths.verification_key_path,
))
.context("coconut keypair load failure")
.map(Some)
nym_pemstore::load_key(&config.storage_paths.coconut_key_path)
.context("coconut key load failure")
.map(Some)
}
pub(crate) fn persist_coconut_keypair(
keys: &nym_coconut_interface::KeyPair,
store_paths: &nym_pemstore::KeyPairPath,
pub(crate) fn persist_coconut_keypair<P: AsRef<Path>>(
keys: &KeyPairWithEpoch,
store_path: P,
) -> anyhow::Result<()> {
nym_pemstore::store_keypair(keys, store_paths).context("coconut keypair store failure")
nym_pemstore::store_key(keys, store_path).context("coconut key store failure")
}
pub(crate) fn archive_coconut_keypair(
store_paths: &nym_pemstore::KeyPairPath,
pub(crate) fn archive_coconut_keypair<P: AsRef<Path>>(
store_path: P,
epoch_id: EpochId,
) -> anyhow::Result<()> {
if !store_paths.private_key_path.exists() {
bail!(
"private key does not exist at {}",
store_paths.private_key_path.as_display()
)
let store_path = store_path.as_ref();
if !store_path.exists() {
bail!("coconut key does not exist at {}", store_path.as_display())
}
let dir = store_paths
.private_key_path
let dir = store_path
.parent()
.ok_or(anyhow!("the private key does not have a valid parent"))?;
let filename = store_paths
.private_key_path
.ok_or(anyhow!("the coconut key does not have a valid parent"))?;
let filename = store_path
.file_name()
.ok_or(anyhow!("the private key does not have a filename"))?
.ok_or(anyhow!("the coconut key does not have a valid filename"))?
.to_str()
.ok_or(anyhow!("the key filename is not valid UTF8"))?;
.ok_or(anyhow!("the coconut key filename is not valid UTF8"))?;
let archive_path = dir.join(format!("epoch-{epoch_id}-{filename}.archived"));
std::fs::rename(&store_paths.private_key_path, archive_path)?;
let dir = store_paths
.public_key_path
.parent()
.ok_or(anyhow!("the public key does not have a valid parent"))?;
let filename = store_paths
.public_key_path
.file_name()
.ok_or(anyhow!("the public key does not have a filename"))?
.to_str()
.ok_or(anyhow!("the key filename is not valid UTF8"))?;
let archive_path = dir.join(format!("epoch-{epoch_id}-{filename}.archived"));
std::fs::rename(&store_paths.public_key_path, archive_path)?;
std::fs::rename(store_path, archive_path)?;
Ok(())
}
+5 -14
View File
@@ -10,7 +10,7 @@ use crate::coconut::dkg::verification_key::{
use crate::coconut::dkg::{
dealing::dealing_exchange, verification_key::verification_key_submission,
};
use crate::coconut::keypair::KeyPair as CoconutKeyPair;
use crate::coconut::keys::KeyPair as CoconutKeyPair;
use crate::nyxd;
use crate::support::config;
use anyhow::{bail, Result};
@@ -30,8 +30,7 @@ pub(crate) mod keys;
pub(crate) struct DkgController<R = OsRng> {
pub(crate) dkg_client: DkgClient,
secret_key_path: PathBuf,
verification_key_path: PathBuf,
pub(crate) coconut_key_path: PathBuf,
pub(crate) state: State,
rng: R,
polling_rate: Duration,
@@ -56,8 +55,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
Ok(DkgController {
dkg_client: DkgClient::new(nyxd_client),
secret_key_path: config.storage_paths.secret_key_path.clone(),
verification_key_path: config.storage_paths.verification_key_path.clone(),
coconut_key_path: config.storage_paths.coconut_key_path.clone(),
state: State::new(
config.storage_paths.dkg_persistent_state_path.clone(),
persistent_state,
@@ -71,10 +69,6 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
})
}
pub(crate) fn coconut_keypaths(&self) -> nym_pemstore::KeyPairPath {
nym_pemstore::KeyPairPath::new(&self.secret_key_path, &self.verification_key_path)
}
fn persist_state(&self) -> Result<(), DkgError> {
// if !self.state.coconut_keypair_is_some().await {
// // Delete the files just in case the process is killed before the new keys are generated
@@ -162,13 +156,11 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
) -> Result<(), DkgError> {
debug!("DKG: verification key submission (resharing: {resharing})");
let keypair_path =
nym_pemstore::KeyPairPath::new(&self.secret_key_path, &self.verification_key_path);
verification_key_submission(
&self.dkg_client,
&mut self.state,
epoch_id,
&keypair_path,
&self.coconut_key_path,
resharing,
)
.await
@@ -307,8 +299,7 @@ impl DkgController {
pub(crate) fn test_mock(dkg_client: DkgClient, state: State) -> DkgController {
DkgController {
dkg_client,
secret_key_path: Default::default(),
verification_key_path: Default::default(),
coconut_key_path: Default::default(),
state,
rng: OsRng,
polling_rate: Default::default(),
+42 -10
View File
@@ -1,6 +1,7 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::dkg::controller::keys::archive_coconut_keypair;
use crate::coconut::dkg::controller::DkgController;
use crate::coconut::error::CoconutError;
use log::debug;
@@ -26,11 +27,17 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
// 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_epoch, _)) = self.state.take_coconut_keypair().await {
if let Some(old_keypair) = self.state.take_coconut_keypair().await {
debug!("resetting and archiving old coconut keypair");
let store_path = self.coconut_keypaths();
// archive_coconut_keypair(&store_path, old_epoch)?
//
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:
@@ -48,6 +55,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
}
}
// perform the full registration instead
let bte_key = bs58::encode(&self.state.dkg_keypair().public_key().to_bytes()).into_string();
let identity_key = self.state.identity_key().to_base58_string();
let announce_address = self.state.announce_address().to_string();
@@ -88,7 +96,7 @@ pub(crate) mod tests {
AccountId::from_str(TEST_VALIDATOR_ADDRESS).unwrap(),
));
let identity_keypair = identity::KeyPair::new(&mut thread_rng());
let mut state = State::new(
let state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
@@ -96,6 +104,7 @@ pub(crate) mod tests {
*identity_keypair.public_key(),
KeyPair::new(),
);
let epoch = dkg_client.get_current_epoch().await.unwrap().epoch_id;
let mut controller = DkgController::test_mock(dkg_client, state);
assert!(controller
@@ -105,7 +114,10 @@ pub(crate) mod tests {
.unwrap()
.details
.is_none());
controller.public_key_submission(0, false).await.unwrap();
controller
.public_key_submission(epoch, false)
.await
.unwrap();
let client_idx = controller
.dkg_client
.get_self_registered_dealer_details()
@@ -114,11 +126,31 @@ pub(crate) mod tests {
.details
.unwrap()
.assigned_index;
assert_eq!(controller.state.node_index().unwrap(), client_idx);
assert_eq!(
controller
.state
.registration_state(epoch)
.assigned_index
.unwrap(),
client_idx
);
// keeps the same index from chain, not calling register_dealer again
controller.state.set_node_index(None);
controller.public_key_submission(0, false).await.unwrap();
assert_eq!(controller.state.node_index().unwrap(), client_idx);
controller
.state
.registration_state_mut(epoch)
.assigned_index = None;
controller
.public_key_submission(epoch, false)
.await
.unwrap();
assert_eq!(
controller
.state
.registration_state(epoch)
.assigned_index
.unwrap(),
client_idx
);
}
}
+41 -105
View File
@@ -1,12 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
mod registration;
use crate::coconut::dkg::complaints::ComplaintReason;
use crate::coconut::dkg::state::registration::RegistrationState;
use crate::coconut::error::CoconutError;
use crate::coconut::keypair::KeyPair as CoconutKeyPair;
use crate::coconut::keys::{KeyPair as CoconutKeyPair, KeyPairWithEpoch};
use cosmwasm_std::Addr;
use log::debug;
use nym_coconut_dkg_common::dealer::DealerDetails;
@@ -15,35 +13,23 @@ 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 rocket::form::validate::Contains;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_helpers::{bte_pk_serde, generated_dealings, vks_serde};
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use time::OffsetDateTime;
use tokio::sync::RwLockReadGuard;
use url::Url;
fn bte_pk_serialize<S: Serializer>(
val: &PublicKeyWithProof,
serializer: S,
) -> Result<S::Ok, S::Error> {
val.to_bytes().serialize(serializer)
}
fn bte_pk_deserialize<'de, D>(deserializer: D) -> Result<PublicKeyWithProof, D::Error>
where
D: Deserializer<'de>,
{
let vec: Vec<u8> = Deserialize::deserialize(deserializer)?;
PublicKeyWithProof::try_from_bytes(&vec).map_err(|err| Error::custom(format_args!("{:?}", err)))
}
mod registration;
mod serde_helpers;
// 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(serialize_with = "bte_pk_serialize")]
#[serde(deserialize_with = "bte_pk_deserialize")]
#[serde(with = "bte_pk_serde")]
pub(crate) bte_public_key_with_proof: PublicKeyWithProof,
pub(crate) assigned_index: NodeIndex,
}
@@ -146,69 +132,6 @@ impl ConsistentState for State {
}
}
fn vks_serialize<S: Serializer>(
val: &[RecoveredVerificationKeys],
serializer: S,
) -> Result<S::Ok, S::Error> {
let vec: Vec<Vec<u8>> = val.iter().map(|vk| vk.to_bytes()).collect();
vec.serialize(serializer)
}
fn vks_deserialize<'de, D>(deserializer: D) -> Result<Vec<RecoveredVerificationKeys>, D::Error>
where
D: Deserializer<'de>,
{
let vec: Vec<Vec<u8>> = Deserialize::deserialize(deserializer)?;
vec.into_iter()
.map(|b| {
RecoveredVerificationKeys::try_from_bytes(&b)
.map_err(|err| D::Error::custom(format_args!("{:?}", err)))
})
.collect()
}
mod generated_dealings {
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
use nym_dkg::Dealing;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
type Helper = HashMap<EpochId, HashMap<DealingIndex, Vec<u8>>>;
pub fn serialize<S: Serializer>(
dealings: &HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut helper = HashMap::new();
for (epoch, dealings) in dealings {
let mut inner = HashMap::new();
for (dealing_index, dealing) in dealings {
inner.insert(*dealing_index, dealing.to_bytes());
}
helper.insert(*epoch, inner);
}
helper.serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<HashMap<EpochId, HashMap<DealingIndex, Dealing>>, D::Error> {
let helper = <Helper>::deserialize(deserializer)?;
let mut epoch_dealings = HashMap::with_capacity(helper.len());
for (epoch, dealings) in helper {
let mut inner = HashMap::with_capacity(dealings.len());
for (dealing_index, raw_dealing) in dealings {
let dealing =
Dealing::try_from_bytes(&raw_dealing).map_err(serde::de::Error::custom)?;
inner.insert(dealing_index, dealing);
}
epoch_dealings.insert(epoch, inner);
}
Ok(epoch_dealings)
}
}
#[derive(Deserialize, Serialize)]
pub(crate) struct PersistentState {
timestamp: OffsetDateTime,
@@ -220,8 +143,7 @@ pub(crate) struct PersistentState {
generated_dealings: HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
receiver_index: Option<usize>,
threshold: Option<Threshold>,
#[serde(serialize_with = "vks_serialize")]
#[serde(deserialize_with = "vks_deserialize")]
#[serde(with = "vks_serde")]
recovered_vks: Vec<RecoveredVerificationKeys>,
proposal_id: Option<u64>,
voted_vks: bool,
@@ -288,19 +210,33 @@ pub(crate) struct State {
dkg_instances: HashMap<EpochId, DkgState>,
//
#[deprecated]
announce_address: Url,
#[deprecated]
identity_key: identity::PublicKey,
#[deprecated]
dkg_keypair: DkgKeyPair,
#[deprecated]
coconut_keypair: CoconutKeyPair,
#[deprecated]
node_index: Option<NodeIndex>,
#[deprecated]
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
#[deprecated]
generated_dealings: HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
#[deprecated]
receiver_index: Option<usize>,
#[deprecated]
threshold: Option<Threshold>,
#[deprecated]
recovered_vks: Vec<RecoveredVerificationKeys>,
#[deprecated]
proposal_id: Option<u64>,
#[deprecated]
voted_vks: bool,
#[deprecated]
executed_proposal: bool,
#[deprecated]
was_in_progress: bool,
}
@@ -313,24 +249,24 @@ impl State {
identity_key: identity::PublicKey,
coconut_keypair: CoconutKeyPair,
) -> Self {
todo!()
// State {
// persistent_state_path,
// announce_address,
// identity_key,
// dkg_keypair,
// coconut_keypair,
// node_index: persistent_state.node_index,
// dealers: persistent_state.dealers,
// generated_dealings: persistent_state.generated_dealings,
// receiver_index: persistent_state.receiver_index,
// threshold: persistent_state.threshold,
// recovered_vks: persistent_state.recovered_vks,
// proposal_id: persistent_state.proposal_id,
// voted_vks: persistent_state.voted_vks,
// executed_proposal: persistent_state.executed_proposal,
// was_in_progress: persistent_state.was_in_progress,
// }
State {
persistent_state_path,
dkg_instances: Default::default(),
announce_address,
identity_key,
dkg_keypair,
coconut_keypair,
node_index: persistent_state.node_index,
dealers: persistent_state.dealers,
generated_dealings: persistent_state.generated_dealings,
receiver_index: persistent_state.receiver_index,
threshold: persistent_state.threshold,
recovered_vks: persistent_state.recovered_vks,
proposal_id: persistent_state.proposal_id,
voted_vks: persistent_state.voted_vks,
executed_proposal: persistent_state.executed_proposal,
was_in_progress: persistent_state.was_in_progress,
}
}
pub fn persist(&self) -> Result<(), CoconutError> {
@@ -392,7 +328,7 @@ impl State {
self.coconut_keypair.get().await.is_some()
}
pub async fn take_coconut_keypair(&self) -> Option<(EpochId, nym_coconut::KeyPair)> {
pub async fn take_coconut_keypair(&self) -> Option<KeyPairWithEpoch> {
self.coconut_keypair.take().await
}
@@ -417,7 +353,7 @@ impl State {
#[cfg(test)]
pub async fn coconut_keypair(
&self,
) -> tokio::sync::RwLockReadGuard<'_, Option<nym_coconut::KeyPair>> {
) -> tokio::sync::RwLockReadGuard<'_, Option<KeyPairWithEpoch>> {
self.coconut_keypair.get().await
}
@@ -0,0 +1,93 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
pub(super) mod bte_pk_serde {
use nym_dkg::bte::PublicKeyWithProof;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(
val: &PublicKeyWithProof,
serializer: S,
) -> Result<S::Ok, S::Error> {
val.to_bytes().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<PublicKeyWithProof, D::Error>
where
D: Deserializer<'de>,
{
let vec: Vec<u8> = Deserialize::deserialize(deserializer)?;
PublicKeyWithProof::try_from_bytes(&vec)
.map_err(|err| Error::custom(format_args!("{:?}", err)))
}
}
pub(super) mod vks_serde {
use nym_dkg::RecoveredVerificationKeys;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(
val: &[RecoveredVerificationKeys],
serializer: S,
) -> Result<S::Ok, S::Error> {
let vec: Vec<Vec<u8>> = val.iter().map(|vk| vk.to_bytes()).collect();
vec.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<RecoveredVerificationKeys>, D::Error>
where
D: Deserializer<'de>,
{
let vec: Vec<Vec<u8>> = Deserialize::deserialize(deserializer)?;
vec.into_iter()
.map(|b| {
RecoveredVerificationKeys::try_from_bytes(&b)
.map_err(|err| D::Error::custom(format_args!("{:?}", err)))
})
.collect()
}
}
pub(super) mod generated_dealings {
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
use nym_dkg::Dealing;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
type Helper = HashMap<EpochId, HashMap<DealingIndex, Vec<u8>>>;
pub fn serialize<S: Serializer>(
dealings: &HashMap<EpochId, HashMap<DealingIndex, Dealing>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut helper = HashMap::new();
for (epoch, dealings) in dealings {
let mut inner = HashMap::new();
for (dealing_index, dealing) in dealings {
inner.insert(*dealing_index, dealing.to_bytes());
}
helper.insert(*epoch, inner);
}
helper.serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<HashMap<EpochId, HashMap<DealingIndex, Dealing>>, D::Error> {
let helper = <Helper>::deserialize(deserializer)?;
let mut epoch_dealings = HashMap::with_capacity(helper.len());
for (epoch, dealings) in helper {
let mut inner = HashMap::with_capacity(dealings.len());
for (dealing_index, raw_dealing) in dealings {
let dealing =
Dealing::try_from_bytes(&raw_dealing).map_err(serde::de::Error::custom)?;
inner.insert(dealing_index, dealing);
}
epoch_dealings.insert(epoch, inner);
}
Ok(epoch_dealings)
}
}
+52 -57
View File
@@ -23,6 +23,7 @@ use nym_dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold}
use nym_pemstore::KeyPairPath;
use nym_validator_client::nyxd::cosmwasm_client::logs::find_attribute;
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
// Filter the dealers based on what dealing they posted (or not) in the contract
@@ -207,7 +208,7 @@ pub(crate) async fn verification_key_submission(
dkg_client: &DkgClient,
state: &mut State,
epoch_id: EpochId,
keypair_path: &KeyPairPath,
key_path: &PathBuf,
resharing: bool,
) -> Result<(), CoconutError> {
if state.coconut_keypair_is_some().await {
@@ -215,38 +216,40 @@ pub(crate) async fn verification_key_submission(
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(())
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(())
}
fn validate_proposal(proposal: &ProposalResponse) -> Option<(Addr, u64)> {
@@ -451,20 +454,17 @@ pub(crate) mod tests {
let mut clients_and_states = prepare_clients_and_states_with_dealing(db).await;
for controller in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
let keypath = temp_dir().join(format!("coconut{}.pem", random_file));
verification_key_submission(
&controller.dkg_client,
&mut controller.state,
0,
&keypair_path,
&keypath,
false,
)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
std::fs::remove_file(keypath).unwrap();
}
clients_and_states
}
@@ -1023,6 +1023,7 @@ pub(crate) mod tests {
.await
.as_ref()
.unwrap()
.keys
.verification_key()
.clone();
let index = controller.state.node_index().unwrap();
@@ -1083,20 +1084,17 @@ pub(crate) mod tests {
for controller in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
let keypath = temp_dir().join(format!("coconut{}.pem", random_file));
verification_key_submission(
&controller.dkg_client,
&mut controller.state,
0,
&keypair_path,
&keypath,
true,
)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
std::fs::remove_file(keypath).unwrap();
}
for controller in clients_and_states.iter_mut() {
verification_key_validation(&controller.dkg_client, &mut controller.state, true)
@@ -1124,6 +1122,7 @@ pub(crate) mod tests {
.await
.as_ref()
.unwrap()
.keys
.verification_key()
.clone();
let index = controller.state.node_index().unwrap();
@@ -1208,20 +1207,17 @@ pub(crate) mod tests {
}
for controller in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
let keypath = temp_dir().join(format!("coconut{}.pem", random_file));
verification_key_submission(
&controller.dkg_client,
&mut controller.state,
0,
&keypair_path,
&keypath,
false,
)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
std::fs::remove_file(keypath).unwrap();
}
for controller in clients_and_states.iter_mut() {
verification_key_validation(&controller.dkg_client, &mut controller.state, false)
@@ -1253,6 +1249,7 @@ pub(crate) mod tests {
.await
.as_ref()
.unwrap()
.keys
.verification_key()
.clone();
let index = controller.state.node_index().unwrap();
@@ -1290,20 +1287,17 @@ pub(crate) mod tests {
for controller in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
let keypath = temp_dir().join(format!("coconut{}.pem", random_file));
verification_key_submission(
&controller.dkg_client,
&mut controller.state,
0,
&keypair_path,
&keypath,
true,
)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
std::fs::remove_file(keypath).unwrap();
}
for controller in clients_and_states.iter_mut() {
@@ -1332,6 +1326,7 @@ pub(crate) mod tests {
.await
.as_ref()
.unwrap()
.keys
.verification_key()
.clone();
let index = controller.state.node_index().unwrap();
+13
View File
@@ -1,10 +1,12 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_coconut_dkg_common::types::EpochId;
use rocket::http::{ContentType, Status};
use rocket::response::Responder;
use rocket::{response, Request, Response};
use std::io::Cursor;
use std::path::PathBuf;
use thiserror::Error;
use nym_crypto::asymmetric::{
@@ -12,6 +14,7 @@ use nym_crypto::asymmetric::{
identity::{Ed25519RecoveryError, SignatureError},
};
use nym_dkg::error::DkgError;
use nym_pemstore::KeyPairPath;
use nym_validator_client::coconut::CoconutApiError;
use nym_validator_client::nyxd::error::{NyxdError, TendermintError};
@@ -118,6 +121,16 @@ pub enum CoconutError {
#[error("the coconut keypair is corrupted")]
CorruptedCoconutKeyPair,
#[error("failed to archive coconut key for epoch {epoch_id} using path {}: {source}", path.display())]
KeyArchiveFailure {
epoch_id: EpochId,
path: PathBuf,
// I hate that we're using anyhow error source here, but changing that would require bigger refactoring
#[source]
source: anyhow::Error,
},
#[error("there was a problem with the proposal id: {reason}")]
ProposalIdError { reason: String },
@@ -1,16 +1,24 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
mod persistence;
use nym_coconut_dkg_common::types::EpochId;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
use tokio::sync::{RwLock, RwLockReadGuard};
#[derive(Clone, Debug)]
pub struct KeyPair {
// keys: Arc<RwLock<HashMap<EpochId, nym_coconut_interface::KeyPair>>>,
keys: Arc<RwLock<Option<(EpochId, nym_coconut_interface::KeyPair)>>>,
// issued_for_epoch: Arc<AtomicU64>,
keys: Arc<RwLock<Option<KeyPairWithEpoch>>>,
}
#[derive(Debug)]
pub struct KeyPairWithEpoch {
pub(crate) keys: nym_coconut_interface::KeyPair,
pub(crate) issued_for_epoch: EpochId,
}
impl KeyPair {
@@ -20,11 +28,11 @@ impl KeyPair {
}
}
pub async fn take(&self) -> Option<(EpochId, nym_coconut::KeyPair)> {
pub async fn take(&self) -> Option<KeyPairWithEpoch> {
self.keys.write().await.take()
}
pub async fn get(&self) -> RwLockReadGuard<'_, Option<nym_coconut::KeyPair>> {
pub async fn get(&self) -> RwLockReadGuard<'_, Option<KeyPairWithEpoch>> {
todo!()
// self.keys.read().await
}
@@ -35,6 +43,7 @@ impl KeyPair {
// *w_lock = Some(keypair);
}
#[deprecated]
pub async fn invalidate(&self) {
*self.keys.write().await = None
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::keys::KeyPairWithEpoch;
use crate::coconut::state::BANDWIDTH_CREDENTIAL_PARAMS;
use nym_coconut::{CoconutError, KeyPair, SecretKey};
use nym_coconut_dkg_common::types::EpochId;
use nym_pemstore::traits::PemStorableKey;
use std::mem;
impl PemStorableKey for KeyPairWithEpoch {
// that's not the best error for this, but it felt like an overkill to define a dedicated struct just for this purpose
type Error = CoconutError;
fn pem_type() -> &'static str {
"COCONUT KEY WITH EPOCH"
}
fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.issued_for_epoch.to_be_bytes().to_vec();
bytes.append(&mut self.keys.secret_key().to_bytes());
bytes
}
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() <= mem::size_of::<EpochId>() {
return Err(CoconutError::Deserialization(
"insufficient number of bytes to decode secret key with epoch id".into(),
));
}
let epoch_id = EpochId::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
let sk = SecretKey::from_bytes(&bytes[mem::size_of::<EpochId>()..])?;
let vk = sk.verification_key(&BANDWIDTH_CREDENTIAL_PARAMS);
Ok(KeyPairWithEpoch {
keys: KeyPair::from_keys(sk, vk),
issued_for_epoch: epoch_id,
})
}
}
+2 -2
View File
@@ -5,7 +5,7 @@ use self::comm::APICommunicationChannel;
use crate::coconut::client::Client as LocalClient;
use crate::coconut::state::State;
use crate::support::storage::NymApiStorage;
use keypair::KeyPair;
use keys::KeyPair;
use nym_config::defaults::NYM_API_VERSION;
use nym_crypto::asymmetric::identity;
use nym_validator_client::nym_api::routes::{BANDWIDTH, COCONUT_ROUTES};
@@ -18,7 +18,7 @@ mod deposit;
pub(crate) mod dkg;
pub(crate) mod error;
pub(crate) mod helpers;
pub(crate) mod keypair;
pub(crate) mod keys;
pub(crate) mod state;
pub(crate) mod storage;
#[cfg(test)]
+1 -1
View File
@@ -5,7 +5,7 @@ use crate::coconut::client::Client as LocalClient;
use crate::coconut::comm::APICommunicationChannel;
use crate::coconut::deposit::validate_deposit_tx;
use crate::coconut::error::Result;
use crate::coconut::keypair::KeyPair;
use crate::coconut::keys::KeyPair;
use crate::coconut::storage::CoconutStorageExt;
use crate::support::storage::NymApiStorage;
use lazy_static::lazy_static;
+1 -1
View File
@@ -69,7 +69,7 @@ async fn start_nym_api_tasks(config: Config) -> anyhow::Result<ShutdownHandles>
let nym_network_details = NymNetworkDetails::new_from_env();
let network_details = NetworkDetails::new(connected_nyxd.to_string(), nym_network_details);
let coconut_keypair_wrapper = coconut::keypair::KeyPair::new();
let coconut_keypair_wrapper = coconut::keys::KeyPair::new();
// if the keypair doesnt exist (because say this API is running in the caching mode), nothing will happen
if let Some(loaded_keys) = load_coconut_keypair_if_exists(&config.coconut_signer)? {
+4 -9
View File
@@ -14,8 +14,7 @@ pub const DEFAULT_NODE_STATUS_API_DATABASE_FILENAME: &str = "db.sqlite";
pub const DEFAULT_DKG_PERSISTENT_STATE_FILENAME: &str = "dkg_persistent_state.json";
pub const DEFAULT_DKG_DECRYPTION_KEY_FILENAME: &str = "dkg_decryption_key.pem";
pub const DEFAULT_DKG_PUBLIC_KEY_WITH_PROOF_FILENAME: &str = "dkg_public_key_with_proof.pem";
pub const DEFAULT_COCONUT_VERIFICATION_KEY_FILENAME: &str = "coconut_verification_key.pem";
pub const DEFAULT_COCONUT_SECRET_KEY_FILENAME: &str = "coconut_secret_key.pem";
pub const DEFAULT_COCONUT_KEY_FILENAME: &str = "coconut.pem";
pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem";
pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem";
@@ -78,11 +77,8 @@ pub struct CoconutSignerPaths {
/// Path to a JSON file where state is persisted between different stages of DKG.
pub dkg_persistent_state_path: PathBuf,
/// Path to the coconut verification key.
pub verification_key_path: PathBuf,
/// Path to the coconut secret key.
pub secret_key_path: PathBuf,
/// Path to the coconut key.
pub coconut_key_path: PathBuf,
/// Path to the dkg dealer decryption key.
pub decryption_key_path: PathBuf,
@@ -97,8 +93,7 @@ impl CoconutSignerPaths {
CoconutSignerPaths {
dkg_persistent_state_path: data_dir.join(DEFAULT_DKG_PERSISTENT_STATE_FILENAME),
verification_key_path: data_dir.join(DEFAULT_COCONUT_VERIFICATION_KEY_FILENAME),
secret_key_path: data_dir.join(DEFAULT_COCONUT_SECRET_KEY_FILENAME),
coconut_key_path: data_dir.join(DEFAULT_COCONUT_KEY_FILENAME),
decryption_key_path: data_dir.join(DEFAULT_DKG_DECRYPTION_KEY_FILENAME),
public_key_with_proof_path: data_dir.join(DEFAULT_DKG_PUBLIC_KEY_WITH_PROOF_FILENAME),
}
+1 -1
View File
@@ -29,7 +29,7 @@ pub(crate) async fn setup_rocket(
network_details: NetworkDetails,
_nyxd_client: nyxd::Client,
identity_keypair: identity::KeyPair,
coconut_keypair: coconut::keypair::KeyPair,
coconut_keypair: coconut::keys::KeyPair,
) -> anyhow::Result<Rocket<Ignite>> {
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
let mut rocket = rocket::build();