Feature/spend coconut (#1261)

* Add coconut verifier structure for coconut protocol in gateway

* Add endpoint for validator-api cred verification

* Remove unused signature field

* Register new endpoint

* Improve validator-api config handling

* Aggregate verif result from all apis

* Simplify aggregate functions

* Verify cred on apis correctly

* Introduced coconut bandwidth contract to validator client

* Fix rebase double import

* Fix clippy on non-coconut

* Add multisig contract address to validator client

* Refactor Credential struct

* Do bincode magic in the coconut interface

* Implement serialization for credential and remove bindcode

* Fix clippy and don't remove dkg

* Client release funds proposal

* Add wrapper for blinded serial number

Also compare theta with a blinded serial number (in base 58 form)
for future double spend protection.

* Only post blinded serial number to blockchain

* Validator api propose credential spending

* Fix wallet

* Gateway calls proposal creation

* Query for proposal in verify coconut

* Remove db from git

* Verify against proposal description

* Validator apis vote based on verification of cred

* Fix wallet fmt

* Execute the release of funds

* Fix translation between token and bytes

* Update CHANGELOG
This commit is contained in:
Bogdan-Ștefan Neacşu
2022-06-02 16:54:59 +03:00
committed by GitHub
parent a5759ab227
commit 5f7b3db9a4
75 changed files with 1580 additions and 565 deletions
+7 -11
View File
@@ -1,13 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "coconut")]
use std::convert::TryFrom;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::error::Error;
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
@@ -22,12 +17,13 @@ impl Bandwidth {
}
#[cfg(feature = "coconut")]
impl TryFrom<Credential> for Bandwidth {
type Error = Error;
fn try_from(credential: Credential) -> Result<Self, Self::Error> {
let value = credential.voucher_value()?;
Ok(Self { value })
impl From<Credential> for Bandwidth {
fn from(credential: Credential) -> Self {
let token_value = credential.voucher_value();
let bandwidth_bytes = token_value * network_defaults::BYTES_PER_UTOKEN;
Bandwidth {
value: bandwidth_bytes,
}
}
}
@@ -42,8 +42,8 @@ pub(crate) enum RequestHandlingError {
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)]
UnsupportedBandwidthValue(u64),
#[error("Provided bandwidth credential did not verify correctly")]
InvalidBandwidthCredential,
#[error("Provided bandwidth credential did not verify correctly on {0}")]
InvalidBandwidthCredential(String),
#[error("This gateway is not running in the disabled credentials mode")]
NotInDisabledCredentialsMode,
@@ -65,8 +65,12 @@ pub(crate) enum RequestHandlingError {
NymdError(#[from] validator_client::nymd::error::NymdError),
#[cfg(feature = "coconut")]
#[error("Provided coconut bandwidth credential did not have expected structure - {0}")]
CoconutBandwidthCredentialError(#[from] credentials::error::Error),
#[error("Validator API error")]
APIError(#[from] validator_client::ValidatorClientError),
#[cfg(feature = "coconut")]
#[error("Not enough validator API endpoints provided. Needed {needed}, received {received}")]
NotEnoughValidatorAPIs { received: usize, needed: usize },
}
impl RequestHandlingError {
@@ -208,11 +212,55 @@ where
iv,
)?;
if !credential.verify(&self.inner.aggregated_verification_key) {
return Err(RequestHandlingError::InvalidBandwidthCredential);
if !credential.verify(
self.inner
.coconut_verifier
.as_ref()
.aggregated_verification_key(),
) {
return Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("credential failed to verify on gateway"),
));
}
let bandwidth = Bandwidth::try_from(credential)?;
let req = coconut_interface::ProposeReleaseFundsRequestBody::new(credential.clone());
let proposal_id = self
.inner
.coconut_verifier
.api_clients()
.get(0)
.ok_or(RequestHandlingError::NotEnoughValidatorAPIs {
needed: 1,
received: 0,
})?
.propose_release_funds(&req)
.await?
.proposal_id;
let req = coconut_interface::VerifyCredentialBody::new(credential.clone(), proposal_id);
for client in self.inner.coconut_verifier.api_clients().iter().skip(1) {
if !client
.verify_bandwidth_credential(&req)
.await?
.verification_result
{
debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.validator_api.current_url());
}
}
let req = coconut_interface::ExecuteReleaseFundsRequestBody::new(proposal_id);
self.inner
.coconut_verifier
.api_clients()
.get(0)
.ok_or(RequestHandlingError::NotEnoughValidatorAPIs {
needed: 1,
received: 0,
})?
.execute_release_funds(&req)
.await?;
let bandwidth = Bandwidth::from(credential);
let bandwidth_value = bandwidth.value();
if bandwidth_value > i64::MAX as u64 {
@@ -254,11 +302,15 @@ where
.inner
.check_local_identity(&credential.gateway_identity())
{
return Err(RequestHandlingError::InvalidBandwidthCredential);
return Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("gateway"),
));
}
if !credential.verify_signature() {
return Err(RequestHandlingError::InvalidBandwidthCredential);
return Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("gateway"),
));
}
debug!("Verifying Ethereum for token burn...");
let gateway_owner = self
@@ -0,0 +1,27 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_interface::VerificationKey;
use validator_client::ApiClient;
pub struct CoconutVerifier {
api_clients: Vec<ApiClient>,
aggregated_verification_key: VerificationKey,
}
impl CoconutVerifier {
pub fn new(api_clients: Vec<ApiClient>, aggregated_verification_key: VerificationKey) -> Self {
CoconutVerifier {
api_clients,
aggregated_verification_key,
}
}
pub fn api_clients(&self) -> &Vec<ApiClient> {
&self.api_clients
}
pub fn aggregated_verification_key(&self) -> &VerificationKey {
&self.aggregated_verification_key
}
}
@@ -39,16 +39,9 @@ impl ERC20Bridge {
.expect("The list of validators is empty");
let mnemonic =
Mnemonic::from_str(&cosmos_mnemonic).expect("Invalid Cosmos mnemonic provided");
let nymd_client = NymdClient::connect_with_mnemonic(
DEFAULT_NETWORK,
nymd_url.as_ref(),
AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).ok(),
None,
AccountId::from_str(DEFAULT_NETWORK.bandwidth_claim_contract_address()).ok(),
mnemonic,
None,
)
.expect("Could not create nymd client");
let nymd_client =
NymdClient::connect_with_mnemonic(DEFAULT_NETWORK, nymd_url.as_ref(), mnemonic, None)
.expect("Could not create nymd client");
ERC20Bridge {
contract: eth_contract(web3.clone()),
@@ -95,7 +88,9 @@ impl ERC20Bridge {
}
}
Err(RequestHandlingError::InvalidBandwidthCredential)
Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("gateway"),
))
}
pub(crate) async fn verify_gateway_owner(
@@ -103,17 +98,22 @@ impl ERC20Bridge {
gateway_owner: String,
gateway_identity: &PublicKey,
) -> Result<(), RequestHandlingError> {
let owner_address = AccountId::from_str(&gateway_owner)
.map_err(|_| RequestHandlingError::InvalidBandwidthCredential)?;
let owner_address = AccountId::from_str(&gateway_owner).map_err(|_| {
RequestHandlingError::InvalidBandwidthCredential(String::from("gateway"))
})?;
let gateway_bond = self
.nymd_client
.owns_gateway(&owner_address)
.await?
.ok_or(RequestHandlingError::InvalidBandwidthCredential)?;
.ok_or_else(|| {
RequestHandlingError::InvalidBandwidthCredential(String::from("gateway"))
})?;
if gateway_bond.gateway.identity_key == gateway_identity.to_base58_string() {
Ok(())
} else {
Err(RequestHandlingError::InvalidBandwidthCredential)
Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("gateway"),
))
}
}
@@ -121,9 +121,7 @@ impl ERC20Bridge {
&self,
credential: &TokenCredential,
) -> Result<(), RequestHandlingError> {
// It's ok to unwrap here, as the cosmos contract is set correctly
let erc20_bridge_contract_address =
self.nymd_client.erc20_bridge_contract_address().unwrap();
let bandwidth_claim_contract_address = self.nymd_client.bandwidth_claim_contract_address();
let req = ExecuteMsg::LinkPayment {
data: LinkPaymentData::new(
credential.verification_key().to_bytes(),
@@ -134,7 +132,7 @@ impl ERC20Bridge {
};
self.nymd_client
.execute(
erc20_bridge_contract_address,
bandwidth_claim_contract_address,
&req,
Default::default(),
"Linking payment",
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::node::client_handling::active_clients::ActiveClientsStore;
#[cfg(feature = "coconut")]
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
use crate::node::client_handling::websocket::connection_handler::{
AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream,
};
@@ -27,9 +29,6 @@ use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
#[cfg(feature = "coconut")]
use coconut_interface::VerificationKey;
#[cfg(not(feature = "coconut"))]
use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge;
@@ -77,11 +76,10 @@ pub(crate) struct FreshHandler<R, S, St> {
pub(crate) socket_connection: SocketStream<S>,
pub(crate) storage: St,
#[cfg(feature = "coconut")]
pub(crate) aggregated_verification_key: VerificationKey,
#[cfg(not(feature = "coconut"))]
pub(crate) erc20_bridge: Arc<ERC20Bridge>,
#[cfg(feature = "coconut")]
pub(crate) coconut_verifier: Arc<CoconutVerifier>,
}
impl<R, S, St> FreshHandler<R, S, St>
@@ -102,7 +100,7 @@ where
local_identity: Arc<identity::KeyPair>,
storage: St,
active_clients_store: ActiveClientsStore,
#[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey,
#[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>,
#[cfg(not(feature = "coconut"))] erc20_bridge: Arc<ERC20Bridge>,
) -> Self {
FreshHandler {
@@ -114,7 +112,7 @@ where
local_identity,
storage,
#[cfg(feature = "coconut")]
aggregated_verification_key,
coconut_verifier,
#[cfg(not(feature = "coconut"))]
erc20_bridge,
}
@@ -15,6 +15,8 @@ pub(crate) use self::authenticated::AuthenticatedHandler;
pub(crate) use self::fresh::FreshHandler;
mod authenticated;
#[cfg(feature = "coconut")]
pub(crate) mod coconut;
#[cfg(not(feature = "coconut"))]
pub(crate) mod eth_events;
mod fresh;
@@ -14,7 +14,7 @@ use std::sync::Arc;
use tokio::task::JoinHandle;
#[cfg(feature = "coconut")]
use coconut_interface::VerificationKey;
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
#[cfg(not(feature = "coconut"))]
use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge;
@@ -25,8 +25,7 @@ pub(crate) struct Listener {
disabled_credentials_mode: bool,
#[cfg(feature = "coconut")]
aggregated_verification_key: VerificationKey,
pub(crate) coconut_verifier: Arc<CoconutVerifier>,
#[cfg(not(feature = "coconut"))]
erc20_bridge: Arc<ERC20Bridge>,
}
@@ -36,7 +35,7 @@ impl Listener {
address: SocketAddr,
local_identity: Arc<identity::KeyPair>,
disabled_credentials_mode: bool,
#[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey,
#[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>,
#[cfg(not(feature = "coconut"))] erc20_bridge: ERC20Bridge,
) -> Self {
Listener {
@@ -44,7 +43,7 @@ impl Listener {
local_identity,
disabled_credentials_mode,
#[cfg(feature = "coconut")]
aggregated_verification_key,
coconut_verifier,
#[cfg(not(feature = "coconut"))]
erc20_bridge: Arc::new(erc20_bridge),
}
@@ -84,7 +83,7 @@ impl Listener {
storage.clone(),
active_clients_store.clone(),
#[cfg(feature = "coconut")]
self.aggregated_verification_key.clone(),
Arc::clone(&self.coconut_verifier),
#[cfg(not(feature = "coconut"))]
Arc::clone(&self.erc20_bridge),
);
+25 -8
View File
@@ -18,11 +18,11 @@ use std::process;
use std::sync::Arc;
use crate::config::persistence::pathfinder::GatewayPathfinder;
#[cfg(feature = "coconut")]
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
#[cfg(not(feature = "coconut"))]
use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge;
#[cfg(feature = "coconut")]
use coconut_interface::VerificationKey;
#[cfg(feature = "coconut")]
use credentials::obtain_aggregate_verification_key;
use self::storage::PersistentStorage;
@@ -175,7 +175,7 @@ where
&self,
forwarding_channel: MixForwardingSender,
active_clients_store: ActiveClientsStore,
#[cfg(feature = "coconut")] verification_key: VerificationKey,
#[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>,
#[cfg(not(feature = "coconut"))] erc20_bridge: ERC20Bridge,
) {
info!("Starting client [web]socket listener...");
@@ -190,7 +190,7 @@ where
Arc::clone(&self.identity_keypair),
self.config.get_disabled_credentials_mode(),
#[cfg(feature = "coconut")]
verification_key,
coconut_verifier,
#[cfg(not(feature = "coconut"))]
erc20_bridge,
)
@@ -227,13 +227,27 @@ where
);
}
// TODO: ask DH whether this function still makes sense in ^0.10
async fn check_if_same_ip_gateway_exists(&self) -> Option<String> {
fn random_api_client(&self) -> validator_client::ApiClient {
let endpoints = self.config.get_validator_api_endpoints();
let validator_api = endpoints
.choose(&mut thread_rng())
.expect("The list of validator apis is empty");
let validator_client = validator_client::ApiClient::new(validator_api.clone());
validator_client::ApiClient::new(validator_api.clone())
}
#[cfg(feature = "coconut")]
fn all_api_clients(&self) -> Vec<validator_client::ApiClient> {
self.config
.get_validator_api_endpoints()
.into_iter()
.map(validator_client::ApiClient::new)
.collect()
}
// TODO: ask DH whether this function still makes sense in ^0.10
async fn check_if_same_ip_gateway_exists(&self) -> Option<String> {
let validator_client = self.random_api_client();
let existing_gateways = match validator_client.get_cached_gateways().await {
Ok(gateways) => gateways,
@@ -271,6 +285,9 @@ where
obtain_aggregate_verification_key(&self.config.get_validator_api_endpoints())
.await
.expect("failed to contact validators to obtain their verification keys");
#[cfg(feature = "coconut")]
let coconut_verifier =
CoconutVerifier::new(self.all_api_clients(), validators_verification_key);
#[cfg(not(feature = "coconut"))]
let erc20_bridge = ERC20Bridge::new(
@@ -291,7 +308,7 @@ where
mix_forwarding_channel,
active_clients_store,
#[cfg(feature = "coconut")]
validators_verification_key,
Arc::new(coconut_verifier),
#[cfg(not(feature = "coconut"))]
erc20_bridge,
);