From 2db1bc8efa4ddb980050215e4dab67d0a1295ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 22 Nov 2022 11:16:02 +0200 Subject: [PATCH] Feature/dkg publish vk (#1747) * Save to disk coconut keypair * Check verification keys of the other signers * Post verification key to chain * Add multisig propose/vote for vks * Execute the proposal * Parse announce address argument * Gateway uses chain data * Network requester uses chain data * Native&socks5 clients use chain data * Credential client signature uses chain data * Remove redundant api endpoints * Undo debugging logging * Fix some tests * Fix clippy * Fix wasm client and contract test * More contract clippy * Update CHANGELOG * Use a bigger expiry period then the testing one --- CHANGELOG.md | 7 + Cargo.lock | 49 ++++-- .../src/client/topology_control.rs | 6 +- clients/client-core/src/config/mod.rs | 12 ++ clients/client-core/src/init.rs | 2 +- clients/credential/src/commands.rs | 21 ++- clients/credential/src/error.rs | 4 + clients/native/src/client/config/template.rs | 7 + clients/native/src/client/mod.rs | 21 ++- clients/native/src/commands/init.rs | 11 +- clients/native/src/commands/mod.rs | 16 +- clients/native/src/commands/run.rs | 11 +- clients/socks5/src/client/config/template.rs | 7 + clients/socks5/src/client/mod.rs | 21 ++- clients/socks5/src/commands/init.rs | 11 +- clients/socks5/src/commands/mod.rs | 14 +- clients/socks5/src/commands/run.rs | 11 +- clients/webassembly/src/gateway_selector.rs | 2 +- .../gateway-client/src/bandwidth.rs | 10 +- .../client-libs/validator-client/Cargo.toml | 3 +- .../validator-client/src/client.rs | 141 ++++++++++++++--- .../client-libs/validator-client/src/lib.rs | 3 +- .../src/nymd/traits/dkg_query_client.rs | 20 +++ .../src/nymd/traits/dkg_signing_client.rs | 29 ++++ .../src/nymd/traits/multisig_query_client.rs | 19 ++- .../nymd/traits/multisig_signing_client.rs | 2 +- .../validator-client/src/validator_api/mod.rs | 32 +--- .../src/validator_api/routes.rs | 2 - .../src/event_attributes.rs | 2 +- .../coconut-dkg/Cargo.toml | 4 +- .../coconut-dkg/src/dealer.rs | 1 + .../coconut-dkg/src/event_attributes.rs | 1 + .../coconut-dkg/src/lib.rs | 1 + .../coconut-dkg/src/msg.rs | 18 ++- .../coconut-dkg/src/types.rs | 8 +- .../coconut-dkg/src/verification_key.rs | 70 +++++++++ .../multisig-contract/Cargo.toml | 7 +- .../multisig-contract/src/msg.rs | 3 +- common/credentials/Cargo.toml | 1 - common/credentials/src/coconut/utils.rs | 86 ++++------- common/crypto/dkg/src/dealing.rs | 26 +++- common/crypto/dkg/tests/integration.rs | 19 ++- common/nymcoconut/Cargo.toml | 4 +- common/nymcoconut/src/lib.rs | 1 + common/nymcoconut/src/scheme/keygen.rs | 50 ++++++ common/nymcoconut/src/scheme/verification.rs | 42 +++++ common/nymcoconut/src/tests/helpers.rs | 31 ++-- contracts/Cargo.lock | 20 ++- contracts/coconut-dkg/src/constants.rs | 2 + .../coconut-dkg/src/dealers/transactions.rs | 2 + .../coconut-dkg/src/dealings/transactions.rs | 6 +- contracts/coconut-dkg/src/error.rs | 7 +- contracts/coconut-dkg/src/lib.rs | 30 +++- contracts/coconut-dkg/src/state/mod.rs | 3 + .../src/verification_key_shares/mod.rs | 6 + .../src/verification_key_shares/queries.rs | 38 +++++ .../src/verification_key_shares/storage.rs | 15 ++ .../verification_key_shares/transactions.rs | 71 +++++++++ contracts/coconut-test/Cargo.toml | 5 +- contracts/coconut-test/src/helpers.rs | 14 ++ .../src/spend_credential_creates_proposal.rs | 3 + .../src/submit_vk_creates_proposal.rs | 142 +++++++++++++++++ contracts/coconut-test/src/tests.rs | 1 + .../multisig/cw3-flex-multisig/Cargo.toml | 16 +- .../cw3-flex-multisig/src/contract.rs | 14 +- .../multisig/cw3-flex-multisig/src/state.rs | 1 + contracts/multisig/cw4-group/Cargo.toml | 12 +- .../websocket/connection_handler/coconut.rs | 39 ++--- gateway/src/node/mod.rs | 59 ++++---- validator-api/Cargo.toml | 6 +- validator-api/src/coconut/client.rs | 17 ++- validator-api/src/coconut/comm.rs | 19 ++- validator-api/src/coconut/dkg/client.rs | 76 +++++++++- validator-api/src/coconut/dkg/complaints.rs | 35 ----- validator-api/src/coconut/dkg/controller.rs | 50 ++++-- validator-api/src/coconut/dkg/dealing.rs | 3 +- validator-api/src/coconut/dkg/public_key.rs | 6 +- validator-api/src/coconut/dkg/state.rs | 84 +++++++++- .../src/coconut/dkg/verification_key.rs | 143 +++++++++++++++--- validator-api/src/coconut/error.rs | 6 + validator-api/src/coconut/mod.rs | 25 +-- validator-api/src/coconut/tests.rs | 141 ++++------------- validator-api/src/config/mod.rs | 86 +++++++---- validator-api/src/config/template.rs | 21 ++- validator-api/src/main.rs | 55 +++---- validator-api/src/network_monitor/mod.rs | 22 ++- .../src/network_monitor/monitor/sender.rs | 11 -- validator-api/src/nymd_client.rs | 45 +++++- 88 files changed, 1623 insertions(+), 603 deletions(-) create mode 100644 common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs create mode 100644 contracts/coconut-dkg/src/verification_key_shares/mod.rs create mode 100644 contracts/coconut-dkg/src/verification_key_shares/queries.rs create mode 100644 contracts/coconut-dkg/src/verification_key_shares/storage.rs create mode 100644 contracts/coconut-dkg/src/verification_key_shares/transactions.rs create mode 100644 contracts/coconut-test/src/submit_vk_creates_proposal.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cd8056355..de700926d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,20 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// - binaries: add `-c` shortform for `--config-env-file` - websocket-requests: add server response signalling current packet queue length in the client +- contracts: DKG contract that handles coconut key generation ([#1678][#1708][#1747]) +- validator-api: generate coconut keys interactively, using DKG and multisig contracts ([#1678][#1708][#1747]) ### Changed - clients: add concept of transmission lanes to better handle multiple data streams ([#1720]) +- clients,validator-api: take coconut signers from the chain instead of specifying them via CLI ([#1747]) +- multisig contract: add DKG contract to the list of addresses that can create proposals ([#1747]) - socks5-client: wait closing inbound connection until data is sent, and throttle incoming data in general ([#1783]) +[#1678]: https://github.com/nymtech/nym/pull/1678 +[#1708]: https://github.com/nymtech/nym/pull/1708 [#1720]: https://github.com/nymtech/nym/pull/1720 +[#1747]: https://github.com/nymtech/nym/pull/1747 [#1783]: https://github.com/nymtech/nym/pull/1783 diff --git a/Cargo.lock b/Cargo.lock index 9012b80f10..4303004d95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,6 +642,8 @@ version = "0.1.0" dependencies = [ "contracts-common", "cosmwasm-std", + "cw-utils", + "multisig-contract-common", "schemars", "serde", ] @@ -963,7 +965,6 @@ dependencies = [ "crypto", "rand 0.7.3", "thiserror", - "url", "validator-api-requests", "validator-client", ] @@ -1242,9 +1243,9 @@ dependencies = [ [[package]] name = "cw-utils" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" dependencies = [ "cosmwasm-std", "schemars", @@ -1253,10 +1254,22 @@ dependencies = [ ] [[package]] -name = "cw3" -version = "0.13.2" +name = "cw2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + +[[package]] +name = "cw3" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b" dependencies = [ "cosmwasm-std", "cw-utils", @@ -1265,10 +1278,26 @@ dependencies = [ ] [[package]] -name = "cw4" -version = "0.13.2" +name = "cw3-fixed-multisig" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52" +checksum = "df54aa54c13f405ec4ab36b6217538bc957d439eee58f89312db05a79caf6706" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw2", + "cw3", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw4" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd" dependencies = [ "cosmwasm-std", "cw-storage-plus", @@ -2952,6 +2981,7 @@ dependencies = [ "cosmwasm-std", "cw-utils", "cw3", + "cw3-fixed-multisig", "cw4", "schemars", "serde", @@ -3485,6 +3515,7 @@ dependencies = [ "getrandom 0.2.6", "group 0.11.0", "itertools", + "pemstore", "rand 0.8.5", "rand_chacha 0.3.1", "serde", diff --git a/clients/client-core/src/client/topology_control.rs b/clients/client-core/src/client/topology_control.rs index 3b0fc4afc3..afedc7ad61 100644 --- a/clients/client-core/src/client/topology_control.rs +++ b/clients/client-core/src/client/topology_control.rs @@ -138,7 +138,7 @@ impl TopologyRefresherConfig { } pub struct TopologyRefresher { - validator_client: validator_client::ApiClient, + validator_client: validator_client::client::ApiClient, client_version: String, validator_api_urls: Vec, @@ -154,7 +154,9 @@ impl TopologyRefresher { cfg.validator_api_urls.shuffle(&mut thread_rng()); TopologyRefresher { - validator_client: validator_client::ApiClient::new(cfg.validator_api_urls[0].clone()), + validator_client: validator_client::client::ApiClient::new( + cfg.validator_api_urls[0].clone(), + ), client_version: cfg.client_version, validator_api_urls: cfg.validator_api_urls, topology_accessor, diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index 678b70ef54..4a3d1c1273 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -125,6 +125,10 @@ impl Config { self.client.gateway_endpoint.gateway_id = id.into(); } + pub fn set_custom_validators(&mut self, validator_urls: Vec) { + self.client.validator_urls = validator_urls; + } + pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec) { self.client.validator_api_urls = validator_api_urls; } @@ -179,6 +183,10 @@ impl Config { self.client.ack_key_file.clone() } + pub fn get_validator_endpoints(&self) -> Vec { + self.client.validator_urls.clone() + } + pub fn get_validator_api_endpoints(&self) -> Vec { self.client.validator_api_urls.clone() } @@ -306,6 +314,9 @@ pub struct Client { #[serde(default)] disabled_credentials_mode: bool, + /// Addresses to nymd validators via which the client can communicate with the chain. + validator_urls: Vec, + /// Addresses to APIs running on validator from which the client gets the view of the network. validator_api_urls: Vec, @@ -354,6 +365,7 @@ impl Default for Client { version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), disabled_credentials_mode: true, + validator_urls: vec![], validator_api_urls: vec![], private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), diff --git a/clients/client-core/src/init.rs b/clients/client-core/src/init.rs index 98cc42df64..be9758d363 100644 --- a/clients/client-core/src/init.rs +++ b/clients/client-core/src/init.rs @@ -31,7 +31,7 @@ pub async fn query_gateway_details( let validator_api = validator_servers .choose(&mut thread_rng()) .ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?; - let validator_client = validator_client::ApiClient::new(validator_api.clone()); + let validator_client = validator_client::client::ApiClient::new(validator_api.clone()); log::trace!("Fetching list of gateways from: {}", validator_api); let gateways = validator_client.get_cached_gateways().await?; diff --git a/clients/credential/src/commands.rs b/clients/credential/src/commands.rs index c281332810..7f9d3ae2e1 100644 --- a/clients/credential/src/commands.rs +++ b/clients/credential/src/commands.rs @@ -14,8 +14,9 @@ use credential_storage::PersistentStorage; use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES}; use credentials::coconut::utils::obtain_aggregate_signature; use crypto::asymmetric::{encryption, identity}; -use network_defaults::VOUCHER_INFO; +use network_defaults::{NymNetworkDetails, VOUCHER_INFO}; use validator_client::nymd::tx::Hash; +use validator_client::{CoconutApiClient, Config}; use crate::client::Client; use crate::error::{CredentialClientError, Result}; @@ -107,10 +108,9 @@ pub(crate) struct GetCredential { /// The hash of a successful deposit transaction #[clap(long)] tx_hash: String, - /// The URLs to the validator-api endpoints the are run as coconut signer authorities, separated - /// by comma (,) + /// The nymd URL that should be used #[clap(long)] - signer_authorities: String, + nymd_url: String, /// If we want to get the signature without attaching a blind sign request; it is expected that /// there is already a signature stored on the signer #[clap(long, parse(from_flag))] @@ -124,7 +124,10 @@ impl Execute for GetCredential { .get::(&self.tx_hash) .ok_or(CredentialClientError::NoDeposit)?; - let urls = config::parse_validators(&self.signer_authorities); + let network_details = NymNetworkDetails::new_from_env(); + let config = Config::try_from_nym_network_details(&network_details)?; + let client = validator_client::Client::new_query(config)?; + let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client).await?; let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap(); let bandwidth_credential_attributes = if self.__no_request { @@ -178,8 +181,12 @@ impl Execute for GetCredential { )?); db.set(&self.tx_hash, &state).unwrap(); - let signature = - obtain_aggregate_signature(¶ms, &bandwidth_credential_attributes, &urls).await?; + let signature = obtain_aggregate_signature( + ¶ms, + &bandwidth_credential_attributes, + &coconut_api_clients, + ) + .await?; shared_storage .insert_coconut_credential( state.amount.to_string(), diff --git a/clients/credential/src/error.rs b/clients/credential/src/error.rs index 830e4d9e16..08bd0001e9 100644 --- a/clients/credential/src/error.rs +++ b/clients/credential/src/error.rs @@ -8,6 +8,7 @@ use credentials::error::Error as CredentialError; use crypto::asymmetric::encryption::KeyRecoveryError; use crypto::asymmetric::identity::Ed25519RecoveryError; use validator_client::nymd::error::NymdError; +use validator_client::ValidatorClientError; pub type Result = std::result::Result; @@ -16,6 +17,9 @@ pub enum CredentialClientError { #[error("Nymd error: {0}")] Nymd(#[from] NymdError), + #[error("Validator client error: {0}")] + ValidatorClientError(#[from] ValidatorClientError), + #[error("Credential error: {0}")] Credential(#[from] CredentialError), diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index ca25e6c794..c327d286ff 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -23,6 +23,13 @@ id = '{{ client.id }}' # to claim bandwidth without presenting bandwidth credentials. disabled_credentials_mode = {{ client.disabled_credentials_mode }} +# Addresses to nymd validators via which the client can communicate with the chain. +validator_urls = [ + {{#each client.validator_urls }} + '{{this}}', + {{/each}} +] + # Addresses to APIs running on validator from which the client gets the view of the network. validator_api_urls = [ {{#each client.validator_api_urls }} diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index dba7d6b47d..357293d17e 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -201,11 +201,22 @@ impl NymClient { .expect("provided gateway id is invalid!"); #[cfg(feature = "coconut")] - let bandwidth_controller = BandwidthController::new( - credential_storage::initialise_storage(self.config.get_base().get_database_path()) - .await, - self.config.get_base().get_validator_api_endpoints(), - ); + let bandwidth_controller = { + let details = network_defaults::NymNetworkDetails::new_from_env(); + let client_config = validator_client::Config::try_from_nym_network_details(&details) + .expect("failed to construct validator client config"); + let client = validator_client::Client::new_query(client_config) + .expect("Could not construct query client"); + let coconut_api_clients = + validator_client::CoconutApiClient::all_coconut_api_clients(&client) + .await + .expect("Could not query api clients"); + BandwidthController::new( + credential_storage::initialise_storage(self.config.get_base().get_database_path()) + .await, + coconut_api_clients, + ) + }; #[cfg(not(feature = "coconut"))] let bandwidth_controller = BandwidthController::new( credential_storage::initialise_storage(self.config.get_base().get_database_path()) diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 208a026959..6a38770eec 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -25,9 +25,13 @@ pub(crate) struct Init { #[clap(long)] force_register_gateway: bool, - /// Comma separated list of rest endpoints of the validators + /// Comma separated list of rest endpoints of the nymd validators #[clap(long)] - validators: Option, + nymd_validators: Option, + + /// Comma separated list of rest endpoints of the API validators + #[clap(long)] + api_validators: Option, /// Whether to not start the websocket #[clap(long)] @@ -52,7 +56,8 @@ pub(crate) struct Init { impl From for OverrideConfig { fn from(init_config: Init) -> Self { OverrideConfig { - validators: init_config.validators, + nymd_validators: init_config.nymd_validators, + api_validators: init_config.api_validators, disable_socket: init_config.disable_socket, port: init_config.port, fastmode: init_config.fastmode, diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 08323a6454..648a1d6002 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -75,7 +75,8 @@ pub(crate) enum Commands { // Configuration that can be overridden. pub(crate) struct OverrideConfig { - validators: Option, + nymd_validators: Option, + api_validators: Option, disable_socket: bool, port: Option, fastmode: bool, @@ -98,7 +99,18 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> { } pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config { - if let Some(raw_validators) = args.validators { + if let Some(raw_validators) = args.nymd_validators { + config + .get_base_mut() + .set_custom_validators(config::parse_validators(&raw_validators)); + } else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() { + let raw_validators = std::env::var(network_defaults::var_names::NYMD_VALIDATOR) + .expect("nymd validator not set"); + config + .get_base_mut() + .set_custom_validators(config::parse_validators(&raw_validators)); + } + if let Some(raw_validators) = args.api_validators { config .get_base_mut() .set_custom_validator_apis(config::parse_validators(&raw_validators)); diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index 7332f81448..d5c892fadc 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -18,9 +18,13 @@ pub(crate) struct Run { #[clap(long)] id: String, - /// Comma separated list of rest endpoints of the validators + /// Comma separated list of rest endpoints of the nymd validators #[clap(long)] - validators: Option, + nymd_validators: Option, + + /// Comma separated list of rest endpoints of the API validators + #[clap(long)] + api_validators: Option, /// Id of the gateway we want to connect to. If overridden, it is user's responsibility to /// ensure prior registration happened @@ -45,7 +49,8 @@ pub(crate) struct Run { impl From for OverrideConfig { fn from(run_config: Run) -> Self { OverrideConfig { - validators: run_config.validators, + nymd_validators: run_config.nymd_validators, + api_validators: run_config.api_validators, disable_socket: run_config.disable_socket, port: run_config.port, fastmode: false, diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 4168d44e85..11d3185f56 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -23,6 +23,13 @@ id = '{{ client.id }}' # to claim bandwidth without presenting bandwidth credentials. disabled_credentials_mode = {{ client.disabled_credentials_mode }} +# Addresses to nymd validators via which the client can communicate with the chain. +validator_urls = [ + {{#each client.validator_urls }} + '{{this}}', + {{/each}} +] + # Addresses to APIs running on validator from which the client gets the view of the network. validator_api_urls = [ {{#each client.validator_api_urls }} diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 863f2252c2..ef07360e2b 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -199,11 +199,22 @@ impl NymClient { .expect("provided gateway id is invalid!"); #[cfg(feature = "coconut")] - let bandwidth_controller = BandwidthController::new( - credential_storage::initialise_storage(self.config.get_base().get_database_path()) - .await, - self.config.get_base().get_validator_api_endpoints(), - ); + let bandwidth_controller = { + let details = network_defaults::NymNetworkDetails::new_from_env(); + let client_config = validator_client::Config::try_from_nym_network_details(&details) + .expect("failed to construct validator client config"); + let client = validator_client::Client::new_query(client_config) + .expect("Could not construct query client"); + let coconut_api_clients = + validator_client::CoconutApiClient::all_coconut_api_clients(&client) + .await + .expect("Could not query api clients"); + BandwidthController::new( + credential_storage::initialise_storage(self.config.get_base().get_database_path()) + .await, + coconut_api_clients, + ) + }; #[cfg(not(feature = "coconut"))] let bandwidth_controller = BandwidthController::new( credential_storage::initialise_storage(self.config.get_base().get_database_path()) diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index fb93e4e30c..58b10002c4 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -29,9 +29,13 @@ pub(crate) struct Init { #[clap(long)] force_register_gateway: bool, - /// Comma separated list of rest endpoints of the validators + /// Comma separated list of rest endpoints of the nymd validators #[clap(long)] - validators: Option, + nymd_validators: Option, + + /// Comma separated list of rest endpoints of the API validators + #[clap(long)] + api_validators: Option, /// Port for the socket to listen on in all subsequent runs #[clap(short, long)] @@ -52,7 +56,8 @@ pub(crate) struct Init { impl From for OverrideConfig { fn from(init_config: Init) -> Self { OverrideConfig { - validators: init_config.validators, + nymd_validators: init_config.nymd_validators, + api_validators: init_config.api_validators, port: init_config.port, fastmode: init_config.fastmode, #[cfg(feature = "coconut")] diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 192e905b62..b069c13542 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -78,7 +78,8 @@ pub(crate) enum Commands { // Configuration that can be overridden. pub(crate) struct OverrideConfig { - validators: Option, + nymd_validators: Option, + api_validators: Option, port: Option, fastmode: bool, @@ -100,7 +101,16 @@ pub(crate) async fn execute(args: &Cli) -> Result<(), Socks5ClientError> { } pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config { - if let Some(raw_validators) = args.validators { + if let Some(raw_validators) = args.nymd_validators { + config + .get_base_mut() + .set_custom_validators(parse_validators(&raw_validators)); + } else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::NYMD_VALIDATOR) { + config + .get_base_mut() + .set_custom_validators(parse_validators(&raw_validators)); + } + if let Some(raw_validators) = args.api_validators { config .get_base_mut() .set_custom_validator_apis(parse_validators(&raw_validators)); diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index af7a22b078..406ae72d94 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -31,9 +31,13 @@ pub(crate) struct Run { #[clap(long)] gateway: Option, - /// Comma separated list of rest endpoints of the validators + /// Comma separated list of rest endpoints of the nymd validators #[clap(long)] - validators: Option, + nymd_validators: Option, + + /// Comma separated list of rest endpoints of the API validators + #[clap(long)] + api_validators: Option, /// Port for the socket to listen on #[clap(short, long)] @@ -49,7 +53,8 @@ pub(crate) struct Run { impl From for OverrideConfig { fn from(run_config: Run) -> Self { OverrideConfig { - validators: run_config.validators, + nymd_validators: run_config.nymd_validators, + api_validators: run_config.api_validators, port: run_config.port, fastmode: false, diff --git a/clients/webassembly/src/gateway_selector.rs b/clients/webassembly/src/gateway_selector.rs index f20ca90fbb..7e15252b8a 100644 --- a/clients/webassembly/src/gateway_selector.rs +++ b/clients/webassembly/src/gateway_selector.rs @@ -6,7 +6,7 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub async fn get_gateway(api_server: String, preferred: Option) -> GatewayEndpoint { - let validator_client = validator_client::ApiClient::new(api_server.parse().unwrap()); + let validator_client = validator_client::client::ApiClient::new(api_server.parse().unwrap()); let gateways = match validator_client.get_cached_gateways().await { Err(err) => panic!("failed to obtain list of all gateways - {}", err), diff --git a/common/client-libs/gateway-client/src/bandwidth.rs b/common/client-libs/gateway-client/src/bandwidth.rs index 77916876f1..6f3b7722dc 100644 --- a/common/client-libs/gateway-client/src/bandwidth.rs +++ b/common/client-libs/gateway-client/src/bandwidth.rs @@ -17,6 +17,8 @@ use credential_storage::error::StorageError; #[cfg(feature = "coconut")] use std::str::FromStr; #[cfg(feature = "coconut")] +use validator_client::client::CoconutApiClient; +#[cfg(feature = "coconut")] use { coconut_interface::Base58, credentials::coconut::{ @@ -29,7 +31,7 @@ pub struct BandwidthController { #[allow(dead_code)] storage: St, #[cfg(feature = "coconut")] - validator_endpoints: Vec, + coconut_api_clients: Vec, } impl BandwidthController @@ -37,10 +39,10 @@ where St: Storage + Clone + 'static, { #[cfg(feature = "coconut")] - pub fn new(storage: St, validator_endpoints: Vec) -> Self { + pub fn new(storage: St, coconut_api_clients: Vec) -> Self { BandwidthController { storage, - validator_endpoints, + coconut_api_clients, } } @@ -53,7 +55,7 @@ where pub async fn prepare_coconut_credential( &self, ) -> Result { - let verification_key = obtain_aggregate_verification_key(&self.validator_endpoints).await?; + let verification_key = obtain_aggregate_verification_key(&self.coconut_api_clients).await?; let bandwidth_credential = self.storage.get_next_coconut_credential().await?; let voucher_value = u64::from_str(&bandwidth_credential.voucher_value) .map_err(|_| StorageError::InconsistentData)?; diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index bd9bb6ef80..ec842828e7 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -10,7 +10,6 @@ rust-version = "1.56" [dependencies] base64 = "0.13" colored = "2.0" -cw3 = "0.13.1" coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" } contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" } @@ -39,6 +38,7 @@ async-trait = { version = "0.1.51", optional = true } bip39 = { version = "1", features = ["rand"], optional = true } config = { path = "../../config", optional = true } cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true} +cw3 = { version = "0.13.4", optional = true } prost = { version = "0.10", default-features = false, optional = true } flate2 = { version = "1.0.20", optional = true } sha2 = { version = "0.9.5", optional = true } @@ -55,6 +55,7 @@ nymd-client = [ "bip39", "config", "cosmrs", + "cw3", "prost", "flate2", "sha2", diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 59e9841d78..8ddce33a4f 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -2,15 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{validator_api, ValidatorClientError}; -use coconut_dkg_common::dealer::ContractDealing; -use coconut_dkg_common::types::DealerDetails; +use coconut_dkg_common::types::NodeIndex; +#[cfg(feature = "nymd-client")] +use coconut_dkg_common::{ + dealer::ContractDealing, types::DealerDetails, verification_key::ContractVKShare, +}; +#[cfg(feature = "nymd-client")] +use coconut_interface::Base58; +use coconut_interface::VerificationKey; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::MixId; use mixnet_contract_common::{GatewayBond, IdentityKeyRef}; -use url::Url; +#[cfg(feature = "nymd-client")] +use std::str::FromStr; use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, - VerifyCredentialBody, VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use validator_api_requests::models::{ GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, @@ -18,10 +24,12 @@ use validator_api_requests::models::{ }; #[cfg(feature = "nymd-client")] -use crate::nymd::traits::{DkgQueryClient, MixnetQueryClient}; +use crate::nymd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient}; #[cfg(feature = "nymd-client")] use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient}; #[cfg(feature = "nymd-client")] +use cw3::ProposalResponse; +#[cfg(feature = "nymd-client")] use mixnet_contract_common::{ mixnode::MixNodeBond, pending_events::{PendingEpochEvent, PendingIntervalEvent}, @@ -29,6 +37,7 @@ use mixnet_contract_common::{ }; #[cfg(feature = "nymd-client")] use network_defaults::NymNetworkDetails; +use url::Url; #[cfg(feature = "nymd-client")] use validator_api_requests::models::MixNodeBondAnnotated; @@ -46,6 +55,8 @@ pub struct Config { mixnode_delegations_page_limit: Option, rewarded_set_page_limit: Option, dealers_page_limit: Option, + verification_key_page_limit: Option, + proposals_page_limit: Option, } #[cfg(feature = "nymd-client")] @@ -76,6 +87,8 @@ impl Config { mixnode_delegations_page_limit: None, rewarded_set_page_limit: None, dealers_page_limit: None, + verification_key_page_limit: None, + proposals_page_limit: None, }) } @@ -124,6 +137,8 @@ pub struct Client { mixnode_delegations_page_limit: Option, rewarded_set_page_limit: Option, dealers_page_limit: Option, + verification_key_page_limit: Option, + proposals_page_limit: Option, // ideally they would have been read-only, but unfortunately rust doesn't have such features pub validator_api: validator_api::Client, @@ -151,6 +166,8 @@ impl Client { mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, rewarded_set_page_limit: config.rewarded_set_page_limit, dealers_page_limit: config.dealers_page_limit, + verification_key_page_limit: config.verification_key_page_limit, + proposals_page_limit: config.proposals_page_limit, validator_api: validator_api_client, nymd: nymd_client, }) @@ -185,6 +202,8 @@ impl Client { mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, rewarded_set_page_limit: config.rewarded_set_page_limit, dealers_page_limit: config.dealers_page_limit, + verification_key_page_limit: config.verification_key_page_limit, + proposals_page_limit: config.proposals_page_limit, validator_api: validator_api_client, nymd: nymd_client, }) @@ -602,6 +621,59 @@ impl Client { Ok(dealings) } + + pub async fn get_all_nymd_verification_key_shares( + &self, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync + Send, + { + let mut shares = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_vk_shares_paged(start_after.take(), self.verification_key_page_limit) + .await?; + shares.append(&mut paged_response.shares); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res.into_string()) + } else { + break; + } + } + + Ok(shares) + } + + pub async fn get_all_nymd_proposals( + &self, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync + Send, + { + let mut proposals = Vec::new(); + let mut start_after = None; + + loop { + let mut paged_response = self + .nymd + .list_proposals(start_after.take(), self.proposals_page_limit) + .await?; + + let last_id = paged_response.proposals.last().map(|prop| prop.id); + proposals.append(&mut paged_response.proposals); + + if let Some(start_after_res) = last_id { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(proposals) + } } // validator-api wrappers @@ -655,14 +727,53 @@ impl Client { ) -> Result { Ok(self.validator_api.blind_sign(request_body).await?) } +} - pub async fn get_coconut_verification_key( - &self, - ) -> Result { - Ok(self.validator_api.get_coconut_verification_key().await?) +#[derive(Clone)] +pub struct CoconutApiClient { + pub api_client: ApiClient, + pub verification_key: VerificationKey, + pub node_id: NodeIndex, + #[cfg(feature = "nymd-client")] + pub cosmos_address: cosmrs::AccountId, +} + +#[cfg(feature = "nymd-client")] +impl CoconutApiClient { + pub async fn all_coconut_api_clients( + nymd_client: &Client, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync + Send, + { + Ok(nymd_client + .get_all_nymd_verification_key_shares() + .await? + .into_iter() + .filter_map(Self::try_from) + .collect()) + } + + fn try_from(share: ContractVKShare) -> Option { + if share.verified { + if let Ok(url_address) = Url::parse(&share.announce_address) { + if let Ok(verification_key) = VerificationKey::try_from_bs58(&share.share) { + if let Ok(cosmos_address) = cosmrs::AccountId::from_str(share.owner.as_str()) { + return Some(CoconutApiClient { + api_client: ApiClient::new(url_address), + verification_key, + node_id: share.node_index, + cosmos_address, + }); + } + } + } + } + None } } +#[derive(Clone)] pub struct ApiClient { pub validator_api: validator_api::Client, // TODO: perhaps if we really need it at some (currently I don't see any reasons for it) @@ -768,16 +879,6 @@ impl ApiClient { .await?) } - pub async fn get_coconut_verification_key( - &self, - ) -> Result { - Ok(self.validator_api.get_coconut_verification_key().await?) - } - - pub async fn get_cosmos_address(&self) -> Result { - Ok(self.validator_api.get_cosmos_address().await?) - } - pub async fn verify_bandwidth_credential( &self, request_body: &VerifyCredentialBody, diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index 3e11d3fcb6..2a5e010338 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -9,7 +9,8 @@ mod error; pub mod nymd; pub mod validator_api; -pub use crate::client::ApiClient; +#[cfg(feature = "nymd-client")] +pub use crate::client::{ApiClient, CoconutApiClient}; pub use crate::error::ValidatorClientError; pub use validator_api_requests::*; diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs index 930346fe17..5e4d5a7388 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs @@ -9,6 +9,7 @@ use coconut_dkg_common::dealer::{ }; use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; use coconut_dkg_common::types::{EpochState, MinimumDepositResponse}; +use coconut_dkg_common::verification_key::PagedVKSharesResponse; use cosmrs::AccountId; #[async_trait] @@ -36,6 +37,11 @@ pub trait DkgQueryClient { start_after: Option, page_limit: Option, ) -> Result; + async fn get_vk_shares_paged( + &self, + start_after: Option, + page_limit: Option, + ) -> Result; } #[async_trait] @@ -111,4 +117,18 @@ where .query_contract_smart(self.coconut_dkg_contract_address(), &request) .await } + + async fn get_vk_shares_paged( + &self, + start_after: Option, + page_limit: Option, + ) -> Result { + let request = DkgQueryMsg::GetVerificationKeys { + limit: page_limit, + start_after, + }; + self.client + .query_contract_smart(self.coconut_dkg_contract_address(), &request) + .await + } } diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs index f93cbac41c..d55664f0d3 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs @@ -8,6 +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 coconut_dkg_common::verification_key::VerificationKeyShare; use contracts_common::dealings::ContractSafeBytes; #[async_trait] @@ -15,6 +16,7 @@ pub trait DkgSigningClient { async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, + announce_address: String, fee: Option, ) -> Result; @@ -23,6 +25,12 @@ pub trait DkgSigningClient { commitment: ContractSafeBytes, fee: Option, ) -> Result; + + async fn submit_verification_key_share( + &self, + share: VerificationKeyShare, + fee: Option, + ) -> Result; } #[async_trait] @@ -33,10 +41,12 @@ where async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, + announce_address: String, fee: Option, ) -> Result { let req = DkgExecuteMsg::RegisterDealer { bte_key_with_proof: bte_key, + announce_address, }; let deposit = self.get_deposit_amount().await?; @@ -70,4 +80,23 @@ where ) .await } + + async fn submit_verification_key_share( + &self, + share: VerificationKeyShare, + fee: Option, + ) -> Result { + let req = DkgExecuteMsg::CommitVerificationKeyShare { share }; + + self.client + .execute( + self.address(), + self.coconut_dkg_contract_address(), + &req, + fee.unwrap_or_default(), + "verification key share commitment", + vec![], + ) + .await + } } diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs index 9166897b18..89643e8136 100644 --- a/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs @@ -4,13 +4,19 @@ use crate::nymd::error::NymdError; use crate::nymd::{CosmWasmClient, NymdClient}; -use multisig_contract_common::msg::{ProposalResponse, QueryMsg}; +use cw3::{ProposalListResponse, ProposalResponse}; +use multisig_contract_common::msg::QueryMsg; use async_trait::async_trait; #[async_trait] pub trait MultisigQueryClient { async fn get_proposal(&self, proposal_id: u64) -> Result; + async fn list_proposals( + &self, + start_after: Option, + limit: Option, + ) -> Result; } #[async_trait] @@ -21,4 +27,15 @@ impl MultisigQueryClient for NymdClient { .query_contract_smart(self.multisig_contract_address(), &request) .await } + + async fn list_proposals( + &self, + start_after: Option, + limit: Option, + ) -> Result { + let request = QueryMsg::ListProposals { start_after, limit }; + self.client + .query_contract_smart(self.multisig_contract_address(), &request) + .await + } } diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs index 46bb118ef0..a715c85075 100644 --- a/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs @@ -7,11 +7,11 @@ use crate::nymd::error::NymdError; use crate::nymd::{Fee, NymdClient}; use coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg; +use cw3::Vote; use multisig_contract_common::msg::ExecuteMsg; use async_trait::async_trait; use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg}; -use cw3::Vote; #[async_trait] pub trait MultisigSigningClient { diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 0f4ebc60db..4a7b7af3cd 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -9,8 +9,7 @@ use reqwest::Response; use serde::{Deserialize, Serialize}; use url::Url; use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, - VerifyCredentialBody, VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use validator_api_requests::models::{ GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, @@ -27,6 +26,7 @@ type Params<'a, K, V> = &'a [(K, V)]; const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[]; +#[derive(Clone)] pub struct Client { url: Url, reqwest_client: reqwest::Client, @@ -443,34 +443,6 @@ impl Client { .await } - pub async fn get_coconut_verification_key( - &self, - ) -> Result { - self.query_validator_api( - &[ - routes::API_VERSION, - routes::COCONUT_ROUTES, - routes::BANDWIDTH, - routes::COCONUT_VERIFICATION_KEY, - ], - NO_PARAMS, - ) - .await - } - - pub async fn get_cosmos_address(&self) -> Result { - self.query_validator_api( - &[ - routes::API_VERSION, - routes::COCONUT_ROUTES, - routes::BANDWIDTH, - routes::COCONUT_COSMOS_ADDRESS, - ], - NO_PARAMS, - ) - .await - } - pub async fn verify_bandwidth_credential( &self, request_body: &VerifyCredentialBody, diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index 64aa1b8cd9..66a822b4fd 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -15,8 +15,6 @@ pub const BANDWIDTH: &str = "bandwidth"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-credential"; -pub const COCONUT_VERIFICATION_KEY: &str = "verification-key"; -pub const COCONUT_COSMOS_ADDRESS: &str = "cosmos-address"; pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential"; pub const STATUS_ROUTES: &str = "status"; diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/event_attributes.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/event_attributes.rs index 431caa226c..80f5daf68a 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/event_attributes.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/event_attributes.rs @@ -1,4 +1,4 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub const PROPOSAL_ID: &str = "proposal_id"; +pub const BANDWIDTH_PROPOSAL_ID: &str = "proposal_id"; diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml index 1ba84dadbc..cffca1ac3d 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml @@ -7,7 +7,9 @@ edition = "2021" [dependencies] cosmwasm-std = "1.0.0" +cw-utils = "0.13.4" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -contracts-common = { path = "../contracts-common" } \ No newline at end of file +contracts-common = { path = "../contracts-common" } +multisig-contract-common = { path = "../multisig-contract" } \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs index 46f48de623..0b60fec2e8 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; pub struct DealerDetails { pub address: Addr, pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, + pub announce_address: String, pub assigned_index: NodeIndex, pub deposit: Coin, } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/event_attributes.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/event_attributes.rs index ed28d113ce..28166677af 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/event_attributes.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/event_attributes.rs @@ -2,3 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 pub const NODE_INDEX: &str = "node_index"; +pub const DKG_PROPOSAL_ID: &str = "proposal_id"; diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/lib.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/lib.rs index 993fc3ae41..545451eeb6 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/lib.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/lib.rs @@ -2,3 +2,4 @@ pub mod dealer; pub mod event_attributes; pub mod msg; pub mod types; +pub mod verification_key; diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index 6ed6f4618a..dd99dde2c0 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -2,14 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof}; +use crate::verification_key::VerificationKeyShare; +use cosmwasm_std::Addr; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct InstantiateMsg { pub group_addr: String, - pub mix_denom: String, + pub multisig_addr: String, pub admin: String, + pub mix_denom: String, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] @@ -17,12 +20,21 @@ pub struct InstantiateMsg { pub enum ExecuteMsg { RegisterDealer { bte_key_with_proof: EncodedBTEPublicKeyWithProof, + announce_address: String, }, CommitDealing { dealing_bytes: ContractSafeBytes, }, + CommitVerificationKeyShare { + share: VerificationKeyShare, + }, + + VerifyVerificationKeyShare { + owner: Addr, + }, + AdvanceEpochState {}, // DEBUG ONLY TXs. THEY SHALL BE REMOVED BEFORE FINALISING THE CODE @@ -53,6 +65,10 @@ pub enum QueryMsg { limit: Option, start_after: Option, }, + GetVerificationKeys { + limit: Option, + start_after: Option, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index 6c64d41aa3..d2696f2941 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -33,6 +33,8 @@ pub enum EpochState { PublicKeySubmission, DealingExchange, VerificationKeySubmission, + VerificationKeyValidation, + VerificationKeyFinalization, InProgress, } @@ -48,6 +50,8 @@ impl Display for EpochState { EpochState::PublicKeySubmission => write!(f, "PublicKeySubmission"), EpochState::DealingExchange => write!(f, "DealingExchange"), EpochState::VerificationKeySubmission => write!(f, "VerificationKeySubmission"), + EpochState::VerificationKeyValidation => write!(f, "VerificationKeyValidation"), + EpochState::VerificationKeyFinalization => write!(f, "VerificationKeyFinalization"), EpochState::InProgress => write!(f, "InProgress"), } } @@ -58,7 +62,9 @@ impl EpochState { match self { EpochState::PublicKeySubmission => Some(EpochState::DealingExchange), EpochState::DealingExchange => Some(EpochState::VerificationKeySubmission), - EpochState::VerificationKeySubmission => Some(EpochState::InProgress), + EpochState::VerificationKeySubmission => Some(EpochState::VerificationKeyValidation), + EpochState::VerificationKeyValidation => Some(EpochState::VerificationKeyFinalization), + EpochState::VerificationKeyFinalization => Some(EpochState::InProgress), EpochState::InProgress => None, } } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs new file mode 100644 index 0000000000..cfc3ffae03 --- /dev/null +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs @@ -0,0 +1,70 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::msg::ExecuteMsg; +use crate::types::NodeIndex; +use cosmwasm_std::{from_binary, to_binary, Addr, CosmosMsg, StdResult, Timestamp, WasmMsg}; +use cw_utils::Expiration; +use multisig_contract_common::msg::ExecuteMsg as MultisigExecuteMsg; +use serde::{Deserialize, Serialize}; + +pub type VerificationKeyShare = String; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct ContractVKShare { + pub share: VerificationKeyShare, + pub announce_address: String, + pub node_index: NodeIndex, + pub owner: Addr, + pub verified: bool, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub struct PagedVKSharesResponse { + pub shares: Vec, + pub per_page: usize, + pub start_next_after: Option, +} + +pub fn to_cosmos_msg( + owner: Addr, + coconut_dkg_addr: String, + multisig_addr: String, + expiration_time: Timestamp, +) -> StdResult { + let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner }; + let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: coconut_dkg_addr, + msg: to_binary(&verify_vk_share_req)?, + funds: vec![], + }); + let req = MultisigExecuteMsg::Propose { + title: String::from("Verify VK share, as ordered by Coconut DKG Contract"), + description: String::new(), + msgs: vec![verify_vk_share_msg], + latest: Some(Expiration::AtTime(expiration_time)), + }; + let msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: multisig_addr, + msg: to_binary(&req)?, + funds: vec![], + }); + + Ok(msg) +} + +pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option { + if let Some(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: _, + msg, + funds: _, + })) = msgs.get(0) + { + if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner }) = from_binary::(msg) + { + return Some(owner); + } + } + None +} diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml index cb3c2c0cb1..f493cb6635 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -6,9 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cw-utils = { version = "0.13.1" } -cw3 = { version = "0.13.1" } -cw4 = { version = "0.13.1" } +cw-utils = { version = "0.13.4" } +cw3 = { version = "0.13.4" } +cw3-fixed-multisig = { version = "0.13.4", features = ["library"] } +cw4 = { version = "0.13.4" } cosmwasm-std = "1.0.0" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs index 93fd9f4839..db37835f64 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs @@ -5,7 +5,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{CosmosMsg, Empty}; -pub use cw3::ProposalResponse; use cw3::Vote; use cw4::MemberChangedHookMsg; use cw_utils::{Duration, Expiration, Threshold}; @@ -15,6 +14,7 @@ pub struct InstantiateMsg { // this is the group contract that contains the member list pub group_addr: String, pub coconut_bandwidth_contract_address: String, + pub coconut_dkg_contract_address: String, pub threshold: Threshold, pub max_voting_period: Duration, } @@ -82,4 +82,5 @@ pub enum QueryMsg { #[serde(rename_all = "snake_case")] pub struct MigrateMsg { pub coconut_bandwidth_address: String, + pub coconut_dkg_address: String, } diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 4ff6b60d2b..58d43af03f 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", optional = true } thiserror = "1.0" -url = "2.2" # I guess temporarily until we get serde support in coconut up and running coconut-interface = { path = "../coconut-interface" } diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 537ea801ff..f701938d59 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -8,8 +8,8 @@ use coconut_interface::{ use crypto::asymmetric::encryption::PublicKey; use crypto::shared_key::recompute_shared_key; use crypto::symmetric::stream_cipher; -use url::Url; use validator_api_requests::coconut::BlindSignRequestBody; +use validator_client::client::CoconutApiClient; use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; use crate::coconut::params::{ @@ -17,46 +17,21 @@ use crate::coconut::params::{ }; use crate::error::Error; -/// Contacts all provided validators and then aggregate their verification keys. -/// -/// # Arguments -/// -/// * `validators`: list of validators to obtain verification keys from. -/// -/// Note: list of validators must be correctly ordered by the polynomial coordinates used -/// during key generation and it is responsibility of the caller to ensure that correct -/// number of them is provided -/// -/// # Examples -/// -/// ```no_run -/// use url::{Url, ParseError}; -/// use credentials::obtain_aggregate_verification_key; -/// -/// async fn example() -> Result<(), ParseError> { -/// let validators = vec!["https://sandbox-validator1.nymtech.net/api".parse()?, "https://sandbox-validator2.nymtech.net/api".parse()?]; -/// let aggregated_key = obtain_aggregate_verification_key(&validators).await; -/// // deal with the obtained Result -/// Ok(()) -/// } -/// ``` pub async fn obtain_aggregate_verification_key( - validators: &[Url], + api_clients: &[CoconutApiClient], ) -> Result { - if validators.is_empty() { + if api_clients.is_empty() { return Err(Error::NoValidatorsAvailable); } - let mut indices = Vec::with_capacity(validators.len()); - let mut shares = Vec::with_capacity(validators.len()); - - let mut client = validator_client::ApiClient::new(validators[0].clone()); - for (id, validator_url) in validators.iter().enumerate() { - client.change_validator_api(validator_url.clone()); - let response = client.get_coconut_verification_key().await?; - indices.push((id + 1) as u64); - shares.push(response.key); - } + let indices: Vec<_> = api_clients + .iter() + .map(|api_client| api_client.node_id) + .collect(); + let shares: Vec<_> = api_clients + .iter() + .map(|api_client| api_client.verification_key.clone()) + .collect(); Ok(aggregate_verification_keys(&shares, Some(&indices))?) } @@ -64,7 +39,7 @@ pub async fn obtain_aggregate_verification_key( async fn obtain_partial_credential( params: &Parameters, attributes: &BandwidthVoucher, - client: &validator_client::ApiClient, + client: &validator_client::client::ApiClient, validator_vk: &VerificationKey, ) -> Result { let public_attributes = attributes.get_public_attributes(); @@ -118,26 +93,33 @@ async fn obtain_partial_credential( pub async fn obtain_aggregate_signature( params: &Parameters, attributes: &BandwidthVoucher, - validators: &[Url], + coconut_api_clients: &[CoconutApiClient], ) -> Result { - if validators.is_empty() { + if coconut_api_clients.is_empty() { return Err(Error::NoValidatorsAvailable); } let public_attributes = attributes.get_public_attributes(); let private_attributes = attributes.get_private_attributes(); - let mut shares = Vec::with_capacity(validators.len()); - let mut validators_partial_vks: Vec = Vec::with_capacity(validators.len()); + let mut shares = Vec::with_capacity(coconut_api_clients.len()); + let validators_partial_vks: Vec<_> = coconut_api_clients + .iter() + .map(|api_client| api_client.verification_key.clone()) + .collect(); + let indices: Vec<_> = coconut_api_clients + .iter() + .map(|api_client| api_client.node_id) + .collect(); - let mut client = validator_client::ApiClient::new(validators[0].clone()); - for (id, validator_url) in validators.iter().enumerate() { - client.change_validator_api(validator_url.clone()); - let validator_partial_vk = client.get_coconut_verification_key().await?; - validators_partial_vks.push(validator_partial_vk.key.clone()); - let signature = - obtain_partial_credential(params, attributes, &client, &validator_partial_vk.key) - .await?; - let share = SignatureShare::new(signature, (id + 1) as u64); + for coconut_api_client in coconut_api_clients.iter() { + let signature = obtain_partial_credential( + params, + attributes, + &coconut_api_client.api_client, + &coconut_api_client.verification_key, + ) + .await?; + let share = SignatureShare::new(signature, coconut_api_client.node_id); shares.push(share) } @@ -145,10 +127,6 @@ pub async fn obtain_aggregate_signature( attributes.extend_from_slice(&private_attributes); attributes.extend_from_slice(&public_attributes); - let mut indices: Vec = Vec::with_capacity(validators_partial_vks.len()); - for i in 0..validators_partial_vks.len() { - indices.push((i + 1) as u64); - } let verification_key = aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?; diff --git a/common/crypto/dkg/src/dealing.rs b/common/crypto/dkg/src/dealing.rs index ead51b7f80..817bffdf12 100644 --- a/common/crypto/dkg/src/dealing.rs +++ b/common/crypto/dkg/src/dealing.rs @@ -15,6 +15,13 @@ use rand_core::RngCore; use std::collections::BTreeMap; use zeroize::Zeroize; +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub struct RecoveredVerificationKeys { + pub recovered_master: G2Projective, + pub recovered_partials: Vec, +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq, Eq))] pub struct Dealing { @@ -245,7 +252,7 @@ pub fn try_recover_verification_keys( dealings: &[Dealing], threshold: Threshold, receivers: &BTreeMap, -) -> Result<(G2Projective, Vec), DkgError> { +) -> Result { if dealings.is_empty() { return Err(DkgError::NoDealingsAvailable); } @@ -292,7 +299,10 @@ pub fn try_recover_verification_keys( .map(|index| interpolated_coefficients.evaluate_at(&Scalar::from(*index))) .collect(); - Ok((master_verification_key, verification_key_shares)) + Ok(RecoveredVerificationKeys { + recovered_master: master_verification_key, + recovered_partials: verification_key_shares, + }) } pub fn verify_verification_keys( @@ -389,8 +399,10 @@ mod tests { .unwrap(); // END OF SETUP - let (recovered_master, recovered_partials) = - try_recover_verification_keys(&dealings, threshold, &receivers).unwrap(); + let RecoveredVerificationKeys { + recovered_master, + recovered_partials, + } = try_recover_verification_keys(&dealings, threshold, &receivers).unwrap(); let g2 = G2Projective::generator(); assert_eq!(g2 * master_secret, recovered_master); @@ -425,8 +437,10 @@ mod tests { }) .collect::>(); - let (recovered_master, recovered_partials) = - try_recover_verification_keys(&dealings, threshold, &receivers).unwrap(); + let RecoveredVerificationKeys { + recovered_master, + recovered_partials, + } = try_recover_verification_keys(&dealings, threshold, &receivers).unwrap(); assert!(verify_verification_keys( &recovered_master, diff --git a/common/crypto/dkg/tests/integration.rs b/common/crypto/dkg/tests/integration.rs index 24518dd789..2d25c31d40 100644 --- a/common/crypto/dkg/tests/integration.rs +++ b/common/crypto/dkg/tests/integration.rs @@ -3,6 +3,7 @@ use bls12_381::{G2Projective, Scalar}; use dkg::bte::{decrypt_share, keygen, setup}; +use dkg::dealing::RecoveredVerificationKeys; use dkg::interpolation::perform_lagrangian_interpolation_at_origin; use dkg::{combine_shares, try_recover_verification_keys, Dealing}; use rand_core::SeedableRng; @@ -100,8 +101,10 @@ fn full_threshold_secret_sharing() { } // recover verification keys - let (recovered_master, recovered_partials) = - try_recover_verification_keys(&dealings, threshold, &receivers).unwrap(); + let RecoveredVerificationKeys { + recovered_master, + recovered_partials, + } = try_recover_verification_keys(&dealings, threshold, &receivers).unwrap(); let g2 = G2Projective::generator(); @@ -171,8 +174,10 @@ fn full_threshold_secret_resharing() { .collect::>(); // recover verification keys - let (public_original_master, recovered_partials) = - try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap(); + let RecoveredVerificationKeys { + recovered_master: public_original_master, + recovered_partials, + } = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap(); let mut derived_secrets = Vec::new(); for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { @@ -217,8 +222,10 @@ fn full_threshold_secret_resharing() { } // recover verification keys - let (public_reshared_master, reshared_partials) = - try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap(); + let RecoveredVerificationKeys { + recovered_master: public_reshared_master, + recovered_partials: reshared_partials, + } = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap(); let mut reshared_secrets = Vec::new(); for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { diff --git a/common/nymcoconut/Cargo.toml b/common/nymcoconut/Cargo.toml index c17aa280d3..5dd70dbd2c 100644 --- a/common/nymcoconut/Cargo.toml +++ b/common/nymcoconut/Cargo.toml @@ -17,6 +17,9 @@ serde_derive = "1.0" bs58 = "0.4.0" sha2 = "0.9" +dkg = { path = "../crypto/dkg" } +pemstore = { path = "../pemstore" } + [dependencies.ff] version = "0.11" default-features = false @@ -28,7 +31,6 @@ 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] diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs index b48bbc2837..a98b546fc1 100644 --- a/common/nymcoconut/src/lib.rs +++ b/common/nymcoconut/src/lib.rs @@ -21,6 +21,7 @@ pub use scheme::keygen::SecretKey; pub use scheme::keygen::VerificationKey; pub use scheme::setup::setup; pub use scheme::setup::Parameters; +pub use scheme::verification::check_vk_pairing; pub use scheme::verification::prove_bandwidth_credential; pub use scheme::verification::verify_credential; pub use scheme::verification::Theta; diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index 75fb203dc2..ea9c88aa65 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -9,6 +9,7 @@ use std::convert::TryInto; use bls12_381::{G1Projective, G2Projective, Scalar}; use group::Curve; +use pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use serde_derive::{Deserialize, Serialize}; use crate::error::{CoconutError, Result}; @@ -29,6 +30,22 @@ pub struct SecretKey { pub(crate) ys: Vec, } +impl PemStorableKey for SecretKey { + type Error = CoconutError; + + fn pem_type() -> &'static str { + "COCONUT SECRET KEY" + } + + fn to_bytes(&self) -> Vec { + self.to_bytes() + } + + fn from_bytes(bytes: &[u8]) -> std::result::Result { + Self::from_bytes(bytes) + } +} + impl TryFrom<&[u8]> for SecretKey { type Error = CoconutError; @@ -128,6 +145,22 @@ pub struct VerificationKey { pub(crate) beta_g2: Vec, } +impl PemStorableKey for VerificationKey { + type Error = CoconutError; + + fn pem_type() -> &'static str { + "COCONUT VERIFICATION KEY" + } + + fn to_bytes(&self) -> Vec { + self.to_bytes() + } + + fn from_bytes(bytes: &[u8]) -> std::result::Result { + Self::from_bytes(bytes) + } +} + impl TryFrom<&[u8]> for VerificationKey { type Error = CoconutError; @@ -366,6 +399,23 @@ pub struct KeyPair { pub index: Option, } +impl PemStorableKeyPair for KeyPair { + type PrivatePemKey = SecretKey; + type PublicPemKey = VerificationKey; + + fn private_key(&self) -> &Self::PrivatePemKey { + &self.secret_key + } + + fn public_key(&self) -> &Self::PublicPemKey { + &self.verification_key + } + + fn from_keys(secret_key: Self::PrivatePemKey, verification_key: Self::PublicPemKey) -> Self { + Self::from_keys(secret_key, verification_key) + } +} + impl KeyPair { const MARKER_BYTES: &'static [u8] = b"coconutkeypair"; diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index 9f7aba12b5..96ef28e9ca 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -212,6 +212,39 @@ pub fn check_bilinear_pairing(p: &G1Affine, q: &G2Prepared, r: &G1Affine, s: &G2 multi_miller.final_exponentiation().is_identity().into() } +pub fn check_vk_pairing( + params: &Parameters, + dkg_values: &[G2Projective], + vk: &VerificationKey, +) -> bool { + let values_len = dkg_values.len(); + if values_len == 0 || values_len - 1 != vk.beta_g1.len() || values_len - 1 != vk.beta_g2.len() { + return false; + } + if vk.alpha != *dkg_values.last().unwrap() { + return false; + } + if dkg_values + .iter() + .zip(vk.beta_g2.iter()) + .any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta) + { + return false; + } + if vk.beta_g1.iter().zip(vk.beta_g2.iter()).any(|(g1, g2)| { + !check_bilinear_pairing( + params.gen1(), + &G2Prepared::from(g2.to_affine()), + &g1.to_affine(), + params.prepared_miller_g2(), + ) + }) { + return false; + } + + true +} + pub fn verify_credential( params: &Parameters, verification_key: &VerificationKey, @@ -284,6 +317,15 @@ mod tests { use super::*; + #[test] + fn vk_pairing() { + let params = setup(2).unwrap(); + let vk = keygen(¶ms).verification_key(); + let mut dkg_values = vk.beta_g2.clone(); + dkg_values.push(vk.alpha); + assert!(check_vk_pairing(¶ms, &dkg_values, &vk)); + } + #[test] fn theta_bytes_roundtrip() { let params = setup(2).unwrap(); diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs index a9be6b885c..ee7e418788 100644 --- a/common/nymcoconut/src/tests/helpers.rs +++ b/common/nymcoconut/src/tests/helpers.rs @@ -3,6 +3,7 @@ use crate::*; use itertools::izip; +use std::fmt::Debug; pub fn theta_from_keys_and_attributes( params: &Parameters, @@ -87,13 +88,26 @@ pub fn theta_from_keys_and_attributes( Ok(theta) } +pub fn transpose_matrix(matrix: Vec>) -> Vec> { + let len = matrix[0].len(); + let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect(); + (0..len) + .map(|_| { + iters + .iter_mut() + .map(|it| it.next().unwrap()) + .collect::>() + }) + .collect::>() +} + #[cfg(test)] pub mod tests { + use super::*; use crate::{KeyPair, Parameters, SecretKey}; use bls12_381::Scalar; use dkg::{bte::decrypt_share, combine_shares, Dealing, NodeIndex}; use rand_chacha::rand_core::SeedableRng; - use std::fmt::Debug; pub fn generate_dkg_secrets(node_indices: &[NodeIndex]) -> Vec { let dummy_seed = [42u8; 32]; @@ -148,19 +162,4 @@ pub mod tests { }) .collect() } - - pub(crate) fn transpose_matrix(matrix: Vec>) -> Vec> { - let len = matrix[0].len(); - let mut iters: Vec<_> = matrix.into_iter().map(|d| d.into_iter()).collect(); - (0..len) - .map(|_| { - iters - .iter_mut() - .map(|it| it.next().unwrap()) - .collect::>() - }) - .collect::>() - .try_into() - .unwrap() - } } diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index b32e8092f6..d2fc273d22 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -210,6 +210,8 @@ version = "0.1.0" dependencies = [ "contracts-common", "cosmwasm-std", + "cw-utils", + "multisig-contract-common", "schemars", "serde", ] @@ -221,6 +223,8 @@ dependencies = [ "bandwidth-claim-contract", "coconut-bandwidth", "coconut-bandwidth-contract-common", + "coconut-dkg", + "coconut-dkg-common", "cosmwasm-std", "cosmwasm-storage", "cw-controllers", @@ -228,6 +232,7 @@ dependencies = [ "cw-storage-plus", "cw-utils", "cw3-flex-multisig", + "cw4", "cw4-group", "multisig-contract-common", "schemars", @@ -409,9 +414,9 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbea57e5be4a682268a5eca1a57efece57a54ff216bfd87603d5e864aad40e12" +checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" dependencies = [ "anyhow", "cosmwasm-std", @@ -523,7 +528,7 @@ dependencies = [ [[package]] name = "cw4-group" -version = "0.13.1" +version = "0.13.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -998,6 +1003,7 @@ dependencies = [ "cosmwasm-std", "cw-utils", "cw3", + "cw3-fixed-multisig", "cw4", "schemars", "serde", @@ -1746,9 +1752,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "x25519-dalek" -version = "1.2.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ "curve25519-dalek", "rand_core 0.5.1", @@ -1757,9 +1763,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] diff --git a/contracts/coconut-dkg/src/constants.rs b/contracts/coconut-dkg/src/constants.rs index e250c889c0..bfa7d44aef 100644 --- a/contracts/coconut-dkg/src/constants.rs +++ b/contracts/coconut-dkg/src/constants.rs @@ -5,3 +5,5 @@ use cosmwasm_std::Uint128; // to be determined whether those should be constants or exist as contract state pub(crate) const MINIMUM_DEPOSIT: Uint128 = Uint128::new(1_000_000_000); +// Wait time for the verification to take place (currently 24h) +pub(crate) const BLOCK_TIME_FOR_VERIFICATION_SECS: u64 = 86400; diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index c7a0411f8c..7ad730e5ab 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -65,6 +65,7 @@ pub fn try_add_dealer( mut deps: DepsMut<'_>, info: MessageInfo, bte_key_with_proof: EncodedBTEPublicKeyWithProof, + announce_address: String, ) -> Result { check_epoch_state(deps.storage, EpochState::PublicKeySubmission)?; let state = STATE.load(deps.storage)?; @@ -94,6 +95,7 @@ pub fn try_add_dealer( let dealer_details = DealerDetails { address: info.sender.clone(), bte_public_key_with_proof: bte_key_with_proof, + announce_address, assigned_index: node_index, deposit, }; diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 7b2b132254..73e067e0d6 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -14,7 +14,7 @@ pub fn try_commit_dealings( dealing_bytes: ContractSafeBytes, ) -> Result { check_epoch_state(deps.storage, EpochState::DealingExchange)?; - // ensure the sender is a dealer for the current epoch + // ensure the sender is a dealer if dealers_storage::current_dealers() .may_load(deps.storage, &info.sender)? .is_none() @@ -31,5 +31,7 @@ pub fn try_commit_dealings( } } - Err(ContractError::AlreadyCommitted) + Err(ContractError::AlreadyCommitted { + commitment: String::from("dealing"), + }) } diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index a29ffd8223..ad3a3e74ef 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -60,6 +60,9 @@ pub enum ContractError { #[error("This sender is not a dealer for the current epoch")] NotADealer, - #[error("This dealer has already commited dealing for this epoch")] - AlreadyCommitted, + #[error("This dealer has already committed {commitment}")] + AlreadyCommitted { commitment: String }, + + #[error("No verification key committed for owner {owner}")] + NoCommitForOwner { owner: String }, } diff --git a/contracts/coconut-dkg/src/lib.rs b/contracts/coconut-dkg/src/lib.rs index 0151770d1f..7787776306 100644 --- a/contracts/coconut-dkg/src/lib.rs +++ b/contracts/coconut-dkg/src/lib.rs @@ -8,7 +8,8 @@ use crate::dealers::queries::{ use crate::dealings::queries::query_dealings_paged; use crate::epoch_state::queries::query_current_epoch_state; use crate::error::ContractError; -use crate::state::{State, ADMIN, STATE}; +use crate::state::{State, ADMIN, MULTISIG, STATE}; +use crate::verification_key_shares::queries::query_vk_shares_paged; use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use coconut_dkg_common::types::{EpochState, MinimumDepositResponse}; use cosmwasm_std::{ @@ -23,6 +24,7 @@ mod dealings; mod epoch_state; mod error; mod state; +mod verification_key_shares; /// Instantiate the contract. /// @@ -37,7 +39,9 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { let admin_addr = deps.api.addr_validate(&msg.admin)?; + let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?; ADMIN.set(deps.branch(), Some(admin_addr))?; + MULTISIG.set(deps.branch(), Some(multisig_addr.clone()))?; let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| { ContractError::InvalidGroup { @@ -47,6 +51,7 @@ pub fn instantiate( let state = State { group_addr, + multisig_addr, mix_denom: msg.mix_denom, }; STATE.save(deps.storage, &state)?; @@ -65,12 +70,25 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::RegisterDealer { bte_key_with_proof } => { - dealers::transactions::try_add_dealer(deps, info, bte_key_with_proof) + ExecuteMsg::RegisterDealer { + bte_key_with_proof, + announce_address, + } => { + dealers::transactions::try_add_dealer(deps, info, bte_key_with_proof, announce_address) } ExecuteMsg::CommitDealing { dealing_bytes } => { dealings::transactions::try_commit_dealings(deps, info, dealing_bytes) } + ExecuteMsg::CommitVerificationKeyShare { share } => { + verification_key_shares::transactions::try_commit_verification_key_share( + deps, env, info, share, + ) + } + ExecuteMsg::VerifyVerificationKeyShare { owner } => { + verification_key_shares::transactions::try_verify_verification_key_share( + deps, info, owner, + ) + } ExecuteMsg::DebugUnsafeResetAll { init_msg } => { reset_contract_state(deps, env, info, init_msg) } @@ -140,6 +158,9 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result to_binary(&query_dealings_paged(deps, idx, start_after, limit)?)?, + QueryMsg::GetVerificationKeys { limit, start_after } => { + to_binary(&query_vk_shares_paged(deps, start_after, limit)?)? + } }; Ok(response) @@ -161,8 +182,9 @@ mod tests { let env = mock_env(); let msg = InstantiateMsg { group_addr: "group_addr".to_string(), - mix_denom: "nym".to_string(), + multisig_addr: "multisig_addr".to_string(), admin: "admin".to_string(), + mix_denom: "nym".to_string(), }; let info = mock_info("creator", &[]); diff --git a/contracts/coconut-dkg/src/state/mod.rs b/contracts/coconut-dkg/src/state/mod.rs index cc5e965839..729618ec60 100644 --- a/contracts/coconut-dkg/src/state/mod.rs +++ b/contracts/coconut-dkg/src/state/mod.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use cosmwasm_std::Addr; use cw4::Cw4Contract; use cw_controllers::Admin; use cw_storage_plus::Item; @@ -10,9 +11,11 @@ use serde::{Deserialize, Serialize}; // unique items pub const STATE: Item = Item::new("state"); pub const ADMIN: Admin = Admin::new("admin"); +pub const MULTISIG: Admin = Admin::new("multisig"); #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct State { pub mix_denom: String, + pub multisig_addr: Addr, pub group_addr: Cw4Contract, } diff --git a/contracts/coconut-dkg/src/verification_key_shares/mod.rs b/contracts/coconut-dkg/src/verification_key_shares/mod.rs new file mode 100644 index 0000000000..11f07ae776 --- /dev/null +++ b/contracts/coconut-dkg/src/verification_key_shares/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod queries; +pub mod storage; +pub mod transactions; diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs new file mode 100644 index 0000000000..6739577abb --- /dev/null +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -0,0 +1,38 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::verification_key_shares::storage; +use crate::verification_key_shares::storage::VK_SHARES; +use coconut_dkg_common::verification_key::PagedVKSharesResponse; +use cosmwasm_std::{Deps, Order, StdResult}; +use cw_storage_plus::Bound; + +pub fn query_vk_shares_paged( + deps: Deps<'_>, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit + .unwrap_or(storage::VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT) + .min(storage::VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT) as usize; + + let addr = start_after + .map(|addr| deps.api.addr_validate(&addr)) + .transpose()?; + + let start = addr.as_ref().map(Bound::exclusive); + + let shares = VK_SHARES + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|res| res.map(|(_, share)| share)) + .collect::>>()?; + + let start_next_after = shares.last().map(|share| share.owner.clone()); + + Ok(PagedVKSharesResponse { + shares, + per_page: limit, + start_next_after, + }) +} diff --git a/contracts/coconut-dkg/src/verification_key_shares/storage.rs b/contracts/coconut-dkg/src/verification_key_shares/storage.rs new file mode 100644 index 0000000000..761736367c --- /dev/null +++ b/contracts/coconut-dkg/src/verification_key_shares/storage.rs @@ -0,0 +1,15 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use coconut_dkg_common::verification_key::ContractVKShare; +use cosmwasm_std::Addr; +use cw_storage_plus::Map; + +pub(crate) const VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT: u32 = 75; +pub(crate) const VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT: u32 = 50; + +type VKShareKey<'a> = &'a Addr; + +pub(crate) const VK_SHARES: Map<'_, VKShareKey<'_>, ContractVKShare> = Map::new("vks"); diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs new file mode 100644 index 0000000000..d86748e7f4 --- /dev/null +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -0,0 +1,71 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::constants::BLOCK_TIME_FOR_VERIFICATION_SECS; +use crate::dealers::storage as dealers_storage; +use crate::epoch_state::utils::check_epoch_state; +use crate::error::ContractError; +use crate::state::{MULTISIG, STATE}; +use crate::verification_key_shares::storage::VK_SHARES; +use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::verification_key::{to_cosmos_msg, ContractVKShare, VerificationKeyShare}; +use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; + +pub fn try_commit_verification_key_share( + deps: DepsMut<'_>, + env: Env, + info: MessageInfo, + share: VerificationKeyShare, +) -> Result { + check_epoch_state(deps.storage, EpochState::VerificationKeySubmission)?; + // ensure the sender is a dealer + let details = dealers_storage::current_dealers() + .load(deps.storage, &info.sender) + .map_err(|_| ContractError::NotADealer)?; + if VK_SHARES.may_load(deps.storage, &info.sender)?.is_some() { + return Err(ContractError::AlreadyCommitted { + commitment: String::from("verification key share"), + }); + } + + let data = ContractVKShare { + share, + node_index: details.assigned_index, + announce_address: details.announce_address, + owner: info.sender.clone(), + verified: false, + }; + VK_SHARES.save(deps.storage, &info.sender, &data)?; + + let msg = to_cosmos_msg( + info.sender, + env.contract.address.to_string(), + STATE.load(deps.storage)?.multisig_addr.to_string(), + env.block + .time + .plus_seconds(BLOCK_TIME_FOR_VERIFICATION_SECS), + )?; + + Ok(Response::new().add_message(msg)) +} + +pub fn try_verify_verification_key_share( + deps: DepsMut<'_>, + info: MessageInfo, + owner: Addr, +) -> Result { + check_epoch_state(deps.storage, EpochState::VerificationKeyFinalization)?; + MULTISIG.assert_admin(deps.as_ref(), &info.sender)?; + VK_SHARES.update(deps.storage, &owner, |vk_share| { + vk_share + .map(|mut share| { + share.verified = true; + share + }) + .ok_or(ContractError::NoCommitForOwner { + owner: owner.to_string(), + }) + })?; + + Ok(Response::default()) +} diff --git a/contracts/coconut-test/Cargo.toml b/contracts/coconut-test/Cargo.toml index 5955391250..3aff6fc368 100644 --- a/contracts/coconut-test/Cargo.toml +++ b/contracts/coconut-test/Cargo.toml @@ -8,10 +8,12 @@ edition = "2021" [dependencies] bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" } coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } +coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" } cosmwasm-std = "1.0.0" cosmwasm-storage = "1.0.0" +cw4 = "0.13.4" cw-storage-plus = "0.13.4" cw-controllers = "0.13.4" cw-utils = "0.13.4" @@ -21,7 +23,8 @@ serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = "1.0.23" coconut-bandwidth = { path = "../coconut-bandwidth" } -cw-multi-test = { version = "0.13.2" } +coconut-dkg = { path = "../coconut-dkg" } +cw-multi-test = { version = "0.13.4" } cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" } cw4-group = { path = "../multisig/cw4-group" } diff --git a/contracts/coconut-test/src/helpers.rs b/contracts/coconut-test/src/helpers.rs index 2a8c0b24ed..b8e8ad6d03 100644 --- a/contracts/coconut-test/src/helpers.rs +++ b/contracts/coconut-test/src/helpers.rs @@ -8,6 +8,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; pub const OWNER: &str = "admin0001"; +pub const MEMBER1: &str = "member1"; pub const MULTISIG_CONTRACT: &str = "multisig contract address"; pub const POOL_CONTRACT: &str = "mix pool contract address"; pub const RANDOM_ADDRESS: &str = "random address"; @@ -16,6 +17,7 @@ pub const RANDOM_ADDRESS: &str = "random address"; #[serde(rename_all = "snake_case")] pub struct MigrateMsg { pub coconut_bandwidth_address: String, + pub coconut_dkg_address: String, } #[entry_point] @@ -32,8 +34,20 @@ pub fn mock_app(init_funds: &[Coin]) -> App { .bank .init_balance(storage, &Addr::unchecked(OWNER), init_funds.to_vec()) .unwrap(); + router + .bank + .init_balance(storage, &Addr::unchecked(MEMBER1), init_funds.to_vec()) + .unwrap(); }) } +pub fn contract_dkg() -> Box> { + let contract = ContractWrapper::new( + coconut_dkg::execute, + coconut_dkg::instantiate, + coconut_dkg::query, + ); + Box::new(contract) +} pub fn contract_bandwidth() -> Box> { let contract = ContractWrapper::new( diff --git a/contracts/coconut-test/src/spend_credential_creates_proposal.rs b/contracts/coconut-test/src/spend_credential_creates_proposal.rs index 7d07905b97..3d382f493b 100644 --- a/contracts/coconut-test/src/spend_credential_creates_proposal.rs +++ b/contracts/coconut-test/src/spend_credential_creates_proposal.rs @@ -15,6 +15,7 @@ use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; pub const TEST_COIN_DENOM: &str = "unym"; pub const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub const TEST_COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; #[test] fn spend_credential_creates_proposal() { @@ -46,6 +47,7 @@ fn spend_credential_creates_proposal() { }, max_voting_period: Duration::Height(1000), coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), + coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(), }; let multisig_contract_addr = app .instantiate_contract( @@ -77,6 +79,7 @@ fn spend_credential_creates_proposal() { let msg = MigrateMsg { coconut_bandwidth_address: coconut_bandwidth_contract_addr.to_string(), + coconut_dkg_address: "".to_string(), }; app.migrate_contract( Addr::unchecked(OWNER), diff --git a/contracts/coconut-test/src/submit_vk_creates_proposal.rs b/contracts/coconut-test/src/submit_vk_creates_proposal.rs new file mode 100644 index 0000000000..a556bbe35f --- /dev/null +++ b/contracts/coconut-test/src/submit_vk_creates_proposal.rs @@ -0,0 +1,142 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::helpers::{ + contract_dkg, contract_group, contract_multisig, mock_app, MigrateMsg, MEMBER1, OWNER, +}; +use crate::spend_credential_creates_proposal::{ + TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS, TEST_COCONUT_DKG_CONTRACT_ADDRESS, TEST_COIN_DENOM, +}; +use coconut_dkg_common::msg::ExecuteMsg::{ + AdvanceEpochState, CommitVerificationKeyShare, RegisterDealer, +}; +use coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg; +use cosmwasm_std::{coins, Addr, Decimal}; +use cw4::Member; +use cw4_group::msg::InstantiateMsg as GroupInstantiateMsg; +use cw_multi_test::Executor; +use cw_utils::{Duration, Threshold}; +use multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; + +#[test] +fn dkg_create_proposal() { + let init_funds = coins(10000000000, TEST_COIN_DENOM); + let mut app = mock_app(&init_funds); + let member1 = Member { + addr: MEMBER1.to_string(), + weight: 10, + }; + + let group_code_id = app.store_code(contract_group()); + let msg = GroupInstantiateMsg { + admin: Some(OWNER.to_string()), + members: vec![member1], + }; + let group_contract_addr = app + .instantiate_contract( + group_code_id, + Addr::unchecked(OWNER), + &msg, + &[], + "group", + None, + ) + .unwrap(); + + let multisig_code_id = app.store_code(contract_multisig()); + let msg = MultisigInstantiateMsg { + group_addr: group_contract_addr.to_string(), + threshold: Threshold::AbsolutePercentage { + percentage: Decimal::from_ratio(2u128, 3u128), + }, + max_voting_period: Duration::Time(1000), + coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), + coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(), + }; + let multisig_contract_addr = app + .instantiate_contract( + multisig_code_id, + Addr::unchecked(OWNER), + &msg, + &[], + "multisig", + Some(OWNER.to_string()), + ) + .unwrap(); + + let coconut_dkg_code_id = app.store_code(contract_dkg()); + let msg = DkgInstantiateMsg { + group_addr: group_contract_addr.to_string(), + multisig_addr: multisig_contract_addr.to_string(), + admin: Addr::unchecked(OWNER).to_string(), + mix_denom: TEST_COIN_DENOM.to_string(), + }; + let coconut_dkg_contract_addr = app + .instantiate_contract( + coconut_dkg_code_id, + Addr::unchecked(OWNER), + &msg, + &[], + "coconut dkg", + None, + ) + .unwrap(); + + let msg = MigrateMsg { + coconut_bandwidth_address: coconut_dkg_contract_addr.to_string(), + coconut_dkg_address: coconut_dkg_contract_addr.to_string(), + }; + app.migrate_contract( + Addr::unchecked(OWNER), + multisig_contract_addr, + &msg, + multisig_code_id, + ) + .unwrap(); + + app.execute_contract( + Addr::unchecked(MEMBER1), + coconut_dkg_contract_addr.clone(), + &RegisterDealer { + bte_key_with_proof: "bte_key_with_proof".to_string(), + announce_address: "127.0.0.1:8000".to_string(), + }, + &coins(1000000000, TEST_COIN_DENOM), + ) + .unwrap(); + + for _ in 0..2 { + app.execute_contract( + Addr::unchecked(OWNER), + coconut_dkg_contract_addr.clone(), + &AdvanceEpochState {}, + &vec![], + ) + .unwrap(); + } + + let msg = CommitVerificationKeyShare { + share: "share".to_string(), + }; + let res = app + .execute_contract( + Addr::unchecked(MEMBER1), + coconut_dkg_contract_addr.clone(), + &msg, + &vec![], + ) + .unwrap(); + let proposal_id = res + .events + .into_iter() + .find(|e| &e.ty == "wasm") + .unwrap() + .attributes + .into_iter() + .find(|attr| &attr.key == "proposal_id") + .unwrap() + .value + .parse::() + .unwrap(); + assert_eq!(1, proposal_id); +} diff --git a/contracts/coconut-test/src/tests.rs b/contracts/coconut-test/src/tests.rs index 575a2c2ac1..2ba5f4b2e8 100644 --- a/contracts/coconut-test/src/tests.rs +++ b/contracts/coconut-test/src/tests.rs @@ -4,3 +4,4 @@ mod deposit_and_release; mod helpers; mod spend_credential_creates_proposal; +mod submit_vk_creates_proposal; diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index 0168eec05f..8afa71f251 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -17,12 +17,12 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -cw-utils = { version = "0.13.2" } -cw2 = { version = "0.13.2" } -cw3 = { version = "0.13.2" } -cw3-fixed-multisig = { version = "0.13.2", features = ["library"] } -cw4 = { version = "0.13.2" } -cw-storage-plus = { version = "0.13.2" } +cw-utils = { version = "0.13.4" } +cw2 = { version = "0.13.4" } +cw3 = { version = "0.13.4" } +cw3-fixed-multisig = { version = "0.13.4", features = ["library"] } +cw4 = { version = "0.13.4" } +cw-storage-plus = { version = "0.13.4" } cosmwasm-std = { version = "1.0.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } @@ -32,5 +32,5 @@ multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/mul [dev-dependencies] cosmwasm-schema = { version = "1.0.0" } -cw4-group = { path = "../cw4-group", version = "0.13.1" } -cw-multi-test = { version = "0.13.1" } +cw4-group = { path = "../cw4-group", version = "0.13.4" } +cw-multi-test = { version = "0.13.4" } diff --git a/contracts/multisig/cw3-flex-multisig/src/contract.rs b/contracts/multisig/cw3-flex-multisig/src/contract.rs index c85d1362a7..5a56ea9745 100644 --- a/contracts/multisig/cw3-flex-multisig/src/contract.rs +++ b/contracts/multisig/cw3-flex-multisig/src/contract.rs @@ -37,11 +37,12 @@ pub fn instantiate( addr: msg.group_addr.clone(), } })?); - // This might need to be changed via a migration, due to circular dependency + // Those might need to be changed via a migration, due to circular dependency // of deploying the two contracts let coconut_bandwidth_addr = deps .api .addr_validate(&msg.coconut_bandwidth_contract_address)?; + let coconut_dkg_addr = deps.api.addr_validate(&msg.coconut_dkg_contract_address)?; let total_weight = group_addr.total_weight(&deps.querier)?; msg.threshold.validate(total_weight)?; @@ -52,6 +53,7 @@ pub fn instantiate( max_voting_period: msg.max_voting_period, group_addr, coconut_bandwidth_addr, + coconut_dkg_addr, }; CONFIG.save(deps.storage, &cfg)?; @@ -62,6 +64,7 @@ pub fn instantiate( pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result { let mut cfg = CONFIG.load(deps.storage)?; cfg.coconut_bandwidth_addr = deps.api.addr_validate(&msg.coconut_bandwidth_address)?; + cfg.coconut_dkg_addr = deps.api.addr_validate(&msg.coconut_dkg_address)?; CONFIG.save(deps.storage, &cfg)?; Ok(Default::default()) } @@ -102,8 +105,8 @@ pub fn execute_propose( // only members of the multisig can create a proposal let cfg = CONFIG.load(deps.storage)?; - // Only the coconut bandwidth contract can create proposals - if info.sender != cfg.coconut_bandwidth_addr { + // Only the coconut bandwidth or dkg contracts can create proposals + if info.sender != cfg.coconut_bandwidth_addr && info.sender != cfg.coconut_dkg_addr { return Err(ContractError::Unauthorized {}); } // The contract doesn't have any say in the voting outcome @@ -460,6 +463,7 @@ mod tests { const SOMEBODY: &str = "somebody"; const TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; + const TEST_COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; fn member>(addr: T, weight: u64) -> Member { Member { @@ -539,6 +543,7 @@ mod tests { threshold, max_voting_period, coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), + coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(), }; app.instantiate_contract(flex_id, Addr::unchecked(OWNER), &msg, &[], "flex", None) .unwrap() @@ -655,6 +660,7 @@ mod tests { }, max_voting_period, coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), + coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(), }; let err = app .instantiate_contract( @@ -677,6 +683,7 @@ mod tests { threshold: Threshold::AbsoluteCount { weight: 100 }, max_voting_period, coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), + coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(), }; let err = app .instantiate_contract( @@ -699,6 +706,7 @@ mod tests { threshold: Threshold::AbsoluteCount { weight: 1 }, max_voting_period, coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(), + coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(), }; let flex_addr = app .instantiate_contract( diff --git a/contracts/multisig/cw3-flex-multisig/src/state.rs b/contracts/multisig/cw3-flex-multisig/src/state.rs index 96e6146c2f..0826f1014e 100644 --- a/contracts/multisig/cw3-flex-multisig/src/state.rs +++ b/contracts/multisig/cw3-flex-multisig/src/state.rs @@ -13,6 +13,7 @@ pub struct Config { // Total weight and voters are queried from this contract pub group_addr: Cw4Contract, pub coconut_bandwidth_addr: Addr, + pub coconut_dkg_addr: Addr, } // unique items diff --git a/contracts/multisig/cw4-group/Cargo.toml b/contracts/multisig/cw4-group/Cargo.toml index ecf5ff366c..235cfeed70 100644 --- a/contracts/multisig/cw4-group/Cargo.toml +++ b/contracts/multisig/cw4-group/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cw4-group" -version = "0.13.1" +version = "0.13.4" authors = ["Ethan Frey "] edition = "2018" description = "Simple cw4 implementation of group membership controlled by admin " @@ -24,11 +24,11 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -cw-utils = { version = "0.13.2" } -cw2 = { version = "0.13.2" } -cw4 = { version = "0.13.2" } -cw-controllers = { version = "0.13.2" } -cw-storage-plus = { version = "0.13.2" } +cw-utils = { version = "0.13.4" } +cw2 = { version = "0.13.4" } +cw4 = { version = "0.13.4" } +cw-controllers = { version = "0.13.4" } +cw-storage-plus = { version = "0.13.4" } cosmwasm-std = { version = "1.0.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index 19c8e426b4..e83c23e5e9 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -8,11 +8,11 @@ use log::*; use coconut_interface::{Credential, VerificationKey}; use validator_client::{ nymd::{ - cosmwasm_client::logs::{find_attribute, PROPOSAL_ID}, + cosmwasm_client::logs::{find_attribute, BANDWIDTH_PROPOSAL_ID}, traits::{CoconutBandwidthSigningClient, MultisigQueryClient, MultisigSigningClient}, - Coin, Fee, NymdClient, SigningNymdClient, + Coin, Fee, SigningNymdClient, }, - ApiClient, + Client, CoconutApiClient, }; use super::authenticated::RequestHandlingError; @@ -21,16 +21,16 @@ const ONE_HOUR_SEC: u64 = 3600; const MAX_FEEGRANT_UNYM: u128 = 10000; pub(crate) struct CoconutVerifier { - api_clients: Vec, - nymd_client: NymdClient, + api_clients: Vec, + nymd_client: Client, mix_denom_base: String, aggregated_verification_key: VerificationKey, } impl CoconutVerifier { pub fn new( - api_clients: Vec, - nymd_client: NymdClient, + api_clients: Vec, + nymd_client: Client, mix_denom_base: String, aggregated_verification_key: VerificationKey, ) -> Result { @@ -59,17 +59,18 @@ impl CoconutVerifier { let res = self .nymd_client + .nymd .spend_credential( Coin::new( credential.voucher_value().into(), self.mix_denom_base.clone(), ), credential.blinded_serial_number(), - self.nymd_client.address().to_string(), + self.nymd_client.nymd.address().to_string(), None, ) .await?; - let proposal_id = find_attribute(&res.logs, "wasm", PROPOSAL_ID) + let proposal_id = find_attribute(&res.logs, "wasm", BANDWIDTH_PROPOSAL_ID) .ok_or(RequestHandlingError::ProposalIdError { reason: String::from("proposal id not found"), })? @@ -79,7 +80,7 @@ impl CoconutVerifier { reason: String::from("proposal id could not be parsed to u64"), })?; - let proposal = self.nymd_client.get_proposal(proposal_id).await?; + let proposal = self.nymd_client.nymd.get_proposal(proposal_id).await?; if !credential.has_blinded_serial_number(&proposal.description)? { return Err(RequestHandlingError::ProposalIdError { reason: String::from("proposal has different serial number"), @@ -89,13 +90,13 @@ impl CoconutVerifier { let req = validator_api_requests::coconut::VerifyCredentialBody::new( credential.clone(), proposal_id, - self.nymd_client.address().clone(), + self.nymd_client.nymd.address().clone(), ); for client in self.api_clients.iter() { - let api_cosmos_addr = client.get_cosmos_address().await?.addr; self.nymd_client + .nymd .grant_allowance( - &api_cosmos_addr, + &client.cosmos_address, vec![Coin::new(MAX_FEEGRANT_UNYM, self.mix_denom_base.clone())], SystemTime::now().checked_add(Duration::from_secs(ONE_HOUR_SEC)), // It would be nice to be able to filter deeper, but for now only the msg type filter is avaialable @@ -104,20 +105,24 @@ impl CoconutVerifier { None, ) .await?; - let ret = client.verify_bandwidth_credential(&req).await; + let ret = client.api_client.verify_bandwidth_credential(&req).await; self.nymd_client + .nymd .revoke_allowance( - &api_cosmos_addr, + &client.cosmos_address, "Cleanup the previous allowance for releasing funds".to_string(), revoke_fee.clone(), ) .await?; if !ret?.verification_result { - debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.validator_api.current_url()); + debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.api_client.validator_api.current_url()); } } - self.nymd_client.execute_proposal(proposal_id, None).await?; + self.nymd_client + .nymd + .execute_proposal(proposal_id, None) + .await?; Ok(()) } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index fd31af1af7..8c2a541f12 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -25,9 +25,9 @@ use crate::config::persistence::pathfinder::GatewayPathfinder; #[cfg(feature = "coconut")] use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; #[cfg(feature = "coconut")] -use credentials::obtain_aggregate_verification_key; +use credentials::coconut::utils::obtain_aggregate_verification_key; #[cfg(feature = "coconut")] -use validator_client::nymd; +use validator_client::{Client, CoconutApiClient}; use self::storage::PersistentStorage; @@ -242,32 +242,24 @@ where #[cfg(feature = "coconut")] fn random_nymd_client( &self, - ) -> validator_client::nymd::NymdClient { + ) -> validator_client::Client { let endpoints = self.config.get_validator_nymd_endpoints(); let validator_nymd = endpoints .choose(&mut thread_rng()) .expect("The list of validators is empty"); let network_details = NymNetworkDetails::new_from_env(); - let client_config = nymd::Config::try_from_nym_network_details(&network_details) - .expect("failed to construct valid validator client config with the provided network"); - - validator_client::nymd::NymdClient::connect_with_mnemonic( - client_config, - validator_nymd.as_ref(), - self.config.get_cosmos_mnemonic(), - None, + let client_config = validator_client::Config::try_from_nym_network_details( + &network_details, ) - .expect("Could not connect with mnemonic") - } + .expect("failed to construct valid validator client config with the provided network"); - #[cfg(feature = "coconut")] - fn all_api_clients(&self) -> Vec { - self.config - .get_validator_api_endpoints() - .into_iter() - .map(validator_client::ApiClient::new) - .collect() + let mut client = Client::new_signing(client_config, self.config.get_cosmos_mnemonic()) + .expect("Could not connect with mnemonic"); + client + .change_nymd(validator_nymd.clone()) + .expect("Could not use the random nymd URL"); + client } // TODO: ask DH whether this function still makes sense in ^0.10 @@ -306,20 +298,23 @@ where } #[cfg(feature = "coconut")] - let validators_verification_key = - obtain_aggregate_verification_key(&self.config.get_validator_api_endpoints()) + let coconut_verifier = { + let nymd_client = self.random_nymd_client(); + let api_clients = CoconutApiClient::all_coconut_api_clients(&nymd_client) + .await + .expect("Could not query all api clients"); + let validators_verification_key = obtain_aggregate_verification_key(&api_clients) .await .expect("failed to contact validators to obtain their verification keys"); - #[cfg(feature = "coconut")] - let nymd_client = self.random_nymd_client(); - #[cfg(feature = "coconut")] - let coconut_verifier = CoconutVerifier::new( - self.all_api_clients(), - nymd_client, - std::env::var(network_defaults::var_names::MIX_DENOM).expect("mix denom base not set"), - validators_verification_key, - ) - .expect("Could not create coconut verifier"); + CoconutVerifier::new( + api_clients, + nymd_client, + std::env::var(network_defaults::var_names::MIX_DENOM) + .expect("mix denom base not set"), + validators_verification_key, + ) + .expect("Could not create coconut verifier") + }; let mix_forwarding_channel = self.start_packet_forwarder(); diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index 90574cb063..4fb4413f99 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -72,6 +72,7 @@ credential-storage = { path = "../common/credential-storage" } credentials = { path = "../common/credentials", optional = true } crypto = { path = "../common/crypto" } logging = { path = "../common/logging"} +cw3 = { version = "0.13.4", optional = true } dkg = { path = "../common/crypto/dkg", optional = true } gateway-client = { path = "../common/client-libs/gateway-client" } inclusion-probability = { path = "../common/inclusion-probability" } @@ -93,6 +94,7 @@ version-checker = { path = "../common/version-checker" } coconut = [ "coconut-interface", "credentials", + "cw3", "gateway-client/coconut", "credentials/coconut", "validator-api-requests/coconut", @@ -121,5 +123,5 @@ vergen = { version = "7", default-features = false, features = [ ] } [dev-dependencies] -cw3 = "0.13.2" -cw-utils = "0.13.2" +cw3 = "0.13.4" +cw-utils = "0.13.4" diff --git a/validator-api/src/coconut/client.rs b/validator-api/src/coconut/client.rs index a09215a9ff..41fffd4856 100644 --- a/validator-api/src/coconut/client.rs +++ b/validator-api/src/coconut/client.rs @@ -5,8 +5,9 @@ use crate::coconut::error::Result; use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; +use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; -use multisig_contract_common::msg::ProposalResponse; +use cw3::ProposalResponse; use validator_client::nymd::cosmwasm_client::types::ExecuteResult; use validator_client::nymd::{AccountId, Fee, TxResponse}; @@ -15,6 +16,7 @@ pub trait Client { async fn address(&self) -> AccountId; async fn get_tx(&self, tx_hash: &str) -> Result; async fn get_proposal(&self, proposal_id: u64) -> Result; + async fn list_proposals(&self) -> Result>; async fn get_spent_credential( &self, blinded_serial_number: String, @@ -23,9 +25,18 @@ pub trait Client { async fn get_self_registered_dealer_details(&self) -> Result; async fn get_current_dealers(&self) -> Result>; async fn get_dealings(&self, idx: usize) -> Result>; + async fn get_verification_key_shares(&self) -> Result>; async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option) -> Result<()>; - async fn register_dealer(&self, bte_key: EncodedBTEPublicKeyWithProof) - -> Result; + async fn execute_proposal(&self, proposal_id: u64) -> Result<()>; + async fn register_dealer( + &self, + bte_key: EncodedBTEPublicKeyWithProof, + announce_address: String, + ) -> Result; async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result; + async fn submit_verification_key_share( + &self, + share: VerificationKeyShare, + ) -> Result; } diff --git a/validator-api/src/coconut/comm.rs b/validator-api/src/coconut/comm.rs index e6f7b8a1ac..42a428f153 100644 --- a/validator-api/src/coconut/comm.rs +++ b/validator-api/src/coconut/comm.rs @@ -2,28 +2,33 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::error::Result; +use crate::nymd_client::Client; use coconut_interface::VerificationKey; -use credentials::obtain_aggregate_verification_key; -use url::Url; +use credentials::coconut::utils::obtain_aggregate_verification_key; +use validator_client::nymd::SigningNymdClient; +use validator_client::CoconutApiClient; #[async_trait] pub trait APICommunicationChannel { async fn aggregated_verification_key(&self) -> Result; } -pub struct QueryCommunicationChannel { - validator_apis: Vec, +pub(crate) struct QueryCommunicationChannel { + nymd_client: Client, } impl QueryCommunicationChannel { - pub fn new(validator_apis: Vec) -> Self { - QueryCommunicationChannel { validator_apis } + pub fn new(nymd_client: Client) -> Self { + QueryCommunicationChannel { nymd_client } } } #[async_trait] impl APICommunicationChannel for QueryCommunicationChannel { async fn aggregated_verification_key(&self) -> Result { - Ok(obtain_aggregate_verification_key(&self.validator_apis).await?) + let client = self.nymd_client.0.read().await; + let coconut_api_clients = CoconutApiClient::all_coconut_api_clients(&client).await?; + let vk = obtain_aggregate_verification_key(&coconut_api_clients).await?; + Ok(vk) } } diff --git a/validator-api/src/coconut/dkg/client.rs b/validator-api/src/coconut/dkg/client.rs index 87d5c926be..6850dabc1c 100644 --- a/validator-api/src/coconut/dkg/client.rs +++ b/validator-api/src/coconut/dkg/client.rs @@ -5,8 +5,11 @@ use crate::coconut::client::Client; use crate::coconut::error::CoconutError; use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, NodeIndex}; +use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; +use cw3::ProposalResponse; use validator_client::nymd::cosmwasm_client::logs::{find_attribute, NODE_INDEX}; +use validator_client::nymd::cosmwasm_client::types::ExecuteResult; use validator_client::nymd::AccountId; pub(crate) struct DkgClient { @@ -14,6 +17,10 @@ pub(crate) struct DkgClient { } impl DkgClient { + // Some queries simply don't work the first time + // Until we determine why that is, retry the query a few more times + const RETRIES: usize = 3; + pub(crate) fn new(nymd_client: C) -> Self where C: Client + Send + Sync + 'static, @@ -28,7 +35,14 @@ impl DkgClient { } pub(crate) async fn get_current_epoch_state(&self) -> Result { - self.inner.get_current_epoch_state().await + let mut ret = self.inner.get_current_epoch_state().await; + for _ in 0..Self::RETRIES { + if ret.is_ok() { + return ret; + } + ret = self.inner.get_current_epoch_state().await; + } + ret } pub(crate) async fn get_self_registered_dealer_details( @@ -45,14 +59,35 @@ impl DkgClient { &self, idx: usize, ) -> Result, CoconutError> { - self.inner.get_dealings(idx).await + let mut ret = self.inner.get_dealings(idx).await; + for _ in 0..Self::RETRIES { + if ret.is_ok() { + return ret; + } + ret = self.inner.get_dealings(idx).await; + } + ret + } + + pub(crate) async fn get_verification_key_shares( + &self, + ) -> Result, CoconutError> { + self.inner.get_verification_key_shares().await + } + + pub(crate) async fn list_proposals(&self) -> Result, CoconutError> { + self.inner.list_proposals().await } pub(crate) async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, + announce_address: String, ) -> Result { - let res = self.inner.register_dealer(bte_key).await?; + let res = self + .inner + .register_dealer(bte_key, announce_address) + .await?; let node_index = find_attribute(&res.logs, "wasm", NODE_INDEX) .ok_or(CoconutError::NodeIndexRecoveryError { reason: String::from("node index not found"), @@ -73,4 +108,39 @@ impl DkgClient { self.inner.submit_dealing(dealing_bytes).await?; Ok(()) } + + pub(crate) async fn submit_verification_key_share( + &self, + share: VerificationKeyShare, + ) -> Result { + let mut ret = self + .inner + .submit_verification_key_share(share.clone()) + .await; + for _ in 0..Self::RETRIES { + if let Ok(res) = ret { + return Ok(res); + } + ret = self + .inner + .submit_verification_key_share(share.clone()) + .await; + } + ret + } + + pub(crate) async fn vote_verification_key_share( + &self, + proposal_id: u64, + vote_yes: bool, + ) -> Result<(), CoconutError> { + self.inner.vote_proposal(proposal_id, vote_yes, None).await + } + + pub(crate) async fn execute_verification_key_share( + &self, + proposal_id: u64, + ) -> Result<(), CoconutError> { + self.inner.execute_proposal(proposal_id).await + } } diff --git a/validator-api/src/coconut/dkg/complaints.rs b/validator-api/src/coconut/dkg/complaints.rs index dbb44a8497..8a95645a30 100644 --- a/validator-api/src/coconut/dkg/complaints.rs +++ b/validator-api/src/coconut/dkg/complaints.rs @@ -9,38 +9,3 @@ pub(crate) enum ComplaintReason { MalformedDealing(DkgError), DealingVerificationError(DkgError), } - -// pub(crate) async fn complaint_period( -// dkg_client: &DkgClient, -// state: &mut State, -// ) -> Result<(), CoconutError> { -// let dkg_params = dkg::bte::setup(); -// let threshold = state -// .threshold() -// .expect("We should have a tentative threshold by now"); -// let dealings = dkg_client.get_dealings().await?; -// for contract_dealing in dealings { -// match Dealing::try_from_bytes(&contract_dealing.dealing) { -// Ok(dealing) => { -// if let Err(err) = -// dealing.verify(&dkg_params, threshold, &state.current_receivers(), None) -// { -// state.remove_good_dealer(&contract_dealing.dealer); -// state.add_bad_dealer( -// contract_dealing.dealer, -// ComplaintReason::DealingVerificationError(err), -// ); -// } -// } -// Err(err) => { -// state.remove_good_dealer(&contract_dealing.dealer); -// state.add_bad_dealer( -// contract_dealing.dealer, -// ComplaintReason::MalformedDealing(err), -// ); -// } -// } -// } -// -// Ok(()) -// } diff --git a/validator-api/src/coconut/dkg/controller.rs b/validator-api/src/coconut/dkg/controller.rs index 26ddc308ed..9058dc558b 100644 --- a/validator-api/src/coconut/dkg/controller.rs +++ b/validator-api/src/coconut/dkg/controller.rs @@ -3,6 +3,9 @@ use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::state::{ConsistentState, State}; +use crate::coconut::dkg::verification_key::{ + verification_key_finalization, verification_key_validation, +}; use crate::coconut::dkg::{ dealing::dealing_exchange, public_key::public_key_submission, verification_key::verification_key_submission, @@ -14,6 +17,7 @@ use coconut_dkg_common::types::EpochState; use dkg::bte::keys::KeyPair as DkgKeyPair; use rand::rngs::OsRng; use rand::RngCore; +use std::path::PathBuf; use std::time::Duration; use task::ShutdownListener; use tokio::time::interval; @@ -35,13 +39,15 @@ pub(crate) fn init_keypair(config: &Config) -> Result<()> { pub(crate) struct DkgController { dkg_client: DkgClient, + secret_key_path: PathBuf, + verification_key_path: PathBuf, state: State, rng: R, polling_rate: Duration, } impl DkgController { - pub(crate) fn new( + pub(crate) async fn new( config: &Config, nymd_client: nymd_client::Client, coconut_keypair: CoconutKeyPair, @@ -51,20 +57,28 @@ impl DkgController { config.decryption_key_path(), config.public_key_with_proof_path(), ))?; + if let Ok(coconut_keypair_value) = pemstore::load_keypair(&pemstore::KeyPairPath::new( + config.secret_key_path(), + config.verification_key_path(), + )) { + coconut_keypair.set(coconut_keypair_value).await; + } Ok(DkgController { dkg_client: DkgClient::new(nymd_client), - state: State::new(dkg_keypair, coconut_keypair), + secret_key_path: config.secret_key_path(), + verification_key_path: config.verification_key_path(), + state: State::new(config.get_announce_address(), dkg_keypair, coconut_keypair), rng, polling_rate: config.get_dkg_contract_polling_rate(), }) } - async fn handle_epoch_state(&mut self) -> bool { + async fn handle_epoch_state(&mut self) { 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) { + if let Err(e) = self.state.is_consistent(epoch_state).await { error!( "Epoch state is corrupted - {}, the process should be terminated", e @@ -78,28 +92,38 @@ impl DkgController { dealing_exchange(&self.dkg_client, &mut self.state, self.rng.clone()).await } EpochState::VerificationKeySubmission => { - verification_key_submission(&self.dkg_client, &mut self.state).await + let keypair_path = pemstore::KeyPairPath::new( + self.secret_key_path.clone(), + self.verification_key_path.clone(), + ); + verification_key_submission( + &self.dkg_client, + &mut self.state, + &keypair_path, + ) + .await } - EpochState::InProgress => return true, + EpochState::VerificationKeyValidation => { + verification_key_validation(&self.dkg_client, &mut self.state).await + } + EpochState::VerificationKeyFinalization => { + verification_key_finalization(&self.dkg_client, &mut self.state).await + } + // Just wait, in case we need to redo dkg at some point + EpochState::InProgress => Ok(()), }; 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; - } - } + _ = interval.tick() => self.handle_epoch_state().await, _ = shutdown.recv() => { trace!("DkgController: Received shutdown"); } diff --git a/validator-api/src/coconut/dkg/dealing.rs b/validator-api/src/coconut/dkg/dealing.rs index 647df75797..fea2d4e98f 100644 --- a/validator-api/src/coconut/dkg/dealing.rs +++ b/validator-api/src/coconut/dkg/dealing.rs @@ -16,7 +16,6 @@ pub(crate) async fn dealing_exchange( rng: impl RngCore + Clone, ) -> Result<(), CoconutError> { if state.receiver_index().is_some() { - info!("Already have index {}", state.receiver_index().unwrap()); return Ok(()); } @@ -46,7 +45,7 @@ pub(crate) async fn dealing_exchange( .await?; } - info!("Setting index to {}", receiver_index.unwrap()); + info!("DKG: Finished submitting dealing"); state.set_receiver_index(receiver_index); Ok(()) diff --git a/validator-api/src/coconut/dkg/public_key.rs b/validator-api/src/coconut/dkg/public_key.rs index de1c8c86e6..6669db09ca 100644 --- a/validator-api/src/coconut/dkg/public_key.rs +++ b/validator-api/src/coconut/dkg/public_key.rs @@ -21,10 +21,12 @@ pub(crate) async fn public_key_submission( { details.assigned_index } else { - dkg_client.register_dealer(bte_key).await? + dkg_client + .register_dealer(bte_key, state.announce_address().to_string()) + .await? }; state.set_node_index(index); - info!("Starting dkg protocol with index {}", index); + info!("DKG: Using node index {}", index); Ok(()) } diff --git a/validator-api/src/coconut/dkg/state.rs b/validator-api/src/coconut/dkg/state.rs index 43098d771b..f6be2bc8a7 100644 --- a/validator-api/src/coconut/dkg/state.rs +++ b/validator-api/src/coconut/dkg/state.rs @@ -8,8 +8,9 @@ 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 dkg::{NodeIndex, RecoveredVerificationKeys, Threshold}; use std::collections::BTreeMap; +use url::Url; // note: each dealer is also a receiver which simplifies some logic significantly #[derive(Debug)] @@ -37,40 +38,50 @@ impl TryFrom for DkgParticipant { } } +#[async_trait] pub(crate) trait ConsistentState { fn node_index_value(&self) -> Result; fn receiver_index_value(&self) -> Result; fn threshold(&self) -> Result; - fn is_consistent(&self, epoch_state: EpochState) -> Result<(), CoconutError> { + async fn coconut_keypair_is_some(&self) -> Result<(), CoconutError>; + fn proposal_id_value(&self) -> Result; + async 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()?; + EpochState::VerificationKeyValidation => { + self.coconut_keypair_is_some().await?; } + EpochState::VerificationKeyFinalization => { + self.proposal_id_value()?; + } + EpochState::InProgress => {} } Ok(()) } } pub(crate) struct State { + announce_address: Url, dkg_keypair: DkgKeyPair, coconut_keypair: CoconutKeyPair, node_index: Option, dealers: BTreeMap>, receiver_index: Option, threshold: Option, + recovered_vks: Vec, + proposal_id: Option, + voted_vks: bool, + executed_proposal: bool, } +#[async_trait] impl ConsistentState for State { fn node_index_value(&self) -> Result { self.node_index.ok_or(CoconutError::UnrecoverableState { @@ -98,20 +109,49 @@ impl ConsistentState for State { Ok(threshold) } } + + async fn coconut_keypair_is_some(&self) -> Result<(), CoconutError> { + if self.coconut_keypair_is_some().await { + Ok(()) + } else { + Err(CoconutError::UnrecoverableState { + reason: String::from("Coconut keypair should have been set"), + }) + } + } + + fn proposal_id_value(&self) -> Result { + self.proposal_id.ok_or(CoconutError::UnrecoverableState { + reason: String::from("Proposal id should have benn set"), + }) + } } impl State { - pub fn new(dkg_keypair: DkgKeyPair, coconut_keypair: CoconutKeyPair) -> Self { + pub fn new( + announce_address: Url, + dkg_keypair: DkgKeyPair, + coconut_keypair: CoconutKeyPair, + ) -> Self { State { + announce_address, dkg_keypair, coconut_keypair, node_index: None, dealers: BTreeMap::new(), receiver_index: None, threshold: None, + recovered_vks: vec![], + proposal_id: None, + voted_vks: false, + executed_proposal: false, } } + pub fn announce_address(&self) -> &Url { + &self.announce_address + } + pub fn dkg_keypair(&self) -> &DkgKeyPair { &self.dkg_keypair } @@ -154,6 +194,22 @@ impl State { .collect() } + pub fn recovered_vks(&self) -> &Vec { + &self.recovered_vks + } + + pub fn voted_vks(&self) -> bool { + self.voted_vks + } + + pub fn executed_proposal(&self) -> bool { + self.executed_proposal + } + + pub fn set_recovered_vks(&mut self, recovered_vks: Vec) { + self.recovered_vks = recovered_vks; + } + pub async fn set_coconut_keypair(&mut self, coconut_keypair: coconut_interface::KeyPair) { self.coconut_keypair.set(coconut_keypair).await } @@ -187,4 +243,16 @@ impl State { pub fn set_threshold(&mut self, threshold: Threshold) { self.threshold = Some(threshold); } + + pub fn set_proposal_id(&mut self, proposal_id: u64) { + self.proposal_id = Some(proposal_id); + } + + pub fn set_voted_vks(&mut self) { + self.voted_vks = true; + } + + pub fn set_executed_proposal(&mut self) { + self.executed_proposal = true; + } } diff --git a/validator-api/src/coconut/dkg/verification_key.rs b/validator-api/src/coconut/dkg/verification_key.rs index a900708fd2..08c352255d 100644 --- a/validator-api/src/coconut/dkg/verification_key.rs +++ b/validator-api/src/coconut/dkg/verification_key.rs @@ -5,41 +5,34 @@ 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::event_attributes::DKG_PROPOSAL_ID; use coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS}; +use coconut_dkg_common::verification_key::owner_from_cosmos_msgs; use coconut_interface::KeyPair as CoconutKeyPair; use cosmwasm_std::Addr; use credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; +use cw3::{ProposalResponse, Status}; use dkg::bte::{decrypt_share, setup}; -use dkg::{combine_shares, Dealing}; -use nymcoconut::{KeyPair, Parameters, SecretKey}; +use dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold}; +use nymcoconut::tests::helpers::transpose_matrix; +use nymcoconut::{check_vk_pairing, Base58, KeyPair, Parameters, SecretKey, VerificationKey}; +use pemstore::KeyPairPath; use std::collections::BTreeMap; +use validator_client::nymd::cosmwasm_client::logs::find_attribute; // 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, + threshold: Threshold, ) -> Result>, CoconutError> { let mut dealings_maps = vec![]; let initial_dealers_by_addr = state.current_dealers_by_addr(); let initial_receivers = state.current_dealers_by_idx(); - let threshold = state.threshold()?; let params = setup(); - let retries = 3; for idx in 0..TOTAL_DEALINGS { - let mut try_no = 0; - let dealings = loop { - // this is a really ugly way to get the dealings, but for some reason the first query - // always fails with a RPC error. - try_no += 1; - if let Ok(dealings) = dkg_client.get_dealings(idx).await { - break dealings; - } else if try_no == retries { - return Err(CoconutError::UnrecoverableState { - reason: String::from("Could not get dealings"), - }); - } - }; + let dealings = dkg_client.get_dealings(idx).await?; let dealings_map = BTreeMap::from_iter(dealings.into_iter().filter_map(|contract_dealing| { match Dealing::try_from(&contract_dealing.dealing) { @@ -84,7 +77,8 @@ async fn deterministic_filter_dealers( } fn derive_partial_keypair( - state: &State, + state: &mut State, + threshold: Threshold, dealings_maps: Vec>, ) -> Result { let filtered_receivers_by_idx = state.current_dealers_by_idx(); @@ -92,6 +86,7 @@ fn derive_partial_keypair( let dk = state.dkg_keypair().private_key(); let node_index_value = state.receiver_index_value()?; let mut scalars = vec![]; + let mut recovered_vks = vec![]; for dealings_map in dealings_maps.into_iter() { let filtered_dealings: Vec<_> = dealings_map .into_iter() @@ -103,6 +98,13 @@ fn derive_partial_keypair( } }) .collect(); + let recovered = try_recover_verification_keys( + &filtered_dealings, + threshold, + &filtered_receivers_by_idx, + )?; + recovered_vks.push(recovered); + let shares = filtered_dealings .iter() .map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None)) @@ -116,6 +118,7 @@ fn derive_partial_keypair( )?; scalars.push(scalar); } + state.set_recovered_vks(recovered_vks); let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?; let x = scalars.pop().unwrap(); @@ -128,14 +131,114 @@ fn derive_partial_keypair( pub(crate) async fn verification_key_submission( dkg_client: &DkgClient, state: &mut State, + keypair_path: &KeyPairPath, ) -> 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)?; + let threshold = state.threshold()?; + let dealings_maps = deterministic_filter_dealers(dkg_client, state, threshold).await?; + let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?; + let vk_share = coconut_keypair.verification_key().to_bs58(); + pemstore::store_keypair(&coconut_keypair, keypair_path)?; + let res = dkg_client.submit_verification_key_share(vk_share).await?; + let proposal_id = find_attribute(&res.logs, "wasm", DKG_PROPOSAL_ID) + .ok_or(CoconutError::ProposalIdError { + reason: String::from("proposal id not found"), + })? + .value + .parse::() + .map_err(|_| CoconutError::ProposalIdError { + reason: String::from("proposal id could not be parsed to u64"), + })?; + state.set_proposal_id(proposal_id); state.set_coconut_keypair(coconut_keypair).await; + info!("DKG: Submitted own verification key"); + + Ok(()) +} + +fn validate_proposal(proposal: &ProposalResponse) -> Option<(Addr, u64)> { + if proposal.status == Status::Open { + if let Some(owner) = owner_from_cosmos_msgs(&proposal.msgs) { + return Some((owner, proposal.id)); + } + } + None +} + +pub(crate) async fn verification_key_validation( + dkg_client: &DkgClient, + state: &mut State, +) -> Result<(), CoconutError> { + if state.voted_vks() { + return Ok(()); + } + + let vk_shares = dkg_client.get_verification_key_shares().await?; + let proposal_ids = BTreeMap::from_iter( + dkg_client + .list_proposals() + .await? + .iter() + .filter_map(validate_proposal), + ); + let filtered_receivers_by_idx: Vec<_> = + state.current_dealers_by_idx().keys().copied().collect(); + let recovered_partials: Vec<_> = state + .recovered_vks() + .iter() + .map(|recovered_vk| recovered_vk.recovered_partials.clone()) + .collect(); + let recovered_partials = transpose_matrix(recovered_partials); + let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?; + for contract_share in vk_shares { + if let Some(proposal_id) = proposal_ids.get(&contract_share.owner).copied() { + match VerificationKey::try_from_bs58(contract_share.share) { + Ok(vk) => { + if let Some(idx) = filtered_receivers_by_idx + .iter() + .position(|node_index| contract_share.node_index == *node_index) + { + if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) { + dkg_client + .vote_verification_key_share(proposal_id, false) + .await?; + } else { + dkg_client + .vote_verification_key_share(proposal_id, true) + .await?; + } + } + } + Err(_) => { + dkg_client + .vote_verification_key_share(proposal_id, false) + .await? + } + } + } + } + state.set_voted_vks(); + info!("DKG: Validated the other verification keys"); + Ok(()) +} + +pub(crate) async fn verification_key_finalization( + dkg_client: &DkgClient, + state: &mut State, +) -> Result<(), CoconutError> { + if state.executed_proposal() { + return Ok(()); + } + + let proposal_id = state.proposal_id_value()?; + dkg_client + .execute_verification_key_share(proposal_id) + .await?; + state.set_executed_proposal(); + info!("DKG: Finalized own verification key on chain"); Ok(()) } diff --git a/validator-api/src/coconut/error.rs b/validator-api/src/coconut/error.rs index 4bf9ba0343..9b6fadf957 100644 --- a/validator-api/src/coconut/error.rs +++ b/validator-api/src/coconut/error.rs @@ -20,6 +20,9 @@ pub type Result = std::result::Result; #[derive(Debug, Error)] pub enum CoconutError { + #[error("{0}")] + IOError(#[from] std::io::Error), + #[error("Could not parse Ed25519 data")] Ed25519ParseError(#[from] Ed25519RecoveryError), @@ -93,6 +96,9 @@ pub enum CoconutError { #[error("DKG has not finished yet in order to derive the coconut key")] KeyPairNotDerivedYet, + + #[error("There was a problem with the proposal id: {reason}")] + ProposalIdError { reason: String }, } impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError { diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index 7902aea36a..94bc96608d 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -31,8 +31,7 @@ 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, + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use validator_client::nymd::{Coin, Fee}; use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES}; @@ -186,8 +185,6 @@ impl InternalSignRequest { ), routes![ post_blind_sign, - get_verification_key, - get_cosmos_address, post_partial_bandwidth_credential, verify_bandwidth_credential ], @@ -258,26 +255,6 @@ pub async fn post_partial_bandwidth_credential( Ok(Json(v)) } -#[get("/verification-key")] -pub async fn get_verification_key( - state: &RocketState, -) -> Result> { - state - .key_pair - .get() - .await - .as_ref() - .map(|keypair| Json(VerificationKeyResponse::new(keypair.verification_key()))) - .ok_or(CoconutError::KeyPairNotDerivedYet) -} - -#[get("/cosmos-address")] -pub async fn get_cosmos_address(state: &RocketState) -> Result> { - Ok(Json(CosmosAddressResponse::new( - state.client.address().await, - ))) -} - #[post("/verify-bandwidth-credential", data = "")] pub async fn verify_bandwidth_credential( verify_credential_body: Json, diff --git a/validator-api/src/coconut/tests.rs b/validator-api/src/coconut/tests.rs index 2f37a5ab0c..842194b913 100644 --- a/validator-api/src/coconut/tests.rs +++ b/validator-api/src/coconut/tests.rs @@ -19,21 +19,18 @@ use credentials::coconut::params::{ }; use crypto::shared_key::recompute_shared_key; use crypto::symmetric::stream_cipher; -use multisig_contract_common::msg::ProposalResponse; use nymcoconut::tests::helpers::theta_from_keys_and_attributes; use nymcoconut::{ - prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters, + prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, Parameters, }; use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse, - VerifyCredentialBody, VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use validator_client::nymd::Coin; use validator_client::nymd::{tx::Hash, AccountId, DeliverTx, Event, Fee, Tag, TxResponse}; use validator_client::validator_api::routes::{ - API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_COSMOS_ADDRESS, - COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, COCONUT_ROUTES, COCONUT_VERIFICATION_KEY, - COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, + API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, + COCONUT_ROUTES, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, }; use crate::coconut::State; @@ -41,8 +38,10 @@ use crate::ValidatorApiStorage; use async_trait::async_trait; use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; +use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; use crypto::asymmetric::{encryption, identity}; +use cw3::ProposalResponse; use rand_07::rngs::OsRng; use rocket::http::Status; use rocket::local::asynchronous::Client; @@ -107,6 +106,10 @@ impl super::client::Client for DummyClient { }) } + async fn list_proposals(&self) -> Result> { + todo!() + } + async fn get_spent_credential( &self, blinded_serial_number: String, @@ -133,7 +136,11 @@ impl super::client::Client for DummyClient { todo!() } - async fn get_dealings(&self, idx: usize) -> Result> { + async fn get_dealings(&self, _idx: usize) -> Result> { + todo!() + } + + async fn get_verification_key_shares(&self) -> Result> { todo!() } @@ -153,14 +160,26 @@ impl super::client::Client for DummyClient { Ok(()) } + async fn execute_proposal(&self, _proposal_id: u64) -> Result<()> { + todo!() + } + async fn register_dealer( &self, - bte_key: EncodedBTEPublicKeyWithProof, + _bte_key: EncodedBTEPublicKeyWithProof, + _announce_address: String, ) -> Result { todo!() } - async fn submit_dealing(&self, dealing_bytes: ContractSafeBytes) -> Result { + async fn submit_dealing(&self, _dealing_bytes: ContractSafeBytes) -> Result { + todo!() + } + + async fn submit_verification_key_share( + &self, + _share: VerificationKeyShare, + ) -> Result { todo!() } } @@ -205,65 +224,6 @@ pub fn tx_entry_fixture(tx_hash: &str) -> TxResponse { } } -async fn check_signer_verif_key(key_pair: KeyPair) { - let verification_key = key_pair.verification_key(); - - let mut db_dir = std::env::temp_dir(); - db_dir.push(&verification_key.to_bs58()[..8]); - let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); - let nymd_client = DummyClient::new( - AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(), - &Arc::new(RwLock::new(HashMap::new())), - &Arc::new(RwLock::new(HashMap::new())), - &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(), - staged_key_pair, - comm_channel, - storage, - )); - - let client = Client::tracked(rocket) - .await - .expect("valid rocket instance"); - - let response = client - .get(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFICATION_KEY - )) - .dispatch() - .await; - assert_eq!(response.status(), Status::Ok); - - // This is a more direct way, but there's a bug which makes it hang https://github.com/SergioBenitez/Rocket/issues/1893 - // assert!(response - // .into_json::() - // .await - // .is_some()); - let verification_key_response = - serde_json::from_str::(&response.into_string().await.unwrap()) - .unwrap(); - assert_eq!(verification_key_response.key, verification_key); -} - -#[tokio::test] -async fn multiple_verification_key() { - let params = Parameters::new(4).unwrap(); - let num_authorities = 4; - - let key_pairs = ttp_keygen(¶ms, num_authorities, num_authorities).unwrap(); - for key_pair in key_pairs.into_iter() { - check_signer_verif_key(key_pair).await; - } -} - #[tokio::test] async fn signed_before() { let tx_hash = @@ -697,49 +657,6 @@ async fn signature_test() { ); } -#[tokio::test] -async fn get_cosmos_address() { - let validator_address = AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap(); - let nymd_client = DummyClient::new( - validator_address.clone(), - &Arc::new(RwLock::new(HashMap::new())), - &Arc::new(RwLock::new(HashMap::new())), - &Arc::new(RwLock::new(HashMap::new())), - ); - let mut db_dir = std::env::temp_dir(); - let key_pair = ttp_keygen(&Parameters::new(4).unwrap(), 1, 1) - .unwrap() - .remove(0); - 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(), - staged_key_pair, - comm_channel, - storage.clone(), - )); - let client = Client::tracked(rocket) - .await - .expect("valid rocket instance"); - - let response = client - .get(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_COSMOS_ADDRESS - )) - .dispatch() - .await; - assert_eq!(response.status(), Status::Ok); - let cosmos_addr_response = - serde_json::from_str::(&response.into_string().await.unwrap()) - .unwrap(); - assert_eq!(validator_address, cosmos_addr_response.addr); -} - #[tokio::test] async fn verification_of_bandwidth_credential() { // Setup variables diff --git a/validator-api/src/config/mod.rs b/validator-api/src/config/mod.rs index 6b6196eb41..e08b3bb20a 100644 --- a/validator-api/src/config/mod.rs +++ b/validator-api/src/config/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; +use config::defaults::DEFAULT_VALIDATOR_API_PORT; use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -93,6 +94,11 @@ pub struct Base { local_validator: Url, + /// Address announced to the directory server for the clients to connect to. + // It is useful, say, in NAT scenarios or wanting to more easily update actual IP address + // later on by using name resolvable with a DNS query, such as `nymtech.net`. + announce_address: Url, + /// Address of the validator contract managing the network mixnet_contract_address: String, @@ -102,11 +108,17 @@ pub struct Base { impl Default for Base { fn default() -> Self { + let default_validator: Url = DEFAULT_LOCAL_VALIDATOR + .parse() + .expect("default local validator is malformed!"); + let mut default_announce_address = default_validator.clone(); + default_announce_address + .set_port(Some(DEFAULT_VALIDATOR_API_PORT)) + .expect("default local validator is malformed!"); Base { id: String::default(), - local_validator: DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"), + local_validator: default_validator, + announce_address: default_announce_address, mixnet_contract_address: String::default(), mnemonic: "exact antique hybrid width raise anchor puzzle degree fee quit long crack net vague hip despair write put useless civil mechanic broom music day".to_string(), } @@ -270,8 +282,11 @@ pub struct CoconutSigner { /// Specifies whether rewarding service is enabled in this process. enabled: bool, - /// Path to the signing keypair - keypair_path: PathBuf, + /// Path to the coconut verification key. + verification_key_path: PathBuf, + + /// Path to the coconut secret key. + secret_key_path: PathBuf, /// Path to the dkg dealer decryption key. decryption_key_path: PathBuf, @@ -281,16 +296,21 @@ pub struct CoconutSigner { /// 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 - all_validator_apis: Vec, } impl CoconutSigner { pub const DKG_DECRYPTION_KEY_FILE: &'static str = "dkg_decryption_key.pem"; pub const DKG_PUBLIC_KEY_WITH_PROOF_FILE: &'static str = "dkg_public_key_with_proof.pem"; + pub const COCONUT_VERIFICATION_KEY_FILE: &'static str = "coconut_verification_key.pem"; + pub const COCONUT_SECRET_KEY_FILE: &'static str = "coconut_secret_key.pem"; + + fn default_coconut_verification_key_path() -> PathBuf { + Config::default_data_directory(None).join(Self::COCONUT_VERIFICATION_KEY_FILE) + } + + fn default_coconut_secret_key_path() -> PathBuf { + Config::default_data_directory(None).join(Self::COCONUT_SECRET_KEY_FILE) + } fn default_dkg_decryption_key_path() -> PathBuf { Config::default_data_directory(None).join(Self::DKG_DECRYPTION_KEY_FILE) @@ -305,11 +325,11 @@ impl Default for CoconutSigner { fn default() -> Self { Self { enabled: Default::default(), - keypair_path: Default::default(), + verification_key_path: CoconutSigner::default_coconut_verification_key_path(), + secret_key_path: CoconutSigner::default_coconut_secret_key_path(), 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(), } } } @@ -325,6 +345,10 @@ impl Config { Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE); self.network_monitor.credentials_database_path = Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE); + self.coconut_signer.verification_key_path = Config::default_data_directory(Some(id)) + .join(CoconutSigner::COCONUT_VERIFICATION_KEY_FILE); + self.coconut_signer.secret_key_path = + Config::default_data_directory(Some(id)).join(CoconutSigner::COCONUT_SECRET_KEY_FILE); self.coconut_signer.decryption_key_path = Config::default_data_directory(Some(id)).join(CoconutSigner::DKG_DECRYPTION_KEY_FILE); self.coconut_signer.public_key_with_proof_path = Config::default_data_directory(Some(id)) @@ -358,6 +382,12 @@ impl Config { self } + #[cfg(feature = "coconut")] + pub fn with_announce_address(mut self, announce_address: Url) -> Self { + self.base.announce_address = announce_address; + self + } + pub fn with_custom_mixnet_contract>(mut self, mixnet_contract: S) -> Self { self.base.mixnet_contract_address = mixnet_contract.into(); self @@ -368,18 +398,6 @@ impl Config { self } - #[cfg(feature = "coconut")] - pub fn with_keypair_path(mut self, keypair_path: PathBuf) -> Self { - self.coconut_signer.keypair_path = keypair_path; - self - } - - #[cfg(feature = "coconut")] - pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec) -> Self { - self.coconut_signer.all_validator_apis = validator_api_urls; - self - } - pub fn with_minimum_interval_monitor_threshold(mut self, threshold: u8) -> Self { self.rewarding.minimum_interval_monitor_threshold = threshold; self @@ -426,6 +444,11 @@ impl Config { self.base.local_validator.clone() } + #[cfg(feature = "coconut")] + pub fn get_announce_address(&self) -> Url { + self.base.announce_address.clone() + } + pub fn get_mixnet_contract_address(&self) -> String { self.base.mixnet_contract_address.clone() } @@ -487,8 +510,13 @@ impl Config { } #[cfg(feature = "coconut")] - pub fn _keypair_path(&self) -> PathBuf { - self.coconut_signer.keypair_path.clone() + pub fn verification_key_path(&self) -> PathBuf { + self.coconut_signer.verification_key_path.clone() + } + + #[cfg(feature = "coconut")] + pub fn secret_key_path(&self) -> PathBuf { + self.coconut_signer.secret_key_path.clone() } #[cfg(feature = "coconut")] @@ -506,12 +534,6 @@ impl Config { 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 { - self.coconut_signer.all_validator_apis.clone() - } - // TODO: Remove if still unused #[allow(dead_code)] pub fn get_minimum_interval_monitor_threshold(&self) -> u8 { diff --git a/validator-api/src/config/template.rs b/validator-api/src/config/template.rs index fdb9ae6258..3d745f30ed 100644 --- a/validator-api/src/config/template.rs +++ b/validator-api/src/config/template.rs @@ -16,6 +16,11 @@ id = '{{ base.id }}' # Validator server to which the API will be getting information about the network. local_validator = '{{ base.local_validator }}' +# Address announced to the directory server for the clients to connect to. +# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address +# later on by using name resolvable with a DNS query, such as `nymtech.net`. +announce_address = '{{ base.announce_address }}' + # Address of the validator contract managing the network. mixnet_contract_address = '{{ base.mixnet_contract_address }}' @@ -93,8 +98,11 @@ minimum_interval_monitor_threshold = {{ rewarding.minimum_interval_monitor_thres # Specifies whether rewarding service is enabled in this process. enabled = {{ coconut_signer.enabled }} -# Path to the signing keypair -keypair_path = '{{ coconut_signer.keypair_path }}' +# Path to the coconut verification key +verification_key_path = '{{ coconut_signer.verification_key_path }}' + +# Path to the coconut verification key +secret_key_path = '{{ coconut_signer.secret_key_path }}' # Path to the dkg dealer decryption key decryption_key_path = '{{ coconut_signer.decryption_key_path }}' @@ -102,14 +110,5 @@ decryption_key_path = '{{ coconut_signer.decryption_key_path }}' # Path to the dkg dealer public key with proof public_key_with_proof_path = '{{ coconut_signer.public_key_with_proof_path }}' -# 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 -all_validator_apis = [ - {{#each coconut_signer.all_validator_apis }} - '{{this}}', - {{/each}} -] - "# } diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index ff44f6057e..5116896858 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -12,8 +12,6 @@ use crate::nymd_client::Client; use crate::storage::ValidatorApiStorage; use ::config::defaults::mainnet::read_var_if_not_default; use ::config::defaults::setup_env; -#[cfg(feature = "coconut")] -use ::config::defaults::var_names::API_VALIDATOR; use ::config::defaults::var_names::{CONFIGURED, MIXNET_CONTRACT_ADDRESS, MIX_DENOM}; use ::config::NymConfig; use anyhow::Result; @@ -35,6 +33,8 @@ use std::time::Duration; use std::{fs, process}; use task::ShutdownNotifier; use tokio::sync::Notify; +#[cfg(feature = "coconut")] +use url::Url; use validator_client::nymd::SigningNymdClient; use crate::epoch_operations::RewardedSetUpdater; @@ -71,9 +71,7 @@ const NYMD_VALIDATOR_ARG: &str = "nymd-validator"; const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode"; #[cfg(feature = "coconut")] -const API_VALIDATORS_ARG: &str = "api-validators"; -#[cfg(feature = "coconut")] -const KEYPAIR_ARG: &str = "keypair"; +const ANNOUNCE_ADDRESS: &str = "announce-address"; #[cfg(feature = "coconut")] const COCONUT_ENABLED: &str = "enable-coconut"; @@ -193,21 +191,15 @@ fn parse_args() -> ArgMatches { #[cfg(feature = "coconut")] let base_app = base_app .arg( - Arg::with_name(KEYPAIR_ARG) - .help("Path to the secret key file") - .takes_value(true) - .long(KEYPAIR_ARG), - ) - .arg( - Arg::with_name(API_VALIDATORS_ARG) - .help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered") - .long(API_VALIDATORS_ARG) - .takes_value(true) + Arg::with_name(ANNOUNCE_ADDRESS) + .help("Announced address where coconut clients will connect.") + .long(ANNOUNCE_ADDRESS) + .takes_value(true), ) .arg( Arg::with_name(COCONUT_ENABLED) .help("Flag to indicate whether coconut signer authority is enabled on this API") - .requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG, API_VALIDATORS_ARG]) + .requires_all(&[MNEMONIC_ARG, ANNOUNCE_ADDRESS]) .long(COCONUT_ENABLED), ); base_app.get_matches() @@ -276,12 +268,10 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { } #[cfg(feature = "coconut")] - if let Some(raw_validators) = matches.value_of(API_VALIDATORS_ARG) { - config = config.with_custom_validator_apis(::config::parse_validators(raw_validators)); - } else if std::env::var(CONFIGURED).is_ok() { - if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) { - config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) - } + if let Some(announce_address) = matches.value_of(ANNOUNCE_ADDRESS) { + config = config.with_announce_address( + Url::parse(announce_address).expect("Could not parse announce address"), + ); } if let Some(raw_validator) = matches.value_of(NYMD_VALIDATOR_ARG) { @@ -338,11 +328,6 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { ) } - #[cfg(feature = "coconut")] - if let Some(keypair_path) = matches.value_of(KEYPAIR_ARG) { - config = config.with_keypair_path(keypair_path.into()) - } - if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) { config = config.with_disabled_credentials_mode(false) } @@ -385,6 +370,7 @@ fn setup_liftoff_notify(notify: Arc) -> AdHoc { fn setup_network_monitor<'a>( config: &'a Config, + _nymd_client: Client, system_version: &str, rocket: &Rocket, ) -> Option> { @@ -398,6 +384,7 @@ fn setup_network_monitor<'a>( Some(NetworkMonitorBuilder::new( config, + _nymd_client, system_version, node_status_storage, validator_cache, @@ -453,10 +440,10 @@ async fn setup_rocket( #[cfg(feature = "coconut")] let rocket = if config.get_coconut_signer_enabled() { rocket.attach(InternalSignRequest::stage( - _nymd_client, + _nymd_client.clone(), _mix_denom, coconut_keypair, - QueryCommunicationChannel::new(config.get_all_validator_api_endpoints()), + QueryCommunicationChannel::new(_nymd_client), storage.clone().unwrap(), )) } else { @@ -554,7 +541,12 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> { coconut_keypair.clone(), ) .await?; - let monitor_builder = setup_network_monitor(&config, system_version, &rocket); + let monitor_builder = setup_network_monitor( + &config, + signing_nymd_client.clone(), + system_version, + &rocket, + ); let validator_cache = rocket.state::().unwrap().clone(); let node_status_cache = rocket.state::().unwrap().clone(); @@ -562,7 +554,8 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> { #[cfg(feature = "coconut")] { let dkg_controller = - DkgController::new(&config, signing_nymd_client.clone(), coconut_keypair, OsRng)?; + DkgController::new(&config, signing_nymd_client.clone(), coconut_keypair, OsRng) + .await?; let shutdown_listener = shutdown.subscribe(); tokio::spawn(async move { dkg_controller.run(shutdown_listener).await }); } diff --git a/validator-api/src/network_monitor/mod.rs b/validator-api/src/network_monitor/mod.rs index 57e1d64e61..8b85d5fd5a 100644 --- a/validator-api/src/network_monitor/mod.rs +++ b/validator-api/src/network_monitor/mod.rs @@ -7,6 +7,7 @@ use futures::channel::mpsc; use gateway_client::bandwidth::BandwidthController; use std::sync::Arc; use task::ShutdownNotifier; +use validator_client::nymd::SigningNymdClient; use crate::config::Config; use crate::contract_cache::ValidatorCache; @@ -20,6 +21,7 @@ use crate::network_monitor::monitor::receiver::{ use crate::network_monitor::monitor::sender::PacketSender; use crate::network_monitor::monitor::summary_producer::SummaryProducer; use crate::network_monitor::monitor::Monitor; +use crate::nymd_client::Client; use crate::storage::ValidatorApiStorage; pub(crate) mod chunker; @@ -32,6 +34,7 @@ pub(crate) const ROUTE_TESTING_TEST_NONCE: u64 = 0; pub(crate) struct NetworkMonitorBuilder<'a> { config: &'a Config, + _nymd_client: Client, system_version: String, node_status_storage: ValidatorApiStorage, validator_cache: ValidatorCache, @@ -40,12 +43,14 @@ pub(crate) struct NetworkMonitorBuilder<'a> { impl<'a> NetworkMonitorBuilder<'a> { pub(crate) fn new( config: &'a Config, + _nymd_client: Client, system_version: &str, node_status_storage: ValidatorApiStorage, validator_cache: ValidatorCache, ) -> Self { NetworkMonitorBuilder { config, + _nymd_client, system_version: system_version.to_string(), node_status_storage, validator_cache, @@ -74,11 +79,18 @@ impl<'a> NetworkMonitorBuilder<'a> { ); #[cfg(feature = "coconut")] - let bandwidth_controller = BandwidthController::new( - credential_storage::initialise_storage(self.config.get_credentials_database_path()) - .await, - self.config.get_all_validator_api_endpoints(), - ); + let bandwidth_controller = { + let client = self._nymd_client.0.read().await; + let coconut_api_clients = + validator_client::CoconutApiClient::all_coconut_api_clients(&client) + .await + .expect("Could not query api clients"); + BandwidthController::new( + credential_storage::initialise_storage(self.config.get_credentials_database_path()) + .await, + coconut_api_clients, + ) + }; #[cfg(not(feature = "coconut"))] let bandwidth_controller = BandwidthController::new( credential_storage::initialise_storage(self.config.get_credentials_database_path()) diff --git a/validator-api/src/network_monitor/monitor/sender.rs b/validator-api/src/network_monitor/monitor/sender.rs index 3689a4b6db..4ec0e9ce45 100644 --- a/validator-api/src/network_monitor/monitor/sender.rs +++ b/validator-api/src/network_monitor/monitor/sender.rs @@ -90,15 +90,6 @@ struct FreshGatewayClientData { gateways_status_updater: GatewayClientUpdateSender, local_identity: Arc, gateway_response_timeout: Duration, - - // I guess in the future this struct will require aggregated verification key and.... - // ... something for obtaining actual credential - - // TODO: - // SECURITY: - // for coconut bandwidth credentials we currently have no double spending protection, just to - // get things running we're re-using the same credential for all gateways all the time. - // THIS IS VERY BAD!! bandwidth_controller: BandwidthController, disabled_credentials_mode: bool, } @@ -140,8 +131,6 @@ pub(crate) struct PacketSender { // behaviour is unlikely. active_gateway_clients: ActiveGatewayClients, - // I guess that will be required later on if credentials are got per gateway - // aggregated_verification_key: Arc, fresh_gateway_client_data: Arc, gateway_connection_timeout: Duration, max_concurrent_clients: usize, diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index d2fd06acdb..0abefc0875 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -30,9 +30,11 @@ use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsRe #[cfg(feature = "coconut")] use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; #[cfg(feature = "coconut")] +use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; +#[cfg(feature = "coconut")] use contracts_common::dealings::ContractSafeBytes; #[cfg(feature = "coconut")] -use multisig_contract_common::msg::ProposalResponse; +use cw3::ProposalResponse; #[cfg(feature = "coconut")] use validator_client::nymd::{ cosmwasm_client::types::ExecuteResult, @@ -291,6 +293,10 @@ where Ok(self.0.read().await.nymd.get_proposal(proposal_id).await?) } + async fn list_proposals(&self) -> crate::coconut::error::Result> { + Ok(self.0.read().await.get_all_nymd_proposals().await?) + } + async fn get_spent_credential( &self, blinded_serial_number: String, @@ -332,6 +338,17 @@ where Ok(self.0.read().await.get_all_nymd_epoch_dealings(idx).await?) } + async fn get_verification_key_shares( + &self, + ) -> crate::coconut::error::Result> { + Ok(self + .0 + .read() + .await + .get_all_nymd_verification_key_shares() + .await?) + } + async fn vote_proposal( &self, proposal_id: u64, @@ -347,16 +364,27 @@ where Ok(()) } + async fn execute_proposal(&self, proposal_id: u64) -> crate::coconut::error::Result<()> { + self.0 + .read() + .await + .nymd + .execute_proposal(proposal_id, None) + .await?; + Ok(()) + } + async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, + announce_address: String, ) -> Result { Ok(self .0 .write() .await .nymd - .register_dealer(bte_key, None) + .register_dealer(bte_key, announce_address, None) .await?) } @@ -372,4 +400,17 @@ where .submit_dealing_bytes(dealing_bytes, None) .await?) } + + async fn submit_verification_key_share( + &self, + share: VerificationKeyShare, + ) -> crate::coconut::error::Result { + Ok(self + .0 + .write() + .await + .nymd + .submit_verification_key_share(share, None) + .await?) + } }