Feature/dkg dealing (#1708)
* Reintroduce epoch states Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com> * 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 <jedrzej.stuczynski@gmail.com>
This commit is contained in:
committed by
GitHub
parent
fea6f44a57
commit
b71a8708db
Generated
+6
-7
@@ -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",
|
||||
|
||||
@@ -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<C> Client<C> {
|
||||
Ok(dealers)
|
||||
}
|
||||
|
||||
pub async fn get_all_nymd_epoch_dealings_commitments(
|
||||
pub async fn get_all_nymd_epoch_dealings(
|
||||
&self,
|
||||
) -> Result<Vec<ContractDealingCommitment>, ValidatorClientError>
|
||||
idx: usize,
|
||||
) -> Result<Vec<ContractDealing>, 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<C> Client<C> {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(commitments)
|
||||
Ok(dealings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<EpochState, NymdError>;
|
||||
async fn get_dealer_details(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
@@ -29,11 +30,12 @@ pub trait DkgQueryClient {
|
||||
) -> Result<PagedDealerResponse, NymdError>;
|
||||
|
||||
async fn get_deposit_amount(&self) -> Result<MinimumDepositResponse, NymdError>;
|
||||
async fn get_dealings_commitments_paged(
|
||||
async fn get_dealings_paged(
|
||||
&self,
|
||||
idx: usize,
|
||||
start_after: Option<String>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedCommitmentsResponse, NymdError>;
|
||||
) -> Result<PagedDealingsResponse, NymdError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -41,6 +43,12 @@ impl<C> DkgQueryClient for NymdClient<C>
|
||||
where
|
||||
C: CosmWasmClient + Send + Sync,
|
||||
{
|
||||
async fn get_current_epoch_state(&self) -> Result<EpochState, NymdError> {
|
||||
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<String>,
|
||||
page_limit: Option<u32>,
|
||||
) -> Result<PagedCommitmentsResponse, NymdError> {
|
||||
let request = DkgQueryMsg::GetDealingsCommitments {
|
||||
) -> Result<PagedDealingsResponse, NymdError> {
|
||||
let request = DkgQueryMsg::GetDealing {
|
||||
idx: idx as u64,
|
||||
limit: page_limit,
|
||||
start_after,
|
||||
};
|
||||
|
||||
@@ -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<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
|
||||
async fn submit_dealing_commitment(
|
||||
async fn submit_dealing_bytes(
|
||||
&self,
|
||||
commitment: ContractSafeCommitment,
|
||||
commitment: ContractSafeBytes,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError>;
|
||||
}
|
||||
@@ -52,12 +52,12 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
async fn submit_dealing_commitment(
|
||||
async fn submit_dealing_bytes(
|
||||
&self,
|
||||
commitment: ContractSafeCommitment,
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NymdError> {
|
||||
let req = DkgExecuteMsg::CommitDealing { commitment };
|
||||
let req = DkgExecuteMsg::CommitDealing { dealing_bytes };
|
||||
|
||||
self.client
|
||||
.execute(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<ContractDealingCommitment>,
|
||||
pub struct PagedDealingsResponse {
|
||||
pub dealings: Vec<ContractDealing>,
|
||||
pub per_page: usize,
|
||||
pub start_next_after: Option<Addr>,
|
||||
}
|
||||
|
||||
impl PagedCommitmentsResponse {
|
||||
impl PagedDealingsResponse {
|
||||
pub fn new(
|
||||
commitments: Vec<ContractDealingCommitment>,
|
||||
dealings: Vec<ContractDealing>,
|
||||
per_page: usize,
|
||||
start_next_after: Option<Addr>,
|
||||
) -> Self {
|
||||
PagedCommitmentsResponse {
|
||||
commitments,
|
||||
PagedDealingsResponse {
|
||||
dealings,
|
||||
per_page,
|
||||
start_next_after,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<String>,
|
||||
},
|
||||
GetDepositAmount {},
|
||||
GetDealingsCommitments {
|
||||
GetDealing {
|
||||
idx: u64,
|
||||
limit: Option<u32>,
|
||||
start_after: Option<String>,
|
||||
},
|
||||
|
||||
@@ -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<Self> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
@@ -1,186 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<u8>);
|
||||
|
||||
impl Deref for ContractSafeCommitment {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&bs58::encode(&self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ContractSafeCommitment {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <String>::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 {
|
||||
<Self::DigestAlgorithm as Digest>::output_size()
|
||||
}
|
||||
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
|
||||
fn produce_commitment(&self) -> MessageCommitment<Self> {
|
||||
MessageCommitment {
|
||||
commitment: Self::DigestAlgorithm::digest(self.to_bytes()),
|
||||
_message_type: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_commitment(&self, commitment: &MessageCommitment<Self>) -> bool {
|
||||
let recomputed = self.produce_commitment();
|
||||
recomputed.commitment == commitment.commitment
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "committable_trait")]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct MessageCommitment<T>
|
||||
where
|
||||
T: ?Sized + Committable,
|
||||
{
|
||||
commitment: Output<T::DigestAlgorithm>,
|
||||
|
||||
#[serde(skip)]
|
||||
_message_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "committable_trait")]
|
||||
impl<T> Clone for MessageCommitment<T>
|
||||
where
|
||||
T: ?Sized + Committable,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
MessageCommitment {
|
||||
commitment: self.commitment.clone(),
|
||||
_message_type: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "committable_trait")]
|
||||
impl<T> MessageCommitment<T>
|
||||
where
|
||||
T: ?Sized + Committable,
|
||||
{
|
||||
pub fn value(&self) -> &[u8] {
|
||||
self.commitment.as_ref()
|
||||
}
|
||||
|
||||
pub fn unchecked_set_value(value: &[u8]) -> Self {
|
||||
MessageCommitment {
|
||||
commitment: Output::<T::DigestAlgorithm>::clone_from_slice(value),
|
||||
_message_type: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(message: &T) -> MessageCommitment<T> {
|
||||
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<T>> for ContractSafeCommitment
|
||||
where
|
||||
T: ?Sized + Committable,
|
||||
{
|
||||
fn from(commitment: &'a MessageCommitment<T>) -> Self {
|
||||
ContractSafeCommitment(commitment.value().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "committable_trait")]
|
||||
impl<'a, T> TryFrom<&'a ContractSafeCommitment> for MessageCommitment<T>
|
||||
where
|
||||
T: ?Sized + Committable,
|
||||
{
|
||||
type Error = InconsistentCommitmentSize;
|
||||
|
||||
fn try_from(value: &'a ContractSafeCommitment) -> Result<Self, Self::Error> {
|
||||
if value.len() != <T::DigestAlgorithm as digest::Digest>::output_size() {
|
||||
Err(InconsistentCommitmentSize)
|
||||
} else {
|
||||
Ok(MessageCommitment::unchecked_set_value(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<u8>);
|
||||
|
||||
impl Deref for ContractSafeBytes {
|
||||
type Target = Vec<u8>;
|
||||
|
||||
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&bs58::encode(&self.0).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ContractSafeBytes {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = <String>::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<Self, Self::Error> {
|
||||
Dealing::try_from_bytes(&value.0)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub mod commitment;
|
||||
pub mod dealings;
|
||||
pub mod events;
|
||||
pub mod types;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Scalar>) -> 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()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<VerificationKey> = 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(
|
||||
|
||||
@@ -7,6 +7,7 @@ use itertools::izip;
|
||||
pub fn theta_from_keys_and_attributes(
|
||||
params: &Parameters,
|
||||
coconut_keypairs: &Vec<KeyPair>,
|
||||
indices: &[scheme::SignerIndex],
|
||||
public_attributes: &Vec<PublicAttribute>,
|
||||
) -> Result<Theta, CoconutError> {
|
||||
let serial_number = params.random_scalar();
|
||||
@@ -23,12 +24,7 @@ pub fn theta_from_keys_and_attributes(
|
||||
.collect();
|
||||
|
||||
// aggregate verification keys
|
||||
let indices: Vec<u64> = 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<Signature> =
|
||||
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<SignatureShare> = 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<Scalar> {
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>()).unwrap();
|
||||
|
||||
derived_secrets.push(recovered_secret)
|
||||
}
|
||||
derived_secrets
|
||||
}
|
||||
pub fn generate_dkg_keys(num_attributes: u32, node_indices: &[NodeIndex]) -> Vec<KeyPair> {
|
||||
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<T: Debug>(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> {
|
||||
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::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Response, ContractError> {
|
||||
check_epoch_state(deps.storage, EpochState::PublicKeySubmission)?;
|
||||
let state = STATE.load(deps.storage)?;
|
||||
|
||||
verify_dealer(deps.branch(), &state, &info.sender)?;
|
||||
|
||||
@@ -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<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedCommitmentsResponse> {
|
||||
) -> StdResult<PagedDealingsResponse> {
|
||||
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::<StdResult<Vec<_>>>()?;
|
||||
|
||||
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,
|
||||
))
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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"),
|
||||
];
|
||||
|
||||
@@ -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<Response, ContractError> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod queries;
|
||||
pub mod storage;
|
||||
pub mod utils;
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<EpochState, ContractError> {
|
||||
storage::current_epoch_state(storage)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<EpochState, ContractError> {
|
||||
CURRENT_EPOCH_STATE
|
||||
.load(storage)
|
||||
.map_err(|_| ContractError::EpochNotInitialised)
|
||||
}
|
||||
|
||||
pub(crate) fn advance_epoch_state(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
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())
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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(())
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
|
||||
@@ -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<Response, ContractError> {
|
||||
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<Response, ContractError> {
|
||||
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::<Result<Vec<_>, _>>()?;
|
||||
let commitments = crate::dealings::storage::DEALING_COMMITMENTS
|
||||
let dealings = dealings::storage::DEALINGS_BYTES[0]
|
||||
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
@@ -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<QueryResponse, ContractError> {
|
||||
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<QueryResponse,
|
||||
MINIMUM_DEPOSIT.u128(),
|
||||
STATE.load(deps.storage)?.mix_denom,
|
||||
)))?,
|
||||
QueryMsg::GetDealingsCommitments { limit, start_after } => 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", &[]);
|
||||
|
||||
|
||||
@@ -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<State> = 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<State> = Item::new("state");
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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<SpendCredentialResponse>;
|
||||
async fn get_current_epoch_state(&self) -> Result<EpochState>;
|
||||
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse>;
|
||||
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>>;
|
||||
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>>;
|
||||
async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option<Fee>)
|
||||
-> Result<()>;
|
||||
async fn register_dealer(&self, bte_key: EncodedBTEPublicKeyWithProof)
|
||||
-> Result<ExecuteResult>;
|
||||
async fn submit_dealing_commitment(
|
||||
&self,
|
||||
commitment: ContractSafeCommitment,
|
||||
) -> Result<ExecuteResult>;
|
||||
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult>;
|
||||
}
|
||||
|
||||
+30
-14
@@ -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<dyn Client + Send + Sync>,
|
||||
pub(crate) struct DkgClient {
|
||||
inner: Box<dyn Client + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Publisher {
|
||||
impl DkgClient {
|
||||
pub(crate) fn new<C>(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<EpochState, CoconutError> {
|
||||
self.inner.get_current_epoch_state().await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_self_registered_dealer_details(
|
||||
&self,
|
||||
) -> Result<DealerDetailsResponse, CoconutError> {
|
||||
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<Vec<DealerDetails>, CoconutError> {
|
||||
self.inner.get_current_dealers().await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_dealings(
|
||||
&self,
|
||||
idx: usize,
|
||||
) -> Result<Vec<ContractDealing>, CoconutError> {
|
||||
self.inner.get_dealings(idx).await
|
||||
}
|
||||
|
||||
pub(crate) async fn register_dealer(
|
||||
&self,
|
||||
bte_key: EncodedBTEPublicKeyWithProof,
|
||||
) -> Result<NodeIndex, CoconutError> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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(())
|
||||
// }
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<R> {
|
||||
dkg_client: DkgClient,
|
||||
state: State,
|
||||
rng: R,
|
||||
polling_rate: Duration,
|
||||
}
|
||||
|
||||
impl<R: RngCore + Clone> DkgController<R> {
|
||||
pub(crate) fn new(
|
||||
config: &Config,
|
||||
nymd_client: nymd_client::Client<SigningNymdClient>,
|
||||
coconut_keypair: CoconutKeyPair,
|
||||
rng: R,
|
||||
) -> Result<Self> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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(())
|
||||
}
|
||||
@@ -1,71 +1,10 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<C>(config: &Config, nymd_client: C) -> Result<Self>
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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(())
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<DealerDetails> for DkgParticipant {
|
||||
type Error = ComplaintReason;
|
||||
|
||||
fn try_from(dealer: DealerDetails) -> Result<Self, Self::Error> {
|
||||
let bte_public_key_with_proof = bs58::decode(dealer.bte_public_key_with_proof)
|
||||
.into_vec()
|
||||
.map(|bytes| PublicKeyWithProof::try_from_bytes(&bytes))
|
||||
.map_err(|_| ComplaintReason::MalformedBTEPublicKey)?
|
||||
.map_err(|_| ComplaintReason::MalformedBTEPublicKey)?;
|
||||
|
||||
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<NodeIndex, CoconutError>;
|
||||
fn receiver_index_value(&self) -> Result<usize, CoconutError>;
|
||||
fn threshold(&self) -> Result<Threshold, CoconutError>;
|
||||
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<NodeIndex>,
|
||||
dealers: BTreeMap<Addr, Result<DkgParticipant, ComplaintReason>>,
|
||||
receiver_index: Option<usize>,
|
||||
threshold: Option<Threshold>,
|
||||
}
|
||||
|
||||
impl ConsistentState for State {
|
||||
fn node_index_value(&self) -> Result<NodeIndex, CoconutError> {
|
||||
self.node_index.ok_or(CoconutError::UnrecoverableState {
|
||||
reason: String::from("Node index should have been set"),
|
||||
})
|
||||
}
|
||||
|
||||
fn receiver_index_value(&self) -> Result<usize, CoconutError> {
|
||||
self.receiver_index.ok_or(CoconutError::UnrecoverableState {
|
||||
reason: String::from("Receiver index should have been set"),
|
||||
})
|
||||
}
|
||||
|
||||
fn threshold(&self) -> Result<Threshold, CoconutError> {
|
||||
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<NodeIndex> {
|
||||
self.node_index
|
||||
}
|
||||
|
||||
pub fn receiver_index(&self) -> Option<usize> {
|
||||
self.receiver_index
|
||||
}
|
||||
|
||||
pub fn current_dealers_by_addr(&self) -> BTreeMap<Addr, NodeIndex> {
|
||||
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<NodeIndex, PublicKey> {
|
||||
self.dealers
|
||||
.iter()
|
||||
.filter_map(|(_, dealer)| {
|
||||
dealer.as_ref().ok().map(|participant| {
|
||||
(
|
||||
participant.assigned_index,
|
||||
*participant.bte_public_key_with_proof.public_key(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
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<DealerDetails>) {
|
||||
self.dealers = BTreeMap::from_iter(
|
||||
dealers
|
||||
.into_iter()
|
||||
.map(|details| (details.address.clone(), DkgParticipant::try_from(details))),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn mark_bad_dealer(&mut self, dealer_addr: &Addr, reason: ComplaintReason) {
|
||||
if let Some((_, value)) = self
|
||||
.dealers
|
||||
.iter_mut()
|
||||
.find(|(addr, _)| *addr == dealer_addr)
|
||||
{
|
||||
*value = Err(reason);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_receiver_index(&mut self, receiver_index: Option<usize>) {
|
||||
self.receiver_index = receiver_index;
|
||||
}
|
||||
|
||||
pub fn set_threshold(&mut self, threshold: Threshold) {
|
||||
self.threshold = Some(threshold);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<Vec<BTreeMap<NodeIndex, (Addr, Dealing)>>, 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<BTreeMap<NodeIndex, (Addr, Dealing)>>,
|
||||
) -> Result<KeyPair, CoconutError> {
|
||||
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::<Result<_, _>>()?;
|
||||
let scalar = combine_shares(
|
||||
shares,
|
||||
&filtered_receivers_by_idx
|
||||
.keys()
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
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(())
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct KeyPair {
|
||||
inner: Arc<RwLock<Option<coconut_interface::KeyPair>>>,
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(&self) -> RwLockReadGuard<'_, Option<nymcoconut::KeyPair>> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<BlindedSignature> {
|
||||
fn blind_sign(request: InternalSignRequest, key_pair: &CoconutKeyPair) -> Result<BlindedSignature> {
|
||||
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<State>,
|
||||
) -> Result<Json<VerificationKeyResponse>> {
|
||||
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")]
|
||||
|
||||
@@ -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<EpochState> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_current_dealers(&self) -> Result<Vec<DealerDetails>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_dealings(&self, idx: usize) -> Result<Vec<ContractDealing>> {
|
||||
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<ExecuteResult> {
|
||||
async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result<ExecuteResult> {
|
||||
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<u64> = 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(),
|
||||
));
|
||||
|
||||
@@ -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<Url> {
|
||||
|
||||
@@ -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<Notify>,
|
||||
_nymd_client: Client<SigningNymdClient>,
|
||||
#[cfg(feature = "coconut")] coconut_keypair: coconut::keypair::KeyPair,
|
||||
) -> Result<Rocket<Ignite>> {
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -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<EpochState> {
|
||||
Ok(self.0.read().await.nymd.get_current_epoch_state().await?)
|
||||
}
|
||||
|
||||
async fn get_self_registered_dealer_details(
|
||||
&self,
|
||||
) -> crate::coconut::error::Result<DealerDetailsResponse> {
|
||||
@@ -317,6 +321,17 @@ where
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn get_current_dealers(&self) -> crate::coconut::error::Result<Vec<DealerDetails>> {
|
||||
Ok(self.0.read().await.get_all_nymd_current_dealers().await?)
|
||||
}
|
||||
|
||||
async fn get_dealings(
|
||||
&self,
|
||||
idx: usize,
|
||||
) -> crate::coconut::error::Result<Vec<ContractDealing>> {
|
||||
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<ExecuteResult, CoconutError> {
|
||||
Ok(self
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.nymd
|
||||
.submit_dealing_commitment(commitment, None)
|
||||
.submit_dealing_bytes(dealing_bytes, None)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user