From b71a8708db974dfc67ac41185e27ffe112b32085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 21 Nov 2022 18:00:47 +0200 Subject: [PATCH] Feature/dkg dealing (#1708) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reintroduce epoch states Co-authored-by: Jędrzej Stuczyński * Use admin address for sensible txs * Validator-api watch contract and handle events * Handle dealing exchange * Dealing exchange * Recover raw verification keys for 5 dkgs * Test coconut with dkg keys * Split dealing storage * Finish dkg task when it achieved its purpose * Temporary fix for clippy * Fix clippy Co-authored-by: Jędrzej Stuczyński --- Cargo.lock | 13 +- .../validator-client/src/client.rs | 15 +- .../src/nymd/traits/dkg_query_client.rs | 24 ++- .../src/nymd/traits/dkg_signing_client.rs | 12 +- .../coconut-dkg/src/dealer.rs | 24 +-- .../coconut-dkg/src/msg.rs | 11 +- .../coconut-dkg/src/types.rs | 35 ++-- .../contracts-common/Cargo.toml | 6 +- .../contracts-common/src/commitment.rs | 186 ----------------- .../contracts-common/src/dealings.rs | 87 ++++++++ .../contracts-common/src/lib.rs | 2 +- common/nymcoconut/Cargo.toml | 8 +- common/nymcoconut/src/lib.rs | 1 + common/nymcoconut/src/scheme/keygen.rs | 14 ++ common/nymcoconut/src/tests/e2e.rs | 57 +++++- common/nymcoconut/src/tests/helpers.rs | 123 ++++++++++-- common/nymcoconut/src/utils.rs | 2 +- .../coconut-dkg/src/dealers/transactions.rs | 4 +- contracts/coconut-dkg/src/dealings/queries.rs | 33 +-- contracts/coconut-dkg/src/dealings/storage.rs | 20 +- .../coconut-dkg/src/dealings/transactions.rs | 25 ++- contracts/coconut-dkg/src/epoch_state/mod.rs | 6 + .../coconut-dkg/src/epoch_state/queries.rs | 13 ++ .../coconut-dkg/src/epoch_state/storage.rs | 29 +++ .../coconut-dkg/src/epoch_state/utils.rs | 21 ++ contracts/coconut-dkg/src/error.rs | 12 ++ contracts/coconut-dkg/src/lib.rs | 40 ++-- contracts/coconut-dkg/src/state/mod.rs | 8 +- validator-api/Cargo.toml | 2 +- validator-api/src/coconut/client.rs | 14 +- .../coconut/dkg/{publisher.rs => client.rs} | 44 ++-- validator-api/src/coconut/dkg/complaints.rs | 46 +++++ validator-api/src/coconut/dkg/controller.rs | 109 ++++++++++ validator-api/src/coconut/dkg/dealing.rs | 53 +++++ validator-api/src/coconut/dkg/mod.rs | 75 +------ validator-api/src/coconut/dkg/public_key.rs | 30 +++ validator-api/src/coconut/dkg/state.rs | 190 ++++++++++++++++++ .../src/coconut/dkg/verification_key.rs | 141 +++++++++++++ validator-api/src/coconut/error.rs | 13 ++ validator-api/src/coconut/keypair.rs | 27 +++ validator-api/src/coconut/mod.rs | 23 ++- validator-api/src/coconut/tests.rs | 59 ++++-- validator-api/src/config/mod.rs | 13 +- validator-api/src/main.rs | 21 +- validator-api/src/nymd_client.rs | 27 ++- 45 files changed, 1256 insertions(+), 462 deletions(-) delete mode 100644 common/cosmwasm-smart-contracts/contracts-common/src/commitment.rs create mode 100644 common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs create mode 100644 contracts/coconut-dkg/src/epoch_state/mod.rs create mode 100644 contracts/coconut-dkg/src/epoch_state/queries.rs create mode 100644 contracts/coconut-dkg/src/epoch_state/storage.rs create mode 100644 contracts/coconut-dkg/src/epoch_state/utils.rs rename validator-api/src/coconut/dkg/{publisher.rs => client.rs} (52%) create mode 100644 validator-api/src/coconut/dkg/complaints.rs create mode 100644 validator-api/src/coconut/dkg/controller.rs create mode 100644 validator-api/src/coconut/dkg/dealing.rs create mode 100644 validator-api/src/coconut/dkg/public_key.rs create mode 100644 validator-api/src/coconut/dkg/state.rs create mode 100644 validator-api/src/coconut/dkg/verification_key.rs create mode 100644 validator-api/src/coconut/keypair.rs diff --git a/Cargo.lock b/Cargo.lock index e079c021e2..5c3f743cb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -755,11 +755,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" name = "contracts-common" version = "0.1.0" dependencies = [ - "blake3", "bs58", "cosmwasm-std", - "digest 0.10.3", - "generic-array 0.14.5", + "dkg", "schemars", "serde", "serde_json", @@ -1986,7 +1984,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ - "serde", "typenum", "version_check", ] @@ -3476,16 +3473,18 @@ name = "nymcoconut" version = "0.5.0" dependencies = [ "bincode", - "bls12_381 0.5.0", + "bls12_381 0.6.0", "bs58", "criterion", "digest 0.9.0", + "dkg", "doc-comment", - "ff 0.10.1", + "ff 0.11.0", "getrandom 0.2.6", - "group 0.10.0", + "group 0.11.0", "itertools", "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "serde_derive", "sha2 0.9.9", diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index e9e5fafc79..59e9841d78 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{validator_api, ValidatorClientError}; -use coconut_dkg_common::dealer::ContractDealingCommitment; +use coconut_dkg_common::dealer::ContractDealing; use coconut_dkg_common::types::DealerDetails; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::MixId; @@ -577,20 +577,21 @@ impl Client { Ok(dealers) } - pub async fn get_all_nymd_epoch_dealings_commitments( + pub async fn get_all_nymd_epoch_dealings( &self, - ) -> Result, ValidatorClientError> + idx: usize, + ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync + Send, { - let mut commitments = Vec::new(); + let mut dealings = Vec::new(); let mut start_after = None; loop { let mut paged_response = self .nymd - .get_dealings_commitments_paged(start_after.take(), self.dealers_page_limit) + .get_dealings_paged(idx, start_after.take(), self.dealers_page_limit) .await?; - commitments.append(&mut paged_response.commitments); + dealings.append(&mut paged_response.dealings); if let Some(start_after_res) = paged_response.start_next_after { start_after = Some(start_after_res.into_string()) @@ -599,7 +600,7 @@ impl Client { } } - Ok(commitments) + Ok(dealings) } } diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs index 67efdec5cb..930346fe17 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs @@ -5,14 +5,15 @@ use crate::nymd::error::NymdError; use crate::nymd::{CosmWasmClient, NymdClient}; use async_trait::async_trait; use coconut_dkg_common::dealer::{ - DealerDetailsResponse, PagedCommitmentsResponse, PagedDealerResponse, + DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse, }; use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; -use coconut_dkg_common::types::MinimumDepositResponse; +use coconut_dkg_common::types::{EpochState, MinimumDepositResponse}; use cosmrs::AccountId; #[async_trait] pub trait DkgQueryClient { + async fn get_current_epoch_state(&self) -> Result; async fn get_dealer_details( &self, address: &AccountId, @@ -29,11 +30,12 @@ pub trait DkgQueryClient { ) -> Result; async fn get_deposit_amount(&self) -> Result; - async fn get_dealings_commitments_paged( + async fn get_dealings_paged( &self, + idx: usize, start_after: Option, page_limit: Option, - ) -> Result; + ) -> Result; } #[async_trait] @@ -41,6 +43,12 @@ impl DkgQueryClient for NymdClient where C: CosmWasmClient + Send + Sync, { + async fn get_current_epoch_state(&self) -> Result { + let request = DkgQueryMsg::GetCurrentEpochState {}; + self.client + .query_contract_smart(self.coconut_dkg_contract_address(), &request) + .await + } async fn get_dealer_details( &self, address: &AccountId, @@ -88,12 +96,14 @@ where .await } - async fn get_dealings_commitments_paged( + async fn get_dealings_paged( &self, + idx: usize, start_after: Option, page_limit: Option, - ) -> Result { - let request = DkgQueryMsg::GetDealingsCommitments { + ) -> Result { + let request = DkgQueryMsg::GetDealing { + idx: idx as u64, limit: page_limit, start_after, }; diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs index 9c9346d901..f93cbac41c 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs @@ -8,7 +8,7 @@ use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient}; use async_trait::async_trait; use coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg; use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; -use contracts_common::commitment::ContractSafeCommitment; +use contracts_common::dealings::ContractSafeBytes; #[async_trait] pub trait DkgSigningClient { @@ -18,9 +18,9 @@ pub trait DkgSigningClient { fee: Option, ) -> Result; - async fn submit_dealing_commitment( + async fn submit_dealing_bytes( &self, - commitment: ContractSafeCommitment, + commitment: ContractSafeBytes, fee: Option, ) -> Result; } @@ -52,12 +52,12 @@ where .await } - async fn submit_dealing_commitment( + async fn submit_dealing_bytes( &self, - commitment: ContractSafeCommitment, + dealing_bytes: ContractSafeBytes, fee: Option, ) -> Result { - let req = DkgExecuteMsg::CommitDealing { commitment }; + let req = DkgExecuteMsg::CommitDealing { dealing_bytes }; self.client .execute( diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs index 31ecce7269..46f48de623 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::types::{ContractSafeCommitment, EncodedBTEPublicKeyWithProof, NodeIndex}; +use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, NodeIndex}; use cosmwasm_std::{Addr, Coin}; use serde::{Deserialize, Serialize}; @@ -68,33 +68,33 @@ impl PagedDealerResponse { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] -pub struct ContractDealingCommitment { - pub commitment: ContractSafeCommitment, +pub struct ContractDealing { + pub dealing: ContractSafeBytes, pub dealer: Addr, } -impl ContractDealingCommitment { - pub fn new(commitment: ContractSafeCommitment, dealer: Addr) -> Self { - ContractDealingCommitment { commitment, dealer } +impl ContractDealing { + pub fn new(dealing: ContractSafeBytes, dealer: Addr) -> Self { + ContractDealing { dealing, dealer } } } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] -pub struct PagedCommitmentsResponse { - pub commitments: Vec, +pub struct PagedDealingsResponse { + pub dealings: Vec, pub per_page: usize, pub start_next_after: Option, } -impl PagedCommitmentsResponse { +impl PagedDealingsResponse { pub fn new( - commitments: Vec, + dealings: Vec, per_page: usize, start_next_after: Option, ) -> Self { - PagedCommitmentsResponse { - commitments, + PagedDealingsResponse { + dealings, per_page, start_next_after, } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index 1e6741a66c..6ed6f4618a 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::types::{ContractSafeCommitment, EncodedBTEPublicKeyWithProof}; +use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; pub struct InstantiateMsg { pub group_addr: String, pub mix_denom: String, + pub admin: String, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] @@ -19,9 +20,11 @@ pub enum ExecuteMsg { }, CommitDealing { - commitment: ContractSafeCommitment, + dealing_bytes: ContractSafeBytes, }, + AdvanceEpochState {}, + // DEBUG ONLY TXs. THEY SHALL BE REMOVED BEFORE FINALISING THE CODE // only exists for debugging purposes on local network to reset the entire state of the contract DebugUnsafeResetAll { @@ -32,6 +35,7 @@ pub enum ExecuteMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { + GetCurrentEpochState {}, GetDealerDetails { dealer_address: String, }, @@ -44,7 +48,8 @@ pub enum QueryMsg { start_after: Option, }, GetDepositAmount {}, - GetDealingsCommitments { + GetDealing { + idx: u64, limit: Option, start_after: Option, }, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index c5b26483b4..6c64d41aa3 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -5,13 +5,16 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; pub use crate::dealer::{DealerDetails, PagedDealerResponse}; -pub use contracts_common::commitment::ContractSafeCommitment; +pub use contracts_common::dealings::ContractSafeBytes; pub use cosmwasm_std::{Addr, Coin}; pub type EncodedBTEPublicKeyWithProof = String; pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str; pub type NodeIndex = u64; +// 2 public attributes, 2 private attributes, 1 fixed for coconut credential +pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; + // currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints), // the epoch can be in the following states (in order): // 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange @@ -29,28 +32,22 @@ pub type NodeIndex = u64; pub enum EpochState { PublicKeySubmission, DealingExchange, - ComplaintSubmission, - ComplaintVoting, VerificationKeySubmission, - VerificationKeyMismatchSubmission, - VerificationKeyMismatchVoting, InProgress, } +impl Default for EpochState { + fn default() -> Self { + Self::PublicKeySubmission + } +} + impl Display for EpochState { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { EpochState::PublicKeySubmission => write!(f, "PublicKeySubmission"), EpochState::DealingExchange => write!(f, "DealingExchange"), - EpochState::ComplaintSubmission => write!(f, "ComplaintSubmission"), - EpochState::ComplaintVoting => write!(f, "ComplaintVoting"), EpochState::VerificationKeySubmission => write!(f, "VerificationKeySubmission"), - EpochState::VerificationKeyMismatchSubmission => { - write!(f, "VerificationKeyMismatchSubmission") - } - EpochState::VerificationKeyMismatchVoting => { - write!(f, "VerificationKeyMismatchVoting") - } EpochState::InProgress => write!(f, "InProgress"), } } @@ -60,16 +57,8 @@ impl EpochState { pub fn next(self) -> Option { match self { EpochState::PublicKeySubmission => Some(EpochState::DealingExchange), - EpochState::DealingExchange => Some(EpochState::ComplaintSubmission), - EpochState::ComplaintSubmission => Some(EpochState::ComplaintVoting), - EpochState::ComplaintVoting => Some(EpochState::VerificationKeySubmission), - EpochState::VerificationKeySubmission => { - Some(EpochState::VerificationKeyMismatchSubmission) - } - EpochState::VerificationKeyMismatchSubmission => { - Some(EpochState::VerificationKeyMismatchVoting) - } - EpochState::VerificationKeyMismatchVoting => Some(EpochState::InProgress), + EpochState::DealingExchange => Some(EpochState::VerificationKeySubmission), + EpochState::VerificationKeySubmission => Some(EpochState::InProgress), EpochState::InProgress => None, } } diff --git a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml index 9c34b5da8f..351830cfe3 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml @@ -7,11 +7,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true } bs58 = "0.4.0" cosmwasm-std = "1.0.0" -digest = { version = "0.10.3", optional = true } -generic-array = { version = "0.14.5", features = ["serde"], optional = true } +dkg = { path = "../../../common/crypto/dkg", optional = true } schemars = "0.8" serde = { version = "1.0", features = ["derive"] } thiserror = "1" @@ -20,4 +18,4 @@ thiserror = "1" serde_json = "1.0.0" [features] -committable_trait = ["blake3", "digest", "generic-array"] +coconut = ["dkg"] \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/commitment.rs b/common/cosmwasm-smart-contracts/contracts-common/src/commitment.rs deleted file mode 100644 index e104090257..0000000000 --- a/common/cosmwasm-smart-contracts/contracts-common/src/commitment.rs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "committable_trait")] -pub use digest::{Digest, Output}; -use schemars::JsonSchema; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt::{Display, Formatter}; -#[cfg(feature = "committable_trait")] -use std::marker::PhantomData; -use std::ops::Deref; - -// some sane upper-bound size on commitment sizes -// currently set to 1024bits -pub const MAX_COMMITMENT_SIZE: usize = 128; - -// TODO: if we are to use commitments for different types, it might make sense to introduce something like -// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait -#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)] -pub struct ContractSafeCommitment(Vec); - -impl Deref for ContractSafeCommitment { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Display for ContractSafeCommitment { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if !self.0.is_empty() { - write!(f, "0x")?; - } - for byte in self.0.iter().take(MAX_COMMITMENT_SIZE) { - write!(f, "{:02X}", byte)?; - } - // just some sanity safeguards - if self.0.len() > MAX_COMMITMENT_SIZE { - write!(f, "...")?; - } - Ok(()) - } -} - -// since cosmwasm stores everything with byte representation of stringified json, it's actually more efficient -// to serialize this as a string as opposed to keeping it as vector of bytes. -// for example vec![255,255] would have string representation of "[255,255]" and will be serialized to -// [91, 50, 53, 53, 44, 50, 53, 53, 93]. the equivalent base58 encoded string `"LUv"` will be serialized to -// [34, 76, 85, 118, 34] -// -// the difference between base58 and base64 is rather minimal and I've gone with base58 for consistency sake -impl Serialize for ContractSafeCommitment { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&bs58::encode(&self.0).into_string()) - } -} - -impl<'de> Deserialize<'de> for ContractSafeCommitment { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = ::deserialize(deserializer)?; - let bytes = bs58::decode(&s) - .into_vec() - .map_err(serde::de::Error::custom)?; - Ok(ContractSafeCommitment(bytes)) - } -} - -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct UnsupportedCommitmentSize; - -#[cfg(feature = "committable_trait")] -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct InconsistentCommitmentSize; - -#[cfg(feature = "committable_trait")] -pub type DefaultHasher = blake3::Hasher; - -#[cfg(feature = "committable_trait")] -pub trait Committable { - type DigestAlgorithm: Digest; - - fn commitment_size() -> usize { - ::output_size() - } - - fn to_bytes(&self) -> Vec; - - fn produce_commitment(&self) -> MessageCommitment { - MessageCommitment { - commitment: Self::DigestAlgorithm::digest(self.to_bytes()), - _message_type: Default::default(), - } - } - - fn verify_commitment(&self, commitment: &MessageCommitment) -> bool { - let recomputed = self.produce_commitment(); - recomputed.commitment == commitment.commitment - } -} - -#[cfg(feature = "committable_trait")] -#[derive(Debug, Serialize, Deserialize)] -pub struct MessageCommitment -where - T: ?Sized + Committable, -{ - commitment: Output, - - #[serde(skip)] - _message_type: PhantomData, -} - -#[cfg(feature = "committable_trait")] -impl Clone for MessageCommitment -where - T: ?Sized + Committable, -{ - fn clone(&self) -> Self { - MessageCommitment { - commitment: self.commitment.clone(), - _message_type: Default::default(), - } - } -} - -#[cfg(feature = "committable_trait")] -impl MessageCommitment -where - T: ?Sized + Committable, -{ - pub fn value(&self) -> &[u8] { - self.commitment.as_ref() - } - - pub fn unchecked_set_value(value: &[u8]) -> Self { - MessageCommitment { - commitment: Output::::clone_from_slice(value), - _message_type: Default::default(), - } - } - - pub fn new(message: &T) -> MessageCommitment { - message.produce_commitment() - } - - pub fn contract_safe_commitment(&self) -> ContractSafeCommitment { - self.into() - } - - pub fn is_same_as(&self, other: &ContractSafeCommitment) -> bool { - self.commitment.as_slice() == other.0 - } -} - -#[cfg(feature = "committable_trait")] -impl<'a, T> From<&'a MessageCommitment> for ContractSafeCommitment -where - T: ?Sized + Committable, -{ - fn from(commitment: &'a MessageCommitment) -> Self { - ContractSafeCommitment(commitment.value().to_vec()) - } -} - -#[cfg(feature = "committable_trait")] -impl<'a, T> TryFrom<&'a ContractSafeCommitment> for MessageCommitment -where - T: ?Sized + Committable, -{ - type Error = InconsistentCommitmentSize; - - fn try_from(value: &'a ContractSafeCommitment) -> Result { - if value.len() != ::output_size() { - Err(InconsistentCommitmentSize) - } else { - Ok(MessageCommitment::unchecked_set_value(value)) - } - } -} diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs new file mode 100644 index 0000000000..8beedd4a42 --- /dev/null +++ b/common/cosmwasm-smart-contracts/contracts-common/src/dealings.rs @@ -0,0 +1,87 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "coconut")] +use dkg::{error::DkgError, Dealing}; +use schemars::JsonSchema; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::{Display, Formatter}; +use std::ops::Deref; + +// some sane upper-bound size on byte sizes +// currently set to 128 bytes +pub const MAX_DISPLAY_SIZE: usize = 128; + +// TODO: if we are to use this for different types, it might make sense to introduce something like +// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)] +pub struct ContractSafeBytes(Vec); + +impl Deref for ContractSafeBytes { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Display for ContractSafeBytes { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + if !self.0.is_empty() { + write!(f, "0x")?; + } + for byte in self.0.iter().take(MAX_DISPLAY_SIZE) { + write!(f, "{:02X}", byte)?; + } + // just some sanity safeguards + if self.0.len() > MAX_DISPLAY_SIZE { + write!(f, "...")?; + } + Ok(()) + } +} + +// since cosmwasm stores everything with byte representation of stringified json, it's actually more efficient +// to serialize this as a string as opposed to keeping it as vector of bytes. +// for example vec![255,255] would have string representation of "[255,255]" and will be serialized to +// [91, 50, 53, 53, 44, 50, 53, 53, 93]. the equivalent base58 encoded string `"LUv"` will be serialized to +// [34, 76, 85, 118, 34] +// +// the difference between base58 and base64 is rather minimal and I've gone with base58 for consistency sake +impl Serialize for ContractSafeBytes { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&bs58::encode(&self.0).into_string()) + } +} + +impl<'de> Deserialize<'de> for ContractSafeBytes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = ::deserialize(deserializer)?; + let bytes = bs58::decode(&s) + .into_vec() + .map_err(serde::de::Error::custom)?; + Ok(ContractSafeBytes(bytes)) + } +} + +#[cfg(feature = "coconut")] +impl<'a> From<&'a Dealing> for ContractSafeBytes { + fn from(dealing: &'a Dealing) -> Self { + ContractSafeBytes(dealing.to_bytes()) + } +} + +#[cfg(feature = "coconut")] +impl<'a> TryFrom<&'a ContractSafeBytes> for Dealing { + type Error = DkgError; + + fn try_from(value: &'a ContractSafeBytes) -> Result { + Dealing::try_from_bytes(&value.0) + } +} diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs b/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs index 31fd5a4a19..4c351a0ccf 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs @@ -4,7 +4,7 @@ #![warn(clippy::expect_used)] #![warn(clippy::unwrap_used)] -pub mod commitment; +pub mod dealings; pub mod events; pub mod types; diff --git a/common/nymcoconut/Cargo.toml b/common/nymcoconut/Cargo.toml index 60b916e58f..c17aa280d3 100644 --- a/common/nymcoconut/Cargo.toml +++ b/common/nymcoconut/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } +bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-serialisation", default-features = false, features = ["pairings", "alloc", "experimental"] } itertools = "0.10" digest = "0.9" rand = "0.8" @@ -18,16 +18,18 @@ bs58 = "0.4.0" sha2 = "0.9" [dependencies.ff] -version = "0.10" +version = "0.11" default-features = false [dependencies.group] -version = "0.10" +version = "0.11" default-features = false [dev-dependencies] criterion = { version="0.3", features=["html_reports"] } doc-comment = "0.3" +dkg = { path = "../crypto/dkg" } +rand_chacha = "0.3" [dev-dependencies.bincode] version = "1" diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs index ab1cab7cf7..b48bbc2837 100644 --- a/common/nymcoconut/src/lib.rs +++ b/common/nymcoconut/src/lib.rs @@ -17,6 +17,7 @@ pub use scheme::issuance::prepare_blind_sign; pub use scheme::issuance::BlindSignRequest; pub use scheme::keygen::ttp_keygen; pub use scheme::keygen::KeyPair; +pub use scheme::keygen::SecretKey; pub use scheme::keygen::VerificationKey; pub use scheme::setup::setup; pub use scheme::setup::Parameters; diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index bd7dd6cf85..75fb203dc2 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -71,6 +71,12 @@ impl TryFrom<&[u8]> for SecretKey { } impl SecretKey { + /// Following a (distributed) key generation process, scalar values can be obtained + /// outside of the normal key generation process. + pub fn create_from_raw(x: Scalar, ys: Vec) -> Self { + Self { x, ys } + } + /// Derive verification key using this secret key. pub fn verification_key(&self, params: &Parameters) -> VerificationKey { let g1 = params.gen1(); @@ -363,6 +369,14 @@ pub struct KeyPair { impl KeyPair { const MARKER_BYTES: &'static [u8] = b"coconutkeypair"; + pub fn from_keys(secret_key: SecretKey, verification_key: VerificationKey) -> Self { + Self { + secret_key, + verification_key, + index: None, + } + } + pub fn secret_key(&self) -> SecretKey { self.secret_key.clone() } diff --git a/common/nymcoconut/src/tests/e2e.rs b/common/nymcoconut/src/tests/e2e.rs index ce4fe2ab57..235b2adbab 100644 --- a/common/nymcoconut/src/tests/e2e.rs +++ b/common/nymcoconut/src/tests/e2e.rs @@ -1,11 +1,16 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::tests::helpers::tests::generate_dkg_keys; use crate::{ - aggregate_verification_keys, setup, tests::helpers::theta_from_keys_and_attributes, ttp_keygen, - verify_credential, CoconutError, VerificationKey, + aggregate_verification_keys, setup, tests::helpers::*, ttp_keygen, verify_credential, + CoconutError, VerificationKey, }; #[test] -fn main() -> Result<(), CoconutError> { +fn keygen() -> Result<(), CoconutError> { let params = setup(5)?; + let node_indices = vec![15u64, 248, 33521]; let public_attributes = params.n_random_scalars(2); @@ -18,10 +23,52 @@ fn main() -> Result<(), CoconutError> { .collect(); // aggregate verification keys - let verification_key = aggregate_verification_keys(&verification_keys, Some(&[1, 2, 3]))?; + let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?; // Generate cryptographic material to verify them - let theta = theta_from_keys_and_attributes(¶ms, &coconut_keypairs, &public_attributes)?; + let theta = theta_from_keys_and_attributes( + ¶ms, + &coconut_keypairs, + &node_indices, + &public_attributes, + )?; + + // Verify credentials + assert!(verify_credential( + ¶ms, + &verification_key, + &theta, + &public_attributes, + )); + + Ok(()) +} + +#[test] +fn dkg() -> Result<(), CoconutError> { + let params = setup(5)?; + let node_indices = vec![15u64, 248, 33521]; + + let public_attributes = params.n_random_scalars(2); + + // generate_keys + let coconut_keypairs = generate_dkg_keys(5, &node_indices); + + let verification_keys: Vec = coconut_keypairs + .iter() + .map(|keypair| keypair.verification_key()) + .collect(); + + // aggregate verification keys + let verification_key = aggregate_verification_keys(&verification_keys, Some(&node_indices))?; + + // Generate cryptographic material to verify them + let theta = theta_from_keys_and_attributes( + ¶ms, + &coconut_keypairs, + &node_indices, + &public_attributes, + )?; // Verify credentials assert!(verify_credential( diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs index 7d9c7098b9..a9be6b885c 100644 --- a/common/nymcoconut/src/tests/helpers.rs +++ b/common/nymcoconut/src/tests/helpers.rs @@ -7,6 +7,7 @@ use itertools::izip; pub fn theta_from_keys_and_attributes( params: &Parameters, coconut_keypairs: &Vec, + indices: &[scheme::SignerIndex], public_attributes: &Vec, ) -> Result { let serial_number = params.random_scalar(); @@ -23,12 +24,7 @@ pub fn theta_from_keys_and_attributes( .collect(); // aggregate verification keys - let indices: Vec = coconut_keypairs - .iter() - .enumerate() - .map(|(idx, _)| (idx + 1) as u64) - .collect(); - let verification_key = aggregate_verification_keys(&verification_keys, Some(&indices))?; + let verification_key = aggregate_verification_keys(&verification_keys, Some(indices))?; // generate blinded signatures let mut blinded_signatures = Vec::new(); @@ -44,26 +40,31 @@ pub fn theta_from_keys_and_attributes( } // Unblind - let unblinded_signatures: Vec = - izip!(blinded_signatures.iter(), verification_keys.iter()) - .map(|(s, vk)| { - s.unblind( - params, - vk, - &private_attributes, - public_attributes, - &blind_sign_request.get_commitment_hash(), - &commitments_openings, - ) - .unwrap() - }) - .collect(); + let unblinded_signatures: Vec<(scheme::SignerIndex, Signature)> = izip!( + indices.iter(), + blinded_signatures.iter(), + verification_keys.iter() + ) + .map(|(idx, s, vk)| { + ( + *idx, + s.unblind( + params, + vk, + &private_attributes, + public_attributes, + &blind_sign_request.get_commitment_hash(), + &commitments_openings, + ) + .unwrap(), + ) + }) + .collect(); // Aggregate signatures let signature_shares: Vec = unblinded_signatures .iter() - .enumerate() - .map(|(idx, signature)| SignatureShare::new(*signature, (idx + 1) as u64)) + .map(|(idx, signature)| SignatureShare::new(*signature, *idx)) .collect(); let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); @@ -85,3 +86,81 @@ pub fn theta_from_keys_and_attributes( Ok(theta) } + +#[cfg(test)] +pub mod tests { + use crate::{KeyPair, Parameters, SecretKey}; + use bls12_381::Scalar; + use dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex}; + use rand_chacha::rand_core::SeedableRng; + use std::fmt::Debug; + + pub fn generate_dkg_secrets(node_indices: &[NodeIndex]) -> Vec { + let dummy_seed = [42u8; 32]; + let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); + let params = dkg::bte::setup(); + + // the simplest possible case + let threshold = 2; + + let mut receivers = std::collections::BTreeMap::new(); + let mut full_keys = Vec::new(); + for index in node_indices { + let (dk, pk) = dkg::bte::keygen(¶ms, &mut rng); + receivers.insert(*index, *pk.public_key()); + full_keys.push((dk, pk)) + } + let dealings = node_indices + .iter() + .map(|&dealer_index| { + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + }) + .collect::>(); + let mut derived_secrets = Vec::new(); + for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + let shares = dealings + .iter() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = + combine_shares(shares, &receivers.keys().copied().collect::>()).unwrap(); + + derived_secrets.push(recovered_secret) + } + derived_secrets + } + pub fn generate_dkg_keys(num_attributes: u32, node_indices: &[NodeIndex]) -> Vec { + let params = Parameters::new(num_attributes).unwrap(); + let mut all_secrets = vec![]; + for _ in 0..num_attributes { + let secrets = generate_dkg_secrets(node_indices); + all_secrets.push(secrets); + } + let signers = transpose_matrix(all_secrets); + signers + .into_iter() + .map(|mut secrets| { + let x = secrets.pop().unwrap(); + let sk = SecretKey::create_from_raw(x, secrets); + let vk = sk.verification_key(¶ms); + KeyPair::from_keys(sk, vk) + }) + .collect() + } + + pub(crate) fn transpose_matrix(matrix: Vec>) -> Vec> { + let len = matrix[0].len(); + let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect(); + (0..len) + .map(|_| { + iters + .iter_mut() + .map(|it| it.next().unwrap()) + .collect::>() + }) + .collect::>() + .try_into() + .unwrap() + } +} diff --git a/common/nymcoconut/src/utils.rs b/common/nymcoconut/src/utils.rs index 764cfe090c..9fec3b334c 100644 --- a/common/nymcoconut/src/utils.rs +++ b/common/nymcoconut/src/utils.rs @@ -32,7 +32,7 @@ impl Polynomial { Scalar::zero() // if x is zero then we can ignore most of the expensive computation and // just return the last term of the polynomial - } else if x.is_zero() { + } else if x.is_zero().into() { // we checked that coefficients are not empty so unwrap here is fine *self.coefficients.first().unwrap() } else { diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index c921caaeb5..c7a0411f8c 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -3,8 +3,9 @@ use crate::constants::MINIMUM_DEPOSIT; use crate::dealers::storage as dealers_storage; +use crate::epoch_state::utils::check_epoch_state; use crate::{ContractError, State, STATE}; -use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof}; +use coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState}; use cosmwasm_std::{Addr, Coin, DepsMut, MessageInfo, Response}; // currently we only require that @@ -65,6 +66,7 @@ pub fn try_add_dealer( info: MessageInfo, bte_key_with_proof: EncodedBTEPublicKeyWithProof, ) -> Result { + check_epoch_state(deps.storage, EpochState::PublicKeySubmission)?; let state = STATE.load(deps.storage)?; verify_dealer(deps.branch(), &state, &info.sender)?; diff --git a/contracts/coconut-dkg/src/dealings/queries.rs b/contracts/coconut-dkg/src/dealings/queries.rs index 7d6abd9d73..cf2321837c 100644 --- a/contracts/coconut-dkg/src/dealings/queries.rs +++ b/contracts/coconut-dkg/src/dealings/queries.rs @@ -2,19 +2,26 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealings::storage; -use crate::dealings::storage::DEALING_COMMITMENTS; -use coconut_dkg_common::dealer::{ContractDealingCommitment, PagedCommitmentsResponse}; +use crate::dealings::storage::DEALINGS_BYTES; +use coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse}; +use coconut_dkg_common::types::TOTAL_DEALINGS; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; -pub fn query_epoch_dealings_commitments_paged( +pub fn query_dealings_paged( deps: Deps<'_>, + idx: u64, start_after: Option, limit: Option, -) -> StdResult { +) -> StdResult { let limit = limit - .unwrap_or(storage::COMMITMENTS_PAGE_DEFAULT_LIMIT) - .min(storage::COMMITMENTS_PAGE_MAX_LIMIT) as usize; + .unwrap_or(storage::DEALINGS_PAGE_DEFAULT_LIMIT) + .min(storage::DEALINGS_PAGE_MAX_LIMIT) as usize; + + let idx = idx as usize; + if idx >= TOTAL_DEALINGS { + return Ok(PagedDealingsResponse::new(vec![], limit, None)); + } let addr = start_after .map(|addr| deps.api.addr_validate(&addr)) @@ -22,20 +29,16 @@ pub fn query_epoch_dealings_commitments_paged( let start = addr.as_ref().map(Bound::exclusive); - let commitments = DEALING_COMMITMENTS + let dealings = DEALINGS_BYTES[idx] .range(deps.storage, start, None, Order::Ascending) .take(limit) - .map(|res| { - res.map(|(dealer, commitment)| ContractDealingCommitment::new(commitment, dealer)) - }) + .map(|res| res.map(|(dealer, dealing)| ContractDealing::new(dealing, dealer))) .collect::>>()?; - let start_next_after = commitments - .last() - .map(|commitment| commitment.dealer.clone()); + let start_next_after = dealings.last().map(|dealing| dealing.dealer.clone()); - Ok(PagedCommitmentsResponse::new( - commitments, + Ok(PagedDealingsResponse::new( + dealings, limit, start_next_after, )) diff --git a/contracts/coconut-dkg/src/dealings/storage.rs b/contracts/coconut-dkg/src/dealings/storage.rs index bcad1b95ee..26cb5b35db 100644 --- a/contracts/coconut-dkg/src/dealings/storage.rs +++ b/contracts/coconut-dkg/src/dealings/storage.rs @@ -1,14 +1,14 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::ContractSafeCommitment; +use coconut_dkg_common::types::{ContractSafeBytes, TOTAL_DEALINGS}; use cosmwasm_std::Addr; use cw_storage_plus::Map; -pub(crate) const COMMITMENTS_PAGE_MAX_LIMIT: u32 = 75; -pub(crate) const COMMITMENTS_PAGE_DEFAULT_LIMIT: u32 = 50; +pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2; +pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1; -type CommitmentKey<'a> = &'a Addr; +type DealingKey<'a> = &'a Addr; // Note to whoever is looking at this implementation and is thinking of using something similar // for storing small commitments/hashes of data on chain: @@ -21,5 +21,13 @@ type CommitmentKey<'a> = &'a Addr; // if you don't choose your prefixes wisely. // I didn't have to do it here as I'm storing relatively little data and after just base58-encoding // my bytes, I was fine with the json overhead. -pub(crate) const DEALING_COMMITMENTS: Map<'_, CommitmentKey<'_>, ContractSafeCommitment> = - Map::new("dcmt"); + +// if TOTAL_DEALINGS is modified to anything other then current value (5), this part will also need +// to be modified +pub(crate) const DEALINGS_BYTES: [Map<'_, DealingKey<'_>, ContractSafeBytes>; TOTAL_DEALINGS] = [ + Map::new("dbyt1"), + Map::new("dbyt2"), + Map::new("dbyt3"), + Map::new("dbyt4"), + Map::new("dbyt5"), +]; diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 7c3160b8b6..7b2b132254 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -2,16 +2,18 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage as dealers_storage; -use crate::dealings::storage::DEALING_COMMITMENTS; +use crate::dealings::storage::DEALINGS_BYTES; +use crate::epoch_state::utils::check_epoch_state; use crate::ContractError; -use coconut_dkg_common::types::ContractSafeCommitment; +use coconut_dkg_common::types::{ContractSafeBytes, EpochState}; use cosmwasm_std::{DepsMut, MessageInfo, Response}; -pub fn try_commit_dealing( +pub fn try_commit_dealings( deps: DepsMut<'_>, info: MessageInfo, - commitment: ContractSafeCommitment, + dealing_bytes: ContractSafeBytes, ) -> Result { + check_epoch_state(deps.storage, EpochState::DealingExchange)?; // ensure the sender is a dealer for the current epoch if dealers_storage::current_dealers() .may_load(deps.storage, &info.sender)? @@ -20,13 +22,14 @@ pub fn try_commit_dealing( return Err(ContractError::NotADealer); } - // check if this dealer has already committed to a dealing - // (we don't want to allow overwriting it as some receivers might already be using the commitment) - if DEALING_COMMITMENTS.has(deps.storage, &info.sender) { - return Err(ContractError::AlreadyCommitted); + // check if this dealer has already committed to all dealings + // (we don't want to allow overwriting anything) + for dealings in DEALINGS_BYTES { + if !dealings.has(deps.storage, &info.sender) { + dealings.save(deps.storage, &info.sender, &dealing_bytes)?; + return Ok(Response::default()); + } } - DEALING_COMMITMENTS.save(deps.storage, &info.sender, &commitment)?; - - Ok(Response::new()) + Err(ContractError::AlreadyCommitted) } diff --git a/contracts/coconut-dkg/src/epoch_state/mod.rs b/contracts/coconut-dkg/src/epoch_state/mod.rs new file mode 100644 index 0000000000..195ff89361 --- /dev/null +++ b/contracts/coconut-dkg/src/epoch_state/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod queries; +pub mod storage; +pub mod utils; diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs new file mode 100644 index 0000000000..142ee4826a --- /dev/null +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -0,0 +1,13 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::storage; +use crate::ContractError; +use coconut_dkg_common::types::EpochState; +use cosmwasm_std::Storage; + +pub(crate) fn query_current_epoch_state( + storage: &dyn Storage, +) -> Result { + storage::current_epoch_state(storage) +} diff --git a/contracts/coconut-dkg/src/epoch_state/storage.rs b/contracts/coconut-dkg/src/epoch_state/storage.rs new file mode 100644 index 0000000000..b0abe5fa5b --- /dev/null +++ b/contracts/coconut-dkg/src/epoch_state/storage.rs @@ -0,0 +1,29 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ContractError, ADMIN}; +use coconut_dkg_common::types::EpochState; +use cosmwasm_std::{DepsMut, MessageInfo, Response, Storage}; +use cw_storage_plus::Item; + +pub(crate) const CURRENT_EPOCH_STATE: Item<'_, EpochState> = Item::new("current_epoch_state"); + +pub(crate) fn current_epoch_state(storage: &dyn Storage) -> Result { + CURRENT_EPOCH_STATE + .load(storage) + .map_err(|_| ContractError::EpochNotInitialised) +} + +pub(crate) fn advance_epoch_state( + deps: DepsMut<'_>, + info: MessageInfo, +) -> Result { + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; + CURRENT_EPOCH_STATE.update::<_, ContractError>(deps.storage, |mut epoch_state| { + // TODO: When defaulting to the first state, some action will probably need to be taken on the + // rest of the contract, as we're starting with a new set of signers + epoch_state = epoch_state.next().unwrap_or_default(); + Ok(epoch_state) + })?; + Ok(Response::default()) +} diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs new file mode 100644 index 0000000000..d78fd49ae3 --- /dev/null +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -0,0 +1,21 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ContractError, CURRENT_EPOCH_STATE}; +use coconut_dkg_common::types::EpochState; +use cosmwasm_std::Storage; + +pub(crate) fn check_epoch_state( + storage: &dyn Storage, + against: EpochState, +) -> Result<(), ContractError> { + let epoch_state = CURRENT_EPOCH_STATE.load(storage)?; + if epoch_state != against { + Err(ContractError::IncorrectEpochState { + current_state: epoch_state.to_string(), + expected_state: against.to_string(), + }) + } else { + Ok(()) + } +} diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index 4d187c7a8d..a29ffd8223 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::{Addr, StdError, VerificationError}; +use cw_controllers::AdminError; use thiserror::Error; /// Custom errors for contract failure conditions. @@ -10,6 +11,9 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + Admin(#[from] AdminError), + #[error("Group contract invalid address '{addr}'")] InvalidGroup { addr: String }, @@ -40,6 +44,14 @@ pub enum ContractError { #[error("Epoch hasn't been correctly initialised!")] EpochNotInitialised, + #[error( + "Requested action needs state to be {expected_state}, currently in state {current_state}, " + )] + IncorrectEpochState { + current_state: String, + expected_state: String, + }, + // we should never ever see this error (famous last words in programming), therefore, I'd want to // explicitly declare it so that when we ultimate do see it, it's gonna be more informative over "normal" panic #[error("Somehow our validated address {address} is not using correct bech32 encoding")] diff --git a/contracts/coconut-dkg/src/lib.rs b/contracts/coconut-dkg/src/lib.rs index 78be98bdd8..0151770d1f 100644 --- a/contracts/coconut-dkg/src/lib.rs +++ b/contracts/coconut-dkg/src/lib.rs @@ -5,19 +5,22 @@ use crate::constants::MINIMUM_DEPOSIT; use crate::dealers::queries::{ query_current_dealers_paged, query_dealer_details, query_past_dealers_paged, }; -use crate::dealings::queries::query_epoch_dealings_commitments_paged; +use crate::dealings::queries::query_dealings_paged; +use crate::epoch_state::queries::query_current_epoch_state; use crate::error::ContractError; -use crate::state::{State, STATE}; +use crate::state::{State, ADMIN, STATE}; use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use coconut_dkg_common::types::MinimumDepositResponse; +use coconut_dkg_common::types::{EpochState, MinimumDepositResponse}; use cosmwasm_std::{ entry_point, to_binary, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, }; use cw4::Cw4Contract; +use epoch_state::storage::{advance_epoch_state, CURRENT_EPOCH_STATE}; mod constants; mod dealers; mod dealings; +mod epoch_state; mod error; mod state; @@ -28,11 +31,14 @@ mod state; /// `msg` is the contract initialization message, sort of like a constructor call. #[entry_point] pub fn instantiate( - deps: DepsMut<'_>, + mut deps: DepsMut<'_>, _env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> Result { + let admin_addr = deps.api.addr_validate(&msg.admin)?; + ADMIN.set(deps.branch(), Some(admin_addr))?; + let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| { ContractError::InvalidGroup { addr: msg.group_addr.clone(), @@ -45,6 +51,8 @@ pub fn instantiate( }; STATE.save(deps.storage, &state)?; + CURRENT_EPOCH_STATE.save(deps.storage, &EpochState::default())?; + Ok(Response::default()) } @@ -60,12 +68,13 @@ pub fn execute( ExecuteMsg::RegisterDealer { bte_key_with_proof } => { dealers::transactions::try_add_dealer(deps, info, bte_key_with_proof) } - ExecuteMsg::CommitDealing { commitment } => { - dealings::transactions::try_commit_dealing(deps, info, commitment) + ExecuteMsg::CommitDealing { dealing_bytes } => { + dealings::transactions::try_commit_dealings(deps, info, dealing_bytes) } ExecuteMsg::DebugUnsafeResetAll { init_msg } => { reset_contract_state(deps, env, info, init_msg) } + ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, info), } } @@ -75,6 +84,7 @@ fn reset_contract_state( info: MessageInfo, init_msg: InstantiateMsg, ) -> Result { + ADMIN.assert_admin(deps.as_ref(), &info.sender)?; // this resets the epoch instantiate(deps.branch(), env, info, init_msg)?; @@ -85,7 +95,7 @@ fn reset_contract_state( let past = dealers::storage::past_dealers() .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending) .collect::, _>>()?; - let commitments = crate::dealings::storage::DEALING_COMMITMENTS + let dealings = dealings::storage::DEALINGS_BYTES[0] .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending) .collect::, _>>()?; @@ -97,8 +107,10 @@ fn reset_contract_state( dealers::storage::past_dealers().remove(deps.storage, &dealer)?; } - for addr in commitments { - dealings::storage::DEALING_COMMITMENTS.remove(deps.storage, &addr); + for addr in dealings { + for map in dealings::storage::DEALINGS_BYTES { + map.remove(deps.storage, &addr); + } } dealers::storage::NODE_INDEX_COUNTER.save(deps.storage, &0u64)?; @@ -109,6 +121,7 @@ fn reset_contract_state( #[entry_point] pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { let response = match msg { + QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch_state(deps.storage)?)?, QueryMsg::GetDealerDetails { dealer_address } => { to_binary(&query_dealer_details(deps, dealer_address)?)? } @@ -122,9 +135,11 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result to_binary( - &query_epoch_dealings_commitments_paged(deps, start_after, limit)?, - )?, + QueryMsg::GetDealing { + idx, + limit, + start_after, + } => to_binary(&query_dealings_paged(deps, idx, start_after, limit)?)?, }; Ok(response) @@ -147,6 +162,7 @@ mod tests { let msg = InstantiateMsg { group_addr: "group_addr".to_string(), mix_denom: "nym".to_string(), + admin: "admin".to_string(), }; let info = mock_info("creator", &[]); diff --git a/contracts/coconut-dkg/src/state/mod.rs b/contracts/coconut-dkg/src/state/mod.rs index c08f9e49cb..cc5e965839 100644 --- a/contracts/coconut-dkg/src/state/mod.rs +++ b/contracts/coconut-dkg/src/state/mod.rs @@ -2,15 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 use cw4::Cw4Contract; +use cw_controllers::Admin; use cw_storage_plus::Item; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +// unique items +pub const STATE: Item = Item::new("state"); +pub const ADMIN: Admin = Admin::new("admin"); + #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct State { pub mix_denom: String, pub group_addr: Cw4Contract, } - -// unique items -pub const STATE: Item = Item::new("state"); diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index 4ab65b6e4a..90574cb063 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -76,7 +76,7 @@ dkg = { path = "../common/crypto/dkg", optional = true } gateway-client = { path = "../common/client-libs/gateway-client" } inclusion-probability = { path = "../common/inclusion-probability" } mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } -contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common", features = ["committable_trait"] } +contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common", features = ["coconut"] } multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } nymcoconut = { path = "../common/nymcoconut", optional = true } nymsphinx = { path = "../common/nymsphinx" } diff --git a/validator-api/src/coconut/client.rs b/validator-api/src/coconut/client.rs index 7df918c67d..a09215a9ff 100644 --- a/validator-api/src/coconut/client.rs +++ b/validator-api/src/coconut/client.rs @@ -3,9 +3,9 @@ use crate::coconut::error::Result; use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; -use coconut_dkg_common::dealer::DealerDetailsResponse; -use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; -use contracts_common::commitment::ContractSafeCommitment; +use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; +use contracts_common::dealings::ContractSafeBytes; use multisig_contract_common::msg::ProposalResponse; use validator_client::nymd::cosmwasm_client::types::ExecuteResult; use validator_client::nymd::{AccountId, Fee, TxResponse}; @@ -19,13 +19,13 @@ pub trait Client { &self, blinded_serial_number: String, ) -> Result; + async fn get_current_epoch_state(&self) -> Result; async fn get_self_registered_dealer_details(&self) -> Result; + async fn get_current_dealers(&self) -> Result>; + async fn get_dealings(&self, idx: usize) -> Result>; async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option) -> Result<()>; async fn register_dealer(&self, bte_key: EncodedBTEPublicKeyWithProof) -> Result; - async fn submit_dealing_commitment( - &self, - commitment: ContractSafeCommitment, - ) -> Result; + async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result; } diff --git a/validator-api/src/coconut/dkg/publisher.rs b/validator-api/src/coconut/dkg/client.rs similarity index 52% rename from validator-api/src/coconut/dkg/publisher.rs rename to validator-api/src/coconut/dkg/client.rs index d3312faa2b..87d5c926be 100644 --- a/validator-api/src/coconut/dkg/publisher.rs +++ b/validator-api/src/coconut/dkg/client.rs @@ -3,40 +3,56 @@ use crate::coconut::client::Client; use crate::coconut::error::CoconutError; -use coconut_dkg_common::dealer::DealerDetailsResponse; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, NodeIndex}; -use contracts_common::commitment::ContractSafeCommitment; +use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, NodeIndex}; +use contracts_common::dealings::ContractSafeBytes; use validator_client::nymd::cosmwasm_client::logs::{find_attribute, NODE_INDEX}; use validator_client::nymd::AccountId; -pub(crate) struct Publisher { - client: Box, +pub(crate) struct DkgClient { + inner: Box, } -impl Publisher { +impl DkgClient { pub(crate) fn new(nymd_client: C) -> Self where C: Client + Send + Sync + 'static, { - let client = Box::new(nymd_client); - Publisher { client } + DkgClient { + inner: Box::new(nymd_client), + } } pub(crate) async fn _get_address(&self) -> AccountId { - self.client.address().await + self.inner.address().await + } + + pub(crate) async fn get_current_epoch_state(&self) -> Result { + self.inner.get_current_epoch_state().await } pub(crate) async fn get_self_registered_dealer_details( &self, ) -> Result { - self.client.get_self_registered_dealer_details().await + self.inner.get_self_registered_dealer_details().await + } + + pub(crate) async fn get_current_dealers(&self) -> Result, CoconutError> { + self.inner.get_current_dealers().await + } + + pub(crate) async fn get_dealings( + &self, + idx: usize, + ) -> Result, CoconutError> { + self.inner.get_dealings(idx).await } pub(crate) async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, ) -> Result { - let res = self.client.register_dealer(bte_key).await?; + let res = self.inner.register_dealer(bte_key).await?; let node_index = find_attribute(&res.logs, "wasm", NODE_INDEX) .ok_or(CoconutError::NodeIndexRecoveryError { reason: String::from("node index not found"), @@ -50,11 +66,11 @@ impl Publisher { Ok(node_index) } - pub(crate) async fn _submit_dealing_commitment( + pub(crate) async fn submit_dealing( &self, - commitment: ContractSafeCommitment, + dealing_bytes: ContractSafeBytes, ) -> Result<(), CoconutError> { - self.client.submit_dealing_commitment(commitment).await?; + self.inner.submit_dealing(dealing_bytes).await?; Ok(()) } } diff --git a/validator-api/src/coconut/dkg/complaints.rs b/validator-api/src/coconut/dkg/complaints.rs new file mode 100644 index 0000000000..dbb44a8497 --- /dev/null +++ b/validator-api/src/coconut/dkg/complaints.rs @@ -0,0 +1,46 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use dkg::error::DkgError; + +pub(crate) enum ComplaintReason { + MalformedBTEPublicKey, + MissingDealing, + MalformedDealing(DkgError), + DealingVerificationError(DkgError), +} + +// pub(crate) async fn complaint_period( +// dkg_client: &DkgClient, +// state: &mut State, +// ) -> Result<(), CoconutError> { +// let dkg_params = dkg::bte::setup(); +// let threshold = state +// .threshold() +// .expect("We should have a tentative threshold by now"); +// let dealings = dkg_client.get_dealings().await?; +// for contract_dealing in dealings { +// match Dealing::try_from_bytes(&contract_dealing.dealing) { +// Ok(dealing) => { +// if let Err(err) = +// dealing.verify(&dkg_params, threshold, &state.current_receivers(), None) +// { +// state.remove_good_dealer(&contract_dealing.dealer); +// state.add_bad_dealer( +// contract_dealing.dealer, +// ComplaintReason::DealingVerificationError(err), +// ); +// } +// } +// Err(err) => { +// state.remove_good_dealer(&contract_dealing.dealer); +// state.add_bad_dealer( +// contract_dealing.dealer, +// ComplaintReason::MalformedDealing(err), +// ); +// } +// } +// } +// +// Ok(()) +// } diff --git a/validator-api/src/coconut/dkg/controller.rs b/validator-api/src/coconut/dkg/controller.rs new file mode 100644 index 0000000000..26ddc308ed --- /dev/null +++ b/validator-api/src/coconut/dkg/controller.rs @@ -0,0 +1,109 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::dkg::client::DkgClient; +use crate::coconut::dkg::state::{ConsistentState, State}; +use crate::coconut::dkg::{ + dealing::dealing_exchange, public_key::public_key_submission, + verification_key::verification_key_submission, +}; +use crate::coconut::keypair::KeyPair as CoconutKeyPair; +use crate::{nymd_client, Config}; +use anyhow::Result; +use coconut_dkg_common::types::EpochState; +use dkg::bte::keys::KeyPair as DkgKeyPair; +use rand::rngs::OsRng; +use rand::RngCore; +use std::time::Duration; +use task::ShutdownListener; +use tokio::time::interval; +use validator_client::nymd::SigningNymdClient; + +pub(crate) fn init_keypair(config: &Config) -> Result<()> { + let mut rng = OsRng; + let dkg_params = dkg::bte::setup(); + let kp = DkgKeyPair::new(&dkg_params, &mut rng); + pemstore::store_keypair( + &kp, + &pemstore::KeyPairPath::new( + config.decryption_key_path(), + config.public_key_with_proof_path(), + ), + )?; + Ok(()) +} + +pub(crate) struct DkgController { + dkg_client: DkgClient, + state: State, + rng: R, + polling_rate: Duration, +} + +impl DkgController { + pub(crate) fn new( + config: &Config, + nymd_client: nymd_client::Client, + coconut_keypair: CoconutKeyPair, + rng: R, + ) -> Result { + let dkg_keypair = pemstore::load_keypair(&pemstore::KeyPairPath::new( + config.decryption_key_path(), + config.public_key_with_proof_path(), + ))?; + + Ok(DkgController { + dkg_client: DkgClient::new(nymd_client), + state: State::new(dkg_keypair, coconut_keypair), + rng, + polling_rate: config.get_dkg_contract_polling_rate(), + }) + } + + async fn handle_epoch_state(&mut self) -> bool { + match self.dkg_client.get_current_epoch_state().await { + Err(e) => warn!("Could not get current epoch state {}", e), + Ok(epoch_state) => { + if let Err(e) = self.state.is_consistent(epoch_state) { + error!( + "Epoch state is corrupted - {}, the process should be terminated", + e + ); + } + let ret = match epoch_state { + EpochState::PublicKeySubmission => { + public_key_submission(&self.dkg_client, &mut self.state).await + } + EpochState::DealingExchange => { + dealing_exchange(&self.dkg_client, &mut self.state, self.rng.clone()).await + } + EpochState::VerificationKeySubmission => { + verification_key_submission(&self.dkg_client, &mut self.state).await + } + EpochState::InProgress => return true, + }; + if let Err(e) = ret { + warn!("Could not handle this iteration for the epoch state: {}", e); + } + } + } + false + } + + pub(crate) async fn run(mut self, mut shutdown: ShutdownListener) { + let mut interval = interval(self.polling_rate); + while !shutdown.is_shutdown() { + tokio::select! { + _ = interval.tick() => { + if self.handle_epoch_state().await { + // If dkg finished, we can finish this task + break; + } + } + _ = shutdown.recv() => { + trace!("DkgController: Received shutdown"); + } + } + } + } +} diff --git a/validator-api/src/coconut/dkg/dealing.rs b/validator-api/src/coconut/dkg/dealing.rs new file mode 100644 index 0000000000..647df75797 --- /dev/null +++ b/validator-api/src/coconut/dkg/dealing.rs @@ -0,0 +1,53 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::dkg::client::DkgClient; +use crate::coconut::dkg::state::{ConsistentState, State}; +use crate::coconut::error::CoconutError; +use coconut_dkg_common::types::TOTAL_DEALINGS; +use contracts_common::dealings::ContractSafeBytes; +use dkg::bte::setup; +use dkg::Dealing; +use rand::RngCore; + +pub(crate) async fn dealing_exchange( + dkg_client: &DkgClient, + state: &mut State, + rng: impl RngCore + Clone, +) -> Result<(), CoconutError> { + if state.receiver_index().is_some() { + info!("Already have index {}", state.receiver_index().unwrap()); + return Ok(()); + } + + let dealers = dkg_client.get_current_dealers().await?; + // note: ceiling in integer division can be achieved via q = (x + y - 1) / y; + let threshold = (2 * dealers.len() as u64 + 3 - 1) / 3; + + state.set_dealers(dealers); + state.set_threshold(threshold); + let receivers = state.current_dealers_by_idx(); + let params = setup(); + let dealer_index = state.node_index_value()?; + let receiver_index = receivers + .keys() + .position(|node_index| *node_index == dealer_index); + for _ in 0..TOTAL_DEALINGS { + let (dealing, _) = Dealing::create( + rng.clone(), + ¶ms, + dealer_index, + threshold, + &receivers, + None, + ); + dkg_client + .submit_dealing(ContractSafeBytes::from(&dealing)) + .await?; + } + + info!("Setting index to {}", receiver_index.unwrap()); + state.set_receiver_index(receiver_index); + + Ok(()) +} diff --git a/validator-api/src/coconut/dkg/mod.rs b/validator-api/src/coconut/dkg/mod.rs index aba57513d5..4149a3e236 100644 --- a/validator-api/src/coconut/dkg/mod.rs +++ b/validator-api/src/coconut/dkg/mod.rs @@ -1,71 +1,10 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::coconut::client::Client; -use crate::coconut::dkg::publisher::Publisher; -use crate::Config; -use anyhow::Result; -use dkg::bte::keys::KeyPair; -use rand::rngs::OsRng; -use task::ShutdownListener; - -pub(crate) mod publisher; - -pub(crate) fn init_keypair(config: &Config) -> Result<()> { - let mut rng = OsRng; - let dkg_params = dkg::bte::setup(); - let kp = KeyPair::new(&dkg_params, &mut rng); - pemstore::store_keypair( - &kp, - &pemstore::KeyPairPath::new( - config.decryption_key_path(), - config.public_key_with_proof_path(), - ), - )?; - Ok(()) -} - -pub(crate) struct DkgController { - publisher: Publisher, - keypair: KeyPair, -} - -impl DkgController { - pub(crate) fn new(config: &Config, nymd_client: C) -> Result - where - C: Client + Send + Sync + 'static, - { - let publisher = Publisher::new(nymd_client); - let keypair = pemstore::load_keypair(&pemstore::KeyPairPath::new( - config.decryption_key_path(), - config.public_key_with_proof_path(), - ))?; - Ok(DkgController { publisher, keypair }) - } - - pub(crate) async fn run(&self, mut shutdown: ShutdownListener) { - let bte_key = bs58::encode(&self.keypair.public_key().to_bytes()).into_string(); - let index = if let Some(details) = self - .publisher - .get_self_registered_dealer_details() - .await - .expect("Could not query for dealer details") - .details - { - details.assigned_index - } else { - self.publisher - .register_dealer(bte_key) - .await - .expect("Could not register dealer in dkg protocol") - }; - info!("Starting dkg protocol with index {}", index); - while !shutdown.is_shutdown() { - tokio::select! { - _ = shutdown.recv() => { - trace!("DkgController: Received shutdown"); - } - } - } - } -} +pub(crate) mod client; +pub(crate) mod complaints; +pub(crate) mod controller; +pub(crate) mod dealing; +pub(crate) mod public_key; +pub(crate) mod state; +pub(crate) mod verification_key; diff --git a/validator-api/src/coconut/dkg/public_key.rs b/validator-api/src/coconut/dkg/public_key.rs new file mode 100644 index 0000000000..de1c8c86e6 --- /dev/null +++ b/validator-api/src/coconut/dkg/public_key.rs @@ -0,0 +1,30 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::dkg::client::DkgClient; +use crate::coconut::dkg::state::State; +use crate::coconut::error::CoconutError; + +pub(crate) async fn public_key_submission( + dkg_client: &DkgClient, + state: &mut State, +) -> Result<(), CoconutError> { + if state.node_index().is_some() { + return Ok(()); + } + + let bte_key = bs58::encode(&state.dkg_keypair().public_key().to_bytes()).into_string(); + let index = if let Some(details) = dkg_client + .get_self_registered_dealer_details() + .await? + .details + { + details.assigned_index + } else { + dkg_client.register_dealer(bte_key).await? + }; + state.set_node_index(index); + info!("Starting dkg protocol with index {}", index); + + Ok(()) +} diff --git a/validator-api/src/coconut/dkg/state.rs b/validator-api/src/coconut/dkg/state.rs new file mode 100644 index 0000000000..43098d771b --- /dev/null +++ b/validator-api/src/coconut/dkg/state.rs @@ -0,0 +1,190 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::dkg::complaints::ComplaintReason; +use crate::coconut::error::CoconutError; +use crate::coconut::keypair::KeyPair as CoconutKeyPair; +use coconut_dkg_common::dealer::DealerDetails; +use coconut_dkg_common::types::EpochState; +use cosmwasm_std::Addr; +use dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; +use dkg::{NodeIndex, Threshold}; +use std::collections::BTreeMap; + +// note: each dealer is also a receiver which simplifies some logic significantly +#[derive(Debug)] +pub(crate) struct DkgParticipant { + pub(crate) _address: Addr, + pub(crate) bte_public_key_with_proof: PublicKeyWithProof, + pub(crate) assigned_index: NodeIndex, +} + +impl TryFrom for DkgParticipant { + type Error = ComplaintReason; + + fn try_from(dealer: DealerDetails) -> Result { + 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)?; + + Ok(DkgParticipant { + _address: dealer.address, + bte_public_key_with_proof, + assigned_index: dealer.assigned_index, + }) + } +} + +pub(crate) trait ConsistentState { + fn node_index_value(&self) -> Result; + fn receiver_index_value(&self) -> Result; + fn threshold(&self) -> Result; + fn is_consistent(&self, epoch_state: EpochState) -> Result<(), CoconutError> { + match epoch_state { + EpochState::PublicKeySubmission => {} + EpochState::DealingExchange => { + self.node_index_value()?; + } + EpochState::VerificationKeySubmission => { + self.node_index_value()?; + self.receiver_index_value()?; + self.threshold()?; + } + EpochState::InProgress => { + self.node_index_value()?; + self.receiver_index_value()?; + self.threshold()?; + } + } + Ok(()) + } +} + +pub(crate) struct State { + dkg_keypair: DkgKeyPair, + coconut_keypair: CoconutKeyPair, + node_index: Option, + dealers: BTreeMap>, + receiver_index: Option, + threshold: Option, +} + +impl ConsistentState for State { + fn node_index_value(&self) -> Result { + self.node_index.ok_or(CoconutError::UnrecoverableState { + reason: String::from("Node index should have been set"), + }) + } + + fn receiver_index_value(&self) -> Result { + self.receiver_index.ok_or(CoconutError::UnrecoverableState { + reason: String::from("Receiver index should have been set"), + }) + } + + fn threshold(&self) -> Result { + let threshold = self.threshold.ok_or(CoconutError::UnrecoverableState { + reason: String::from("Threshold should have been set"), + })?; + if self.current_dealers_by_idx().len() < threshold as usize { + Err(CoconutError::UnrecoverableState { + reason: String::from( + "Not enough good dealers in the signer set to achieve threshold", + ), + }) + } else { + Ok(threshold) + } + } +} + +impl State { + pub fn new(dkg_keypair: DkgKeyPair, coconut_keypair: CoconutKeyPair) -> Self { + State { + dkg_keypair, + coconut_keypair, + node_index: None, + dealers: BTreeMap::new(), + receiver_index: None, + threshold: None, + } + } + + pub fn dkg_keypair(&self) -> &DkgKeyPair { + &self.dkg_keypair + } + + pub async fn coconut_keypair_is_some(&self) -> bool { + self.coconut_keypair.get().await.is_some() + } + + pub fn node_index(&self) -> Option { + self.node_index + } + + pub fn receiver_index(&self) -> Option { + self.receiver_index + } + + pub fn current_dealers_by_addr(&self) -> BTreeMap { + self.dealers + .iter() + .filter_map(|(addr, dealer)| { + dealer + .as_ref() + .ok() + .map(|participant| (addr.clone(), participant.assigned_index)) + }) + .collect() + } + + pub fn current_dealers_by_idx(&self) -> BTreeMap { + 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 async fn set_coconut_keypair(&mut self, coconut_keypair: coconut_interface::KeyPair) { + self.coconut_keypair.set(coconut_keypair).await + } + + pub fn set_node_index(&mut self, node_index: NodeIndex) { + self.node_index = Some(node_index); + } + + pub fn set_dealers(&mut self, dealers: Vec) { + 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 + .dealers + .iter_mut() + .find(|(addr, _)| *addr == dealer_addr) + { + *value = Err(reason); + } + } + + pub fn set_receiver_index(&mut self, receiver_index: Option) { + self.receiver_index = receiver_index; + } + + pub fn set_threshold(&mut self, threshold: Threshold) { + self.threshold = Some(threshold); + } +} diff --git a/validator-api/src/coconut/dkg/verification_key.rs b/validator-api/src/coconut/dkg/verification_key.rs new file mode 100644 index 0000000000..a900708fd2 --- /dev/null +++ b/validator-api/src/coconut/dkg/verification_key.rs @@ -0,0 +1,141 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::dkg::client::DkgClient; +use crate::coconut::dkg::complaints::ComplaintReason; +use crate::coconut::dkg::state::{ConsistentState, State}; +use crate::coconut::error::CoconutError; +use coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS}; +use coconut_interface::KeyPair as CoconutKeyPair; +use cosmwasm_std::Addr; +use credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; +use dkg::bte::{decrypt_share, setup}; +use dkg::{combine_shares, Dealing}; +use nymcoconut::{KeyPair, Parameters, SecretKey}; +use std::collections::BTreeMap; + +// Filter the dealers based on what dealing they posted (or not) in the contract +async fn deterministic_filter_dealers( + dkg_client: &DkgClient, + state: &mut State, +) -> Result>, CoconutError> { + let mut dealings_maps = vec![]; + let initial_dealers_by_addr = state.current_dealers_by_addr(); + let initial_receivers = state.current_dealers_by_idx(); + let threshold = state.threshold()?; + let params = setup(); + let retries = 3; + + for idx in 0..TOTAL_DEALINGS { + let mut try_no = 0; + let dealings = loop { + // this is a really ugly way to get the dealings, but for some reason the first query + // always fails with a RPC error. + try_no += 1; + if let Ok(dealings) = dkg_client.get_dealings(idx).await { + break dealings; + } else if try_no == retries { + return Err(CoconutError::UnrecoverableState { + reason: String::from("Could not get dealings"), + }); + } + }; + let dealings_map = + BTreeMap::from_iter(dealings.into_iter().filter_map(|contract_dealing| { + match Dealing::try_from(&contract_dealing.dealing) { + Ok(dealing) => { + if let Err(err) = + dealing.verify(¶ms, threshold, &initial_receivers, None) + { + state.mark_bad_dealer( + &contract_dealing.dealer, + ComplaintReason::DealingVerificationError(err), + ); + None + } else if let Some(idx) = + initial_dealers_by_addr.get(&contract_dealing.dealer) + { + Some((*idx, (contract_dealing.dealer, dealing))) + } else { + None + } + } + Err(err) => { + state.mark_bad_dealer( + &contract_dealing.dealer, + ComplaintReason::MalformedDealing(err), + ); + None + } + } + })); + dealings_maps.push(dealings_map); + } + for (addr, _) in initial_dealers_by_addr.iter() { + for dealings_map in dealings_maps.iter() { + if !dealings_map.iter().any(|(_, (address, _))| address == addr) { + state.mark_bad_dealer(addr, ComplaintReason::MissingDealing); + break; + } + } + } + + Ok(dealings_maps) +} + +fn derive_partial_keypair( + state: &State, + dealings_maps: Vec>, +) -> Result { + let filtered_receivers_by_idx = state.current_dealers_by_idx(); + let filtered_dealers_by_addr = state.current_dealers_by_addr(); + let dk = state.dkg_keypair().private_key(); + let node_index_value = state.receiver_index_value()?; + let mut scalars = vec![]; + for dealings_map in dealings_maps.into_iter() { + let filtered_dealings: Vec<_> = dealings_map + .into_iter() + .filter_map(|(_, (addr, dealing))| { + if filtered_dealers_by_addr.keys().any(|a| addr == *a) { + Some(dealing) + } else { + None + } + }) + .collect(); + let shares = filtered_dealings + .iter() + .map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None)) + .collect::>()?; + let scalar = combine_shares( + shares, + &filtered_receivers_by_idx + .keys() + .copied() + .collect::>(), + )?; + scalars.push(scalar); + } + + let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?; + let x = scalars.pop().unwrap(); + let sk = SecretKey::create_from_raw(x, scalars); + let vk = sk.verification_key(¶ms); + + Ok(CoconutKeyPair::from_keys(sk, vk)) +} + +pub(crate) async fn verification_key_submission( + dkg_client: &DkgClient, + state: &mut State, +) -> Result<(), CoconutError> { + if state.coconut_keypair_is_some().await { + return Ok(()); + } + + let dealings_maps = deterministic_filter_dealers(dkg_client, state).await?; + let coconut_keypair = derive_partial_keypair(state, dealings_maps)?; + state.set_coconut_keypair(coconut_keypair).await; + + Ok(()) +} diff --git a/validator-api/src/coconut/error.rs b/validator-api/src/coconut/error.rs index 1dcad8224d..4bf9ba0343 100644 --- a/validator-api/src/coconut/error.rs +++ b/validator-api/src/coconut/error.rs @@ -11,6 +11,7 @@ use crypto::asymmetric::{ encryption::KeyRecoveryError, identity::{Ed25519RecoveryError, SignatureError}, }; +use dkg::error::DkgError; use validator_client::nymd::error::NymdError; use crate::node_status_api::models::ValidatorApiStorageError; @@ -31,6 +32,9 @@ pub enum CoconutError { #[error("Nymd error - {0}")] NymdError(#[from] NymdError), + #[error("Validator client error - {0}")] + ValidatorClientError(#[from] validator_client::ValidatorClientError), + #[error("Coconut internal error - {0}")] CoconutInternalError(#[from] nymcoconut::CoconutError), @@ -78,8 +82,17 @@ pub enum CoconutError { #[error("Invalid status of credential: {status}")] InvalidCredentialStatus { status: String }, + #[error("DKG error: {0}")] + DkgError(#[from] DkgError), + #[error("Failed to recover assigned node index: {reason}")] NodeIndexRecoveryError { reason: String }, + + #[error("Unrecoverable state: {reason}. Process should be restarted")] + UnrecoverableState { reason: String }, + + #[error("DKG has not finished yet in order to derive the coconut key")] + KeyPairNotDerivedYet, } impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError { diff --git a/validator-api/src/coconut/keypair.rs b/validator-api/src/coconut/keypair.rs new file mode 100644 index 0000000000..680f1c4d13 --- /dev/null +++ b/validator-api/src/coconut/keypair.rs @@ -0,0 +1,27 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::Arc; +use tokio::sync::{RwLock, RwLockReadGuard}; + +#[derive(Clone, Debug)] +pub struct KeyPair { + inner: Arc>>, +} + +impl KeyPair { + pub fn new() -> Self { + Self { + inner: Arc::new(RwLock::new(None)), + } + } + + pub async fn get(&self) -> RwLockReadGuard<'_, Option> { + self.inner.read().await + } + + pub async fn set(&self, keypair: coconut_interface::KeyPair) { + let mut w_lock = self.inner.write().await; + *w_lock = Some(keypair); + } +} diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index 6e0552cace..7902aea36a 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod comm; mod deposit; pub(crate) mod dkg; pub(crate) mod error; +pub(crate) mod keypair; #[cfg(test)] mod tests; @@ -17,8 +18,9 @@ use crate::ValidatorApiStorage; use coconut_bandwidth_contract_common::spend_credential::{ funds_from_cosmos_msgs, SpendCredentialStatus, }; +use coconut_interface::KeyPair as CoconutKeyPair; use coconut_interface::{ - Attribute, BlindSignRequest, BlindedSignature, KeyPair, Parameters, VerificationKey, + Attribute, BlindSignRequest, BlindedSignature, Parameters, VerificationKey, }; use config::defaults::VALIDATOR_API_VERSION; use credentials::coconut::params::{ @@ -27,6 +29,7 @@ use credentials::coconut::params::{ use crypto::asymmetric::encryption; use crypto::shared_key::new_ephemeral_shared_key; use crypto::symmetric::stream_cipher; +use keypair::KeyPair; use validator_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse, @@ -193,7 +196,7 @@ impl InternalSignRequest { } } -fn blind_sign(request: InternalSignRequest, key_pair: &KeyPair) -> Result { +fn blind_sign(request: InternalSignRequest, key_pair: &CoconutKeyPair) -> Result { let params = Parameters::new(request.total_params())?; Ok(coconut_interface::blind_sign( ¶ms, @@ -226,7 +229,11 @@ pub async fn post_blind_sign( blind_sign_request_body.public_attributes(), blind_sign_request_body.blind_sign_request().clone(), ); - let blinded_signature = blind_sign(internal_request, &state.key_pair)?; + let blinded_signature = if let Some(keypair) = state.key_pair.get().await.as_ref() { + blind_sign(internal_request, keypair)? + } else { + return Err(CoconutError::KeyPairNotDerivedYet); + }; let response = state .encrypt_and_store( @@ -255,9 +262,13 @@ pub async fn post_partial_bandwidth_credential( pub async fn get_verification_key( state: &RocketState, ) -> Result> { - Ok(Json(VerificationKeyResponse::new( - state.key_pair.verification_key(), - ))) + state + .key_pair + .get() + .await + .as_ref() + .map(|keypair| Json(VerificationKeyResponse::new(keypair.verification_key()))) + .ok_or(CoconutError::KeyPairNotDerivedYet) } #[get("/cosmos-address")] diff --git a/validator-api/src/coconut/tests.rs b/validator-api/src/coconut/tests.rs index afbfe2c889..2f37a5ab0c 100644 --- a/validator-api/src/coconut/tests.rs +++ b/validator-api/src/coconut/tests.rs @@ -39,9 +39,9 @@ use validator_client::validator_api::routes::{ use crate::coconut::State; use crate::ValidatorApiStorage; use async_trait::async_trait; -use coconut_dkg_common::dealer::DealerDetailsResponse; -use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; -use contracts_common::commitment::ContractSafeCommitment; +use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; +use contracts_common::dealings::ContractSafeBytes; use crypto::asymmetric::{encryption, identity}; use rand_07::rngs::OsRng; use rocket::http::Status; @@ -121,10 +121,22 @@ impl super::client::Client for DummyClient { }) } + async fn get_current_epoch_state(&self) -> Result { + todo!() + } + async fn get_self_registered_dealer_details(&self) -> Result { todo!() } + async fn get_current_dealers(&self) -> Result> { + todo!() + } + + async fn get_dealings(&self, idx: usize) -> Result> { + todo!() + } + async fn vote_proposal( &self, proposal_id: u64, @@ -148,10 +160,7 @@ impl super::client::Client for DummyClient { todo!() } - async fn submit_dealing_commitment( - &self, - commitment: ContractSafeCommitment, - ) -> Result { + async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result { todo!() } } @@ -209,11 +218,13 @@ async fn check_signer_verif_key(key_pair: KeyPair) { &Arc::new(RwLock::new(HashMap::new())), ); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let staged_key_pair = crate::coconut::KeyPair::new(); + staged_key_pair.set(key_pair).await; let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, TEST_COIN_DENOM.to_string(), - key_pair, + staged_key_pair, comm_channel, storage, )); @@ -303,11 +314,13 @@ async fn signed_before() { &Arc::new(RwLock::new(HashMap::new())), ); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let staged_key_pair = crate::coconut::KeyPair::new(); + staged_key_pair.set(key_pair).await; let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, TEST_COIN_DENOM.to_string(), - key_pair, + staged_key_pair, comm_channel, storage.clone(), )); @@ -373,10 +386,12 @@ async fn state_functions() { db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let staged_key_pair = crate::coconut::KeyPair::new(); + staged_key_pair.set(key_pair).await; let state = State::new( nymd_client, TEST_COIN_DENOM.to_string(), - key_pair, + staged_key_pair, comm_channel, storage.clone(), ); @@ -543,11 +558,13 @@ async fn blind_sign_correct() { &Arc::new(RwLock::new(HashMap::new())), ); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let staged_key_pair = crate::coconut::KeyPair::new(); + staged_key_pair.set(key_pair).await; let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, TEST_COIN_DENOM.to_string(), - key_pair, + staged_key_pair, comm_channel, storage.clone(), )); @@ -622,11 +639,13 @@ async fn signature_test() { &Arc::new(RwLock::new(HashMap::new())), ); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let staged_key_pair = crate::coconut::KeyPair::new(); + staged_key_pair.set(key_pair).await; let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, TEST_COIN_DENOM.to_string(), - key_pair, + staged_key_pair, comm_channel, storage.clone(), )); @@ -694,10 +713,12 @@ async fn get_cosmos_address() { db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let staged_key_pair = crate::coconut::KeyPair::new(); + staged_key_pair.set(key_pair).await; let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, TEST_COIN_DENOM.to_string(), - key_pair, + staged_key_pair, comm_channel, storage.clone(), )); @@ -740,15 +761,23 @@ async fn verification_of_bandwidth_credential() { hash_to_scalar(voucher_value.to_string()), hash_to_scalar(voucher_info), ]; - let theta = theta_from_keys_and_attributes(¶ms, &key_pairs, &public_attributes).unwrap(); + let indices: Vec = key_pairs + .iter() + .enumerate() + .map(|(idx, _)| (idx + 1) as u64) + .collect(); + let theta = + theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes).unwrap(); let key_pair = key_pairs.remove(0); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage1 = ValidatorApiStorage::init(db_dir).await.unwrap(); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let staged_key_pair = crate::coconut::KeyPair::new(); + staged_key_pair.set(key_pair).await; let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client.clone(), TEST_COIN_DENOM.to_string(), - key_pair, + staged_key_pair, comm_channel.clone(), storage1.clone(), )); diff --git a/validator-api/src/config/mod.rs b/validator-api/src/config/mod.rs index 2eb19d351c..6b6196eb41 100644 --- a/validator-api/src/config/mod.rs +++ b/validator-api/src/config/mod.rs @@ -12,6 +12,8 @@ mod template; pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; +pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10); + const DEFAULT_GATEWAY_SENDING_RATE: usize = 200; const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50; const DEFAULT_PACKET_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20); @@ -277,6 +279,9 @@ pub struct CoconutSigner { /// Path to the dkg dealer public key with proof. public_key_with_proof_path: PathBuf, + /// Duration of the interval for polling the dkg contract. + dkg_contract_polling_rate: Duration, + /// Specifies list of all validators on the network issuing coconut credentials. /// A special care must be taken to ensure they are in correct order. /// The list must also contain THIS validator that is running the test @@ -303,6 +308,7 @@ impl Default for CoconutSigner { keypair_path: Default::default(), decryption_key_path: CoconutSigner::default_dkg_decryption_key_path(), public_key_with_proof_path: CoconutSigner::default_dkg_public_key_with_proof_path(), + dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE, all_validator_apis: Default::default(), } } @@ -481,7 +487,7 @@ impl Config { } #[cfg(feature = "coconut")] - pub fn keypair_path(&self) -> PathBuf { + pub fn _keypair_path(&self) -> PathBuf { self.coconut_signer.keypair_path.clone() } @@ -495,6 +501,11 @@ impl Config { self.coconut_signer.public_key_with_proof_path.clone() } + #[cfg(feature = "coconut")] + pub fn get_dkg_contract_polling_rate(&self) -> Duration { + self.coconut_signer.dkg_contract_polling_rate + } + // fix dead code warnings as this method is only ever used with coconut feature #[cfg(feature = "coconut")] pub fn get_all_validator_api_endpoints(&self) -> Vec { diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 11b8a90a1d..ff44f6057e 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -41,12 +41,12 @@ use crate::epoch_operations::RewardedSetUpdater; #[cfg(feature = "coconut")] use coconut::{ comm::QueryCommunicationChannel, - dkg::{init_keypair, DkgController}, + dkg::controller::{init_keypair, DkgController}, InternalSignRequest, }; -#[cfg(feature = "coconut")] -use coconut_interface::{Base58, KeyPair}; use logging::setup_logging; +#[cfg(feature = "coconut")] +use validator_client::nymd::bip32::secp256k1::elliptic_curve::rand_core::OsRng; pub(crate) mod config; pub(crate) mod contract_cache; @@ -419,6 +419,7 @@ async fn setup_rocket( _mix_denom: String, liftoff_notify: Arc, _nymd_client: Client, + #[cfg(feature = "coconut")] coconut_keypair: coconut::keypair::KeyPair, ) -> Result> { let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); let mut rocket = rocket::build(); @@ -451,14 +452,10 @@ async fn setup_rocket( #[cfg(feature = "coconut")] let rocket = if config.get_coconut_signer_enabled() { - let keypair_bs58 = fs::read_to_string(config.keypair_path())? - .trim() - .to_string(); - let keypair = KeyPair::try_from_bs58(keypair_bs58)?; rocket.attach(InternalSignRequest::stage( _nymd_client, _mix_denom, - keypair, + coconut_keypair, QueryCommunicationChannel::new(config.get_all_validator_api_endpoints()), storage.clone().unwrap(), )) @@ -544,12 +541,17 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> { // We need a bigger timeout let shutdown = ShutdownNotifier::new(10); + #[cfg(feature = "coconut")] + let coconut_keypair = coconut::keypair::KeyPair::new(); + // let's build our rocket! let rocket = setup_rocket( &config, mix_denom, Arc::clone(&liftoff_notify), signing_nymd_client.clone(), + #[cfg(feature = "coconut")] + coconut_keypair.clone(), ) .await?; let monitor_builder = setup_network_monitor(&config, system_version, &rocket); @@ -559,7 +561,8 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> { #[cfg(feature = "coconut")] { - let dkg_controller = DkgController::new(&config, signing_nymd_client.clone())?; + let dkg_controller = + DkgController::new(&config, signing_nymd_client.clone(), coconut_keypair, OsRng)?; let shutdown_listener = shutdown.subscribe(); tokio::spawn(async move { dkg_controller.run(shutdown_listener).await }); } diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 9f38582339..d2fd06acdb 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -26,11 +26,11 @@ use async_trait::async_trait; #[cfg(feature = "coconut")] use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; #[cfg(feature = "coconut")] -use coconut_dkg_common::dealer::DealerDetailsResponse; +use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; #[cfg(feature = "coconut")] -use coconut_dkg_common::types::EncodedBTEPublicKeyWithProof; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; #[cfg(feature = "coconut")] -use contracts_common::commitment::ContractSafeCommitment; +use contracts_common::dealings::ContractSafeBytes; #[cfg(feature = "coconut")] use multisig_contract_common::msg::ProposalResponse; #[cfg(feature = "coconut")] @@ -304,6 +304,10 @@ where .await?) } + async fn get_current_epoch_state(&self) -> crate::coconut::error::Result { + Ok(self.0.read().await.nymd.get_current_epoch_state().await?) + } + async fn get_self_registered_dealer_details( &self, ) -> crate::coconut::error::Result { @@ -317,6 +321,17 @@ where .await?) } + async fn get_current_dealers(&self) -> crate::coconut::error::Result> { + Ok(self.0.read().await.get_all_nymd_current_dealers().await?) + } + + async fn get_dealings( + &self, + idx: usize, + ) -> crate::coconut::error::Result> { + Ok(self.0.read().await.get_all_nymd_epoch_dealings(idx).await?) + } + async fn vote_proposal( &self, proposal_id: u64, @@ -345,16 +360,16 @@ where .await?) } - async fn submit_dealing_commitment( + async fn submit_dealing( &self, - commitment: ContractSafeCommitment, + dealing_bytes: ContractSafeBytes, ) -> Result { Ok(self .0 .write() .await .nymd - .submit_dealing_commitment(commitment, None) + .submit_dealing_bytes(dealing_bytes, None) .await?) } }