happy path for key finalization

This commit is contained in:
Jędrzej Stuczyński
2024-01-23 17:45:56 +00:00
parent 2ed4449e0b
commit a7dca2f07c
14 changed files with 158 additions and 91 deletions
@@ -54,6 +54,7 @@ pub fn try_commit_verification_key_share(
resharing,
env.contract.address.to_string(),
STATE.load(deps.storage)?.multisig_addr.to_string(),
// TODO: make this value configurable
env.block
.time
.plus_seconds(BLOCK_TIME_FOR_VERIFICATION_SECS),
+2 -1
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::error::Result;
use cw3::ProposalResponse;
use cw3::{ProposalResponse, VoteResponse};
use cw4::MemberResponse;
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse, DealingStatusResponse};
@@ -24,6 +24,7 @@ pub trait Client {
async fn get_tx(&self, tx_hash: Hash) -> Result<TxResponse>;
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse>;
async fn list_proposals(&self) -> Result<Vec<ProposalResponse>>;
async fn get_vote(&self, proposal_id: u64, voter: String) -> Result<VoteResponse>;
async fn get_spent_credential(
&self,
blinded_serial_number: String,
+8 -1
View File
@@ -3,7 +3,7 @@
use crate::coconut::client::Client;
use crate::coconut::error::CoconutError;
use cw3::ProposalResponse;
use cw3::{ProposalResponse, Status};
use cw4::MemberResponse;
use nym_coconut_dkg_common::dealer::{DealerDetails, DealerDetailsResponse};
use nym_coconut_dkg_common::types::{
@@ -119,6 +119,13 @@ impl DkgClient {
self.inner.list_proposals().await
}
pub(crate) async fn get_proposal_status(
&self,
proposal_id: u64,
) -> Result<Status, CoconutError> {
self.inner.get_proposal(proposal_id).await.map(|p| p.status)
}
pub(crate) async fn advance_epoch_state(&self) -> Result<(), CoconutError> {
self.inner.advance_epoch_state().await
}
+2 -3
View File
@@ -1,9 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::controller::error::DkgError;
use crate::coconut::dkg::key_finalization::verification_key_finalization;
use crate::coconut::dkg::state::{ConsistentState, PersistentState, State};
use crate::coconut::keys::KeyPair as CoconutKeyPair;
use crate::nyxd;
@@ -173,7 +172,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
) -> Result<(), DkgError> {
debug!("DKG: verification key finalization (resharing: {resharing})");
verification_key_finalization(&self.dkg_client, &mut self.state, resharing)
self.verification_key_finalization(epoch_id, resharing)
.await
.map_err(|source| DkgError::VerificationKeyFinalizationFailure { source })
}
+7 -5
View File
@@ -450,7 +450,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
&mut self,
key: &VerificationKey,
resharing: bool,
) -> Result<(), CoconutError> {
) -> Result<u64, CoconutError> {
debug!("submitting derived partial verification key to the contract");
let res = self
.dkg_client
@@ -468,8 +468,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
})?;
debug!("Submitted own verification key share, proposal id {proposal_id} is attached to it. tx hash: {hash}");
// TODO: state.set_proposal_id(proposal_id);
Ok(())
Ok(proposal_id)
}
/// Third step of the DKG process during which the nym api will generate its Coconut keypair
@@ -528,11 +527,14 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
todo!()
}
self.submit_partial_verification_key(coconut_keypair.keys.verification_key(), resharing)
let proposal_id = self.submit_partial_verification_key(coconut_keypair.keys.verification_key(), resharing)
.await?;
self.state.set_coconut_keypair(coconut_keypair).await;
self.state.key_derivation_state_mut(epoch_id)?.completed = true;
let derivation_state = self.state.key_derivation_state_mut(epoch_id)?;
derivation_state.completed = true;
derivation_state.proposal_id = Some(proposal_id);
info!("DKG: Finished key derivation");
Ok(())
+83 -33
View File
@@ -5,50 +5,100 @@ use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::controller::DkgController;
use crate::coconut::dkg::state::{ConsistentState, State};
use crate::coconut::error::CoconutError;
use cw3::Status;
use nym_coconut_dkg_common::types::EpochId;
use rand::{CryptoRng, RngCore};
impl<R: RngCore + CryptoRng> DkgController<R> {}
impl<R: RngCore + CryptoRng> DkgController<R> {
pub(crate) async fn verification_key_finalization(
&mut self,
epoch_id: EpochId,
_resharing: bool,
) -> Result<(), CoconutError> {
let key_finalization_state = self.state.key_finalization_state(epoch_id)?;
pub(crate) async fn verification_key_finalization(
dkg_client: &DkgClient,
state: &mut State,
_resharing: bool,
) -> Result<(), CoconutError> {
if state.executed_proposal() {
log::debug!("Already executed the proposal, nothing to do");
return Ok(());
// check if we have already executed our own proposal
if key_finalization_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!("our key has already been verified");
return Ok(());
}
let proposal_id = self.state.proposal_id(epoch_id)?;
// check whether our key has already been verified with executed proposal,
// either by us in previous iteration after a timeout
// or by another party
let status = self.dkg_client.get_proposal_status(proposal_id).await?;
match status {
Status::Pending | Status::Open => {
warn!("our proposal ({proposal_id}) still hasn't received enough votes to get accepted");
todo!();
}
Status::Rejected => {
// TODO: technically there's nothing enforcing this...
error!("our key verification proposal ({proposal_id}) has been rejected - we can't use our derived keys!");
todo!()
}
Status::Passed => {
self.dkg_client
.execute_verification_key_share(proposal_id)
.await?;
}
Status::Executed => {
debug!("our dkg proposal has already been executed");
}
}
self.state.key_finalization_state_mut(epoch_id)?.completed = true;
info!("DKG: Finalized own verification key on chain");
Ok(())
}
let proposal_id = state.proposal_id_value()?;
dkg_client
.execute_verification_key_share(proposal_id)
.await?;
state.set_executed_proposal();
info!("DKG: Finalized own verification key on chain");
Ok(())
}
// each dealer is responsible for executing its own proposals
#[cfg(test)]
mod tests {
use super::*;
use crate::coconut::tests::helpers::{
derive_keypairs, exchange_dealings, initialise_controllers, initialise_dkg,
submit_public_keys, validate_keys,
};
#[tokio::test]
#[ignore] // expensive test
async fn finalize_verification_key() {
todo!()
// let db = MockContractDb::new();
// let clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
//
// for controller in clients_and_states.iter() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// assert_eq!(proposal.status, Status::Executed);
// }
async fn finalize_verification_key() -> anyhow::Result<()> {
let validators = 4;
let mut controllers = initialise_controllers(validators);
let chain = controllers[0].chain_state.clone();
let epoch = chain.lock().unwrap().dkg_epoch.epoch_id;
initialise_dkg(&mut controllers, false);
submit_public_keys(&mut controllers, false).await;
exchange_dealings(&mut controllers, false).await;
derive_keypairs(&mut controllers, false).await;
validate_keys(&mut controllers, false).await;
for controller in controllers.iter_mut() {
let res = controller.verification_key_finalization(epoch, false).await;
assert!(res.is_ok());
assert!(controller.state.key_finalization_state(epoch)?.completed);
}
let chain = controllers[0].chain_state.clone();
let guard = chain.lock().unwrap();
let proposals = &guard.proposals;
assert_eq!(proposals.len(), validators);
for proposal in proposals.values() {
assert_eq!(Status::Executed, proposal.status)
}
Ok(())
}
}
+1 -15
View File
@@ -269,8 +269,7 @@ mod tests {
#[tokio::test]
#[ignore] // expensive test
async fn validate_verification_key() -> anyhow::Result<()> {
let unused = 42;
let validators = 1;
let validators = 4;
let mut controllers = initialise_controllers(validators);
let chain = controllers[0].chain_state.clone();
@@ -298,19 +297,6 @@ mod tests {
}
Ok(())
// let db = MockContractDb::new();
// let mut clients_and_states = prepare_clients_and_states_with_validation(&db).await;
// for controller in clients_and_states.iter_mut() {
// let proposal = db
// .proposal_db
// .read()
// .unwrap()
// .get(&controller.state.proposal_id_value().unwrap())
// .unwrap()
// .clone();
// assert_eq!(proposal.status, Status::Passed);
// }
}
#[tokio::test]
@@ -16,8 +16,11 @@ pub struct KeyDerivationState {
#[serde(with = "recovered_keys")]
pub(crate) derived_partials: BTreeMap<DealingIndex, RecoveredVerificationKeys>,
pub(crate) proposal_id: Option<u64>,
pub(crate) completed: bool,
// because we couldn't decrypt shares or there were no shares, etc
// TODO: because we couldn't decrypt shares or there were no shares, etc
// failed:
}
@@ -4,11 +4,13 @@
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct FinalizationState {}
pub struct FinalizationState {
pub(crate) completed: bool,
}
impl FinalizationState {
/// Specifies whether this dealer has already registered in the particular DKG epoch
/// Specifies whether this (or another) dealer has already executed its verification proposal
pub fn completed(&self) -> bool {
todo!()
self.completed
}
}
+4
View File
@@ -524,6 +524,10 @@ impl State {
.receiver_index
.ok_or(CoconutError::UnavailableReceiverIndex { epoch_id })
}
pub fn proposal_id(&self, epoch_id: EpochId) -> Result<u64, CoconutError> {
self.key_derivation_state(epoch_id)?.proposal_id.ok_or(CoconutError::UnavailableProposalId {epoch_id})
}
pub fn persistent_state_path(&self) -> &Path {
self.persistent_state_path.as_path()
+3
View File
@@ -152,6 +152,9 @@ pub enum CoconutError {
#[error("the threshold value for epoch {epoch_id} is not available")]
UnavailableThreshold { epoch_id: EpochId },
#[error("the proposal id value for epoch {epoch_id} is not available")]
UnavailableProposalId { epoch_id: EpochId },
#[error("insufficient number of dealings provided to derive the key")]
InsufficientDealings {
// TODO: details
+4 -5
View File
@@ -134,11 +134,10 @@ pub(crate) async fn finalize(controllers: &mut [TestingDkgController], resharing
.epoch_id;
for controller in controllers.iter_mut() {
todo!()
// controller
// .verification_key_finalization(epoch, resharing)
// .await
// .unwrap();
controller
.verification_key_finalization(epoch, resharing)
.await
.unwrap();
}
let mut guard = controllers[0].chain_state.lock().unwrap();
+25 -23
View File
@@ -7,7 +7,7 @@ use crate::coconut::{self, State};
use crate::support::storage::NymApiStorage;
use async_trait::async_trait;
use cosmwasm_std::{coin, to_binary, Addr, CosmosMsg, Decimal, WasmMsg};
use cw3::ProposalResponse;
use cw3::{ProposalResponse, VoteResponse};
use cw4::{Cw4Contract, MemberResponse};
use nym_api_requests::coconut::models::{IssuedCredentialBody, IssuedCredentialResponse};
use nym_api_requests::coconut::{
@@ -268,16 +268,15 @@ impl super::client::Client for DummyClient {
}
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse> {
todo!()
// self.state
// .lock()
// .unwrap()
// .proposals
// .get(&proposal_id)
// .cloned()
// .ok_or(CoconutError::IncorrectProposal {
// reason: String::from("proposal not found"),
// })
self.state
.lock()
.unwrap()
.proposals
.get(&proposal_id)
.cloned()
.ok_or(CoconutError::IncorrectProposal {
reason: String::from("proposal not found"),
})
}
async fn list_proposals(&self) -> Result<Vec<ProposalResponse>> {
@@ -291,6 +290,10 @@ impl super::client::Client for DummyClient {
.collect())
}
async fn get_vote(&self, proposal_id: u64, voter: String) -> Result<VoteResponse> {
todo!()
}
async fn get_spent_credential(
&self,
blinded_serial_number: String,
@@ -445,18 +448,17 @@ impl super::client::Client for DummyClient {
}
async fn execute_proposal(&self, proposal_id: u64) -> Result<()> {
todo!()
// self.state
// .lock()
// .unwrap()
// .proposals
// .entry(proposal_id)
// .and_modify(|prop| {
// if prop.status == cw3::Status::Passed {
// prop.status = cw3::Status::Executed
// }
// });
// Ok(())
self.state
.lock()
.unwrap()
.proposals
.entry(proposal_id)
.and_modify(|prop| {
if prop.status == cw3::Status::Passed {
prop.status = cw3::Status::Executed
}
});
Ok(())
}
async fn advance_epoch_state(&self) -> Result<()> {
+9 -1
View File
@@ -6,7 +6,7 @@ use crate::epoch_operations::MixnodeWithPerformance;
use crate::support::config::Config;
use anyhow::Result;
use async_trait::async_trait;
use cw3::ProposalResponse;
use cw3::{ProposalResponse, VoteResponse};
use cw4::MemberResponse;
use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse;
use nym_coconut_dkg_common::dealer::DealingStatusResponse;
@@ -375,6 +375,14 @@ impl crate::coconut::client::Client for Client {
Ok(nyxd_query!(self, get_all_proposals().await?))
}
async fn get_vote(
&self,
proposal_id: u64,
voter: String,
) -> crate::coconut::error::Result<VoteResponse> {
Ok(nyxd_query!(self, query_vote(proposal_id, voter).await?))
}
async fn get_spent_credential(
&self,
blinded_serial_number: String,