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:
committed by
GitHub
parent
a5759ab227
commit
5f7b3db9a4
@@ -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
@@ -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,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user