diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 65aab8fb7f..147a20df3f 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -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)] diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 6e505d0479..a406a53126 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -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"); diff --git a/nym-api/src/coconut/dkg/controller/keys.rs b/nym-api/src/coconut/dkg/controller/keys.rs index eefd277937..f002caf8a1 100644 --- a/nym-api/src/coconut/dkg/controller/keys.rs +++ b/nym-api/src/coconut/dkg/controller/keys.rs @@ -1,11 +1,13 @@ // Copyright 2024 - Nym Technologies SA // 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( @@ -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> { - if !config.storage_paths.secret_key_path.exists() { +) -> anyhow::Result> { + 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>( + 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>( + 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(()) } diff --git a/nym-api/src/coconut/dkg/controller/mod.rs b/nym-api/src/coconut/dkg/controller/mod.rs index 660fcaf5ae..7af9afb65f 100644 --- a/nym-api/src/coconut/dkg/controller/mod.rs +++ b/nym-api/src/coconut/dkg/controller/mod.rs @@ -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 { 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 DkgController { 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 DkgController { }) } - 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 DkgController { ) -> 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(), diff --git a/nym-api/src/coconut/dkg/public_key.rs b/nym-api/src/coconut/dkg/public_key.rs index 953264a922..accf170184 100644 --- a/nym-api/src/coconut/dkg/public_key.rs +++ b/nym-api/src/coconut/dkg/public_key.rs @@ -1,6 +1,7 @@ // Copyright 2022-2024 - Nym Technologies SA // 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 DkgController { // 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 DkgController { } } + // 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 + ); } } diff --git a/nym-api/src/coconut/dkg/state/mod.rs b/nym-api/src/coconut/dkg/state/mod.rs index 0a9a43ebe4..cd8aedc7a6 100644 --- a/nym-api/src/coconut/dkg/state/mod.rs +++ b/nym-api/src/coconut/dkg/state/mod.rs @@ -1,12 +1,10 @@ // Copyright 2022 - Nym Technologies SA // 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( - val: &PublicKeyWithProof, - serializer: S, -) -> Result { - val.to_bytes().serialize(serializer) -} - -fn bte_pk_deserialize<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let vec: Vec = 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( - val: &[RecoveredVerificationKeys], - serializer: S, -) -> Result { - let vec: Vec> = val.iter().map(|vk| vk.to_bytes()).collect(); - vec.serialize(serializer) -} - -fn vks_deserialize<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let vec: Vec> = 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>>; - - pub fn serialize( - dealings: &HashMap>, - serializer: S, - ) -> Result { - 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>, D::Error> { - let 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>, receiver_index: Option, threshold: Option, - #[serde(serialize_with = "vks_serialize")] - #[serde(deserialize_with = "vks_deserialize")] + #[serde(with = "vks_serde")] recovered_vks: Vec, proposal_id: Option, voted_vks: bool, @@ -288,19 +210,33 @@ pub(crate) struct State { dkg_instances: HashMap, // + #[deprecated] announce_address: Url, + #[deprecated] identity_key: identity::PublicKey, + #[deprecated] dkg_keypair: DkgKeyPair, + #[deprecated] coconut_keypair: CoconutKeyPair, + #[deprecated] node_index: Option, + #[deprecated] dealers: BTreeMap>, + #[deprecated] generated_dealings: HashMap>, + #[deprecated] receiver_index: Option, + #[deprecated] threshold: Option, + #[deprecated] recovered_vks: Vec, + #[deprecated] proposal_id: Option, + #[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 { self.coconut_keypair.take().await } @@ -417,7 +353,7 @@ impl State { #[cfg(test)] pub async fn coconut_keypair( &self, - ) -> tokio::sync::RwLockReadGuard<'_, Option> { + ) -> tokio::sync::RwLockReadGuard<'_, Option> { self.coconut_keypair.get().await } diff --git a/nym-api/src/coconut/dkg/state/serde_helpers.rs b/nym-api/src/coconut/dkg/state/serde_helpers.rs new file mode 100644 index 0000000000..f6fda611cd --- /dev/null +++ b/nym-api/src/coconut/dkg/state/serde_helpers.rs @@ -0,0 +1,93 @@ +// Copyright 2024 - Nym Technologies SA +// 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( + val: &PublicKeyWithProof, + serializer: S, + ) -> Result { + val.to_bytes().serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let vec: Vec = 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( + val: &[RecoveredVerificationKeys], + serializer: S, + ) -> Result { + let vec: Vec> = val.iter().map(|vk| vk.to_bytes()).collect(); + vec.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let vec: Vec> = 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>>; + + pub fn serialize( + dealings: &HashMap>, + serializer: S, + ) -> Result { + 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>, D::Error> { + let 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) + } +} diff --git a/nym-api/src/coconut/dkg/verification_key.rs b/nym-api/src/coconut/dkg/verification_key.rs index f7f7cb4699..c7734d7ad8 100644 --- a/nym-api/src/coconut/dkg/verification_key.rs +++ b/nym-api/src/coconut/dkg/verification_key.rs @@ -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::>() - ); - 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::() - .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::>() + // ); + // 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::() + // .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(); diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 05e89b4081..0103acc240 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -1,10 +1,12 @@ // Copyright 2022 - Nym Technologies SA // 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 }, diff --git a/nym-api/src/coconut/keypair.rs b/nym-api/src/coconut/keys/mod.rs similarity index 64% rename from nym-api/src/coconut/keypair.rs rename to nym-api/src/coconut/keys/mod.rs index 85939b46ca..9c03e9a096 100644 --- a/nym-api/src/coconut/keypair.rs +++ b/nym-api/src/coconut/keys/mod.rs @@ -1,16 +1,24 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // 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>>, - keys: Arc>>, - // issued_for_epoch: Arc, + keys: Arc>>, +} + +#[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 { self.keys.write().await.take() } - pub async fn get(&self) -> RwLockReadGuard<'_, Option> { + pub async fn get(&self) -> RwLockReadGuard<'_, Option> { 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 } diff --git a/nym-api/src/coconut/keys/persistence.rs b/nym-api/src/coconut/keys/persistence.rs new file mode 100644 index 0000000000..df34ea17be --- /dev/null +++ b/nym-api/src/coconut/keys/persistence.rs @@ -0,0 +1,43 @@ +// Copyright 2024 - Nym Technologies SA +// 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 { + 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 { + if bytes.len() <= mem::size_of::() { + 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::()..])?; + let vk = sk.verification_key(&BANDWIDTH_CREDENTIAL_PARAMS); + + Ok(KeyPairWithEpoch { + keys: KeyPair::from_keys(sk, vk), + issued_for_epoch: epoch_id, + }) + } +} diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index 3f25cba0e1..2e1af74968 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -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)] diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs index 0cffc9e752..b1f4310db8 100644 --- a/nym-api/src/coconut/state.rs +++ b/nym-api/src/coconut/state.rs @@ -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; diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs index fe86b5afe1..c5cfb53454 100644 --- a/nym-api/src/main.rs +++ b/nym-api/src/main.rs @@ -69,7 +69,7 @@ async fn start_nym_api_tasks(config: Config) -> anyhow::Result 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)? { diff --git a/nym-api/src/support/config/persistence.rs b/nym-api/src/support/config/persistence.rs index 9a9b253776..d14996b743 100644 --- a/nym-api/src/support/config/persistence.rs +++ b/nym-api/src/support/config/persistence.rs @@ -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), } diff --git a/nym-api/src/support/http/mod.rs b/nym-api/src/support/http/mod.rs index 7f54a35f8e..1ca67c50bb 100644 --- a/nym-api/src/support/http/mod.rs +++ b/nym-api/src/support/http/mod.rs @@ -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> { let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); let mut rocket = rocket::build();