diff --git a/common/nym_offline_compact_ecash/src/error.rs b/common/nym_offline_compact_ecash/src/error.rs index 55f737b45a..56d909a780 100644 --- a/common/nym_offline_compact_ecash/src/error.rs +++ b/common/nym_offline_compact_ecash/src/error.rs @@ -34,6 +34,9 @@ pub enum CompactEcashError { #[error("Could not decode base 58 string - {0}")] MalformedString(#[from] bs58::decode::Error), + #[error("Payment did not verify")] + PaymentVerification, + #[error( "Deserailization error, expected at least {} bytes, got {}", min, diff --git a/gateway/migrations/20210921120000_create_initial_tables.sql b/gateway/migrations/20210921120000_create_initial_tables.sql index f8b9b62765..e10e317883 100644 --- a/gateway/migrations/20210921120000_create_initial_tables.sql +++ b/gateway/migrations/20210921120000_create_initial_tables.sql @@ -22,4 +22,10 @@ CREATE TABLE available_bandwidth available INTEGER NOT NULL ); -CREATE INDEX `message_store_index` ON `message_store` (`client_address_bs58`, `content`); \ No newline at end of file +CREATE INDEX `message_store_index` ON `message_store` (`client_address_bs58`, `content`); + +CREATE TABLE credentials +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + credentials TEXT NOT NULL +); \ No newline at end of file diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index 688005eea4..9db3612648 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -266,11 +266,14 @@ where )) })?; + //SW PUT that in a new threads, ensuring it eventually gets sent self.inner .ecash_verifier - .post_and_store_credential(current_api_clients, credential) + .post_credential(current_api_clients, credential.clone()) .await?; + self.inner.storage.insert_credential(credential).await?; + let bandwidth_value = 500000; self.increase_bandwidth(bandwidth_value as i64).await?; 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 7500f221b1..a5d891133c 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -24,17 +24,11 @@ const MAX_FEEGRANT_UNYM: u128 = 10000; pub(crate) struct EcashVerifier { nyxd_client: DirectSigningHttpRpcNyxdClient, - mix_denom_base: String, } impl EcashVerifier { pub fn new(nyxd_client: DirectSigningHttpRpcNyxdClient) -> Self { - let mix_denom_base = nyxd_client.current_chain_details().mix_denom.base.clone(); - - EcashVerifier { - nyxd_client, - mix_denom_base, - } + EcashVerifier { nyxd_client } } pub async fn all_current_ecash_api_clients( @@ -60,7 +54,7 @@ impl EcashVerifier { Ok(()) } - pub async fn post_and_store_credential( + pub async fn post_credential( &self, api_clients: Vec, credential: EcashCredential, @@ -72,28 +66,22 @@ impl EcashVerifier { ); for client in api_clients { - let ret = client.api_client.verify_bandwidth_credential(&req).await; //SW PUT that in a new threads, ensuring it eventually gets sent - warn!( - "Return from {} : {:?}", - client.api_client.nym_api.current_url(), - ret - ); - // match ret { - // Ok(res) => { - // if !res.verification_result { - // debug!( - // "Validator {} didn't accept the credential.", - // client.api_client.nym_api.current_url() - // ); - // } - // } - // Err(e) => { - // warn!("Validator {} could not be reached. There might be a problem with the coconut endpoint - {:?}", client.api_client.nym_api.current_url(), e); - // } - // } - } + let ret = client.api_client.verify_bandwidth_credential(&req).await; - //store credential + match ret { + Ok(res) => { + if !res.verification_result { + debug!( + "Validator {} didn't accept the credential.", + client.api_client.nym_api.current_url() + ); + } + } + Err(e) => { + warn!("Validator {} could not be reached. There might be a problem with the coconut endpoint - {:?}", client.api_client.nym_api.current_url(), e); + } + } + } Ok(()) } diff --git a/gateway/src/node/storage/credential.rs b/gateway/src/node/storage/credential.rs new file mode 100644 index 0000000000..caadb119cf --- /dev/null +++ b/gateway/src/node/storage/credential.rs @@ -0,0 +1,34 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[derive(Clone)] +pub(crate) struct CredentialManager { + connection_pool: sqlx::SqlitePool, +} + +impl CredentialManager { + /// Creates new instance of the `CredentialManager` with the provided sqlite connection pool. + /// + /// # Arguments + /// + /// * `connection_pool`: database connection pool to use. + pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self { + CredentialManager { connection_pool } + } + + /// Inserts provided credential into the database. + /// If the credential previously existed for the provided client, they are overwritten with the new data. + /// + /// # Arguments + /// + /// * `credential`: base58 representation of an ecash credential + pub(crate) async fn insert_credential(&self, credential: String) -> Result<(), sqlx::Error> { + sqlx::query!( + "INSERT OR REPLACE INTO credentials(credentials) VALUES (?)", + credential + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } +} diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs index 77f87437ea..048bac88b6 100644 --- a/gateway/src/node/storage/mod.rs +++ b/gateway/src/node/storage/mod.rs @@ -2,18 +2,22 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::node::storage::bandwidth::BandwidthManager; +use crate::node::storage::credential::CredentialManager; use crate::node::storage::error::StorageError; use crate::node::storage::inboxes::InboxManager; use crate::node::storage::models::{PersistedSharedKeys, StoredMessage}; use crate::node::storage::shared_keys::SharedKeysManager; use async_trait::async_trait; use log::{debug, error}; +use nym_compact_ecash::scheme::EcashCredential; +use nym_compact_ecash::Base58; use nym_gateway_requests::registration::handshake::SharedKeys; use nym_sphinx::DestinationAddressBytes; use sqlx::ConnectOptions; use std::path::Path; mod bandwidth; +mod credential; pub(crate) mod error; mod inboxes; mod models; @@ -134,6 +138,13 @@ pub(crate) trait Storage: Send + Sync { client_address: DestinationAddressBytes, amount: i64, ) -> Result<(), StorageError>; + + /// Stored the accepted credential + /// + /// # Arguments + /// + /// * `credential`: credential to store + async fn insert_credential(&self, credential: EcashCredential) -> Result<(), StorageError>; } // note that clone here is fine as upon cloning the same underlying pool will be used @@ -142,6 +153,7 @@ pub(crate) struct PersistentStorage { shared_key_manager: SharedKeysManager, inbox_manager: InboxManager, bandwidth_manager: BandwidthManager, + credential_manager: CredentialManager, } impl PersistentStorage { @@ -187,7 +199,8 @@ impl PersistentStorage { Ok(PersistentStorage { shared_key_manager: SharedKeysManager::new(connection_pool.clone()), inbox_manager: InboxManager::new(connection_pool.clone(), message_retrieval_limit), - bandwidth_manager: BandwidthManager::new(connection_pool), + bandwidth_manager: BandwidthManager::new(connection_pool.clone()), + credential_manager: CredentialManager::new(connection_pool), }) } } @@ -304,6 +317,13 @@ impl Storage for PersistentStorage { .await?; Ok(()) } + + async fn insert_credential(&self, credential: EcashCredential) -> Result<(), StorageError> { + self.credential_manager + .insert_credential(credential.to_bs58()) + .await?; + Ok(()) + } } /// In-memory implementation of `Storage`. The intention is primarily in testing environments. diff --git a/gateway/src/node/storage/models.rs b/gateway/src/node/storage/models.rs index c98fb4c26b..e0a902e480 100644 --- a/gateway/src/node/storage/models.rs +++ b/gateway/src/node/storage/models.rs @@ -18,3 +18,7 @@ pub(crate) struct PersistedBandwidth { pub(crate) client_address_bs58: String, pub(crate) available: i64, } + +pub(crate) struct Credentials { + pub(crate) credential: String, +} diff --git a/nym-api/migrations/20210722120000_create_initial_tables.sql b/nym-api/migrations/20210722120000_create_initial_tables.sql index eeafc220db..a4618860a6 100644 --- a/nym-api/migrations/20210722120000_create_initial_tables.sql +++ b/nym-api/migrations/20210722120000_create_initial_tables.sql @@ -76,6 +76,14 @@ create table gateway_ipv6_status FOREIGN KEY (gateway_details_id) REFERENCES gateway_details (id) ); +create table credentials +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + credential TEXT NOT NULL, + gateway_address TEXT NOT NULL + +); + -- indices for faster lookups CREATE INDEX `mixnode_ipv4_status_index` ON `mixnode_ipv4_status` (`mixnode_details_id`, `timestamp` desc); diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index 2a3af0bb3a..b4d4a3339c 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -18,8 +18,10 @@ use nym_coconut_bandwidth_contract_common::spend_credential::{ }; use nym_coconut_dkg_common::types::EpochId; +use nym_compact_ecash::error::CompactEcashError; use nym_compact_ecash::scheme::keygen::{KeyPairAuth, SecretKeyAuth}; use nym_compact_ecash::scheme::withdrawal::WithdrawalRequest; +use nym_compact_ecash::scheme::EcashCredential; use nym_compact_ecash::setup::GroupParameters; use nym_compact_ecash::utils::BlindedSignature; use nym_compact_ecash::{PublicKeyUser, VerificationKeyAuth}; @@ -31,7 +33,7 @@ use nym_crypto::asymmetric::encryption; use nym_crypto::shared_key::new_ephemeral_shared_key; use nym_crypto::symmetric::stream_cipher; use nym_validator_client::nym_api::routes::{BANDWIDTH, COCONUT_ROUTES}; -use nym_validator_client::nyxd::{Coin, Fee}; +use nym_validator_client::nyxd::{AccountId, Coin, Fee}; use rand_07::rngs::OsRng; use rocket::fairing::AdHoc; use rocket::serde::json::Json; @@ -143,6 +145,17 @@ impl State { .aggregated_verification_key(epoch_id) .await } + + pub async fn store_credential( + &self, + credential: &EcashCredential, + gateway_addr: &AccountId, + ) -> Result<()> { + self.storage + .insert_credential(credential, gateway_addr) + .await + .map_err(|err| err.into()) + } } #[derive(Getters, CopyGetters, Debug)] @@ -250,15 +263,23 @@ pub async fn verify_bandwidth_credential( .verification_key(*verify_credential_body.credential().epoch_id()) .await?; - if let Err(err) = verify_credential_body.credential().payment().spend_verify( + if let Err(_) = verify_credential_body.credential().payment().spend_verify( verify_credential_body.credential().params(), &verification_key, verify_credential_body.credential().pay_info(), ) { - return Err(CoconutError::CompactEcashInternalError(err)); + return Err(CoconutError::CompactEcashInternalError( + CompactEcashError::PaymentVerification, + )); } //store credential + state + .store_credential( + verify_credential_body.credential(), + verify_credential_body.gateway_cosmos_addr(), + ) + .await?; Ok(Json(VerifyCredentialResponse::new(true))) } diff --git a/nym-api/src/support/storage/manager.rs b/nym-api/src/support/storage/manager.rs index 2bb3fa5c99..93bf37d27e 100644 --- a/nym-api/src/support/storage/manager.rs +++ b/nym-api/src/support/storage/manager.rs @@ -1013,4 +1013,25 @@ impl StorageManager { Ok(blinded_signature_response) } + + /// Creates new credential entry for a given gateway addr. + /// + /// # Arguments + /// + /// * `credential`: base58 repr of a credential. + /// * `gateway_addr`: cosmos address of the gateway + pub(crate) async fn insert_credential( + &self, + credential: String, + gateway_addr: String, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + "INSERT INTO credentials(credential, gateway_address) VALUES (?, ?)", + credential, + gateway_addr + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } } diff --git a/nym-api/src/support/storage/mod.rs b/nym-api/src/support/storage/mod.rs index 4ae899aac1..814e7cbc2d 100644 --- a/nym-api/src/support/storage/mod.rs +++ b/nym-api/src/support/storage/mod.rs @@ -10,7 +10,10 @@ use crate::node_status_api::models::{ use crate::node_status_api::{ONE_DAY, ONE_HOUR}; use crate::storage::manager::StorageManager; use crate::storage::models::{NodeStatus, TestingRoute}; +use nym_compact_ecash::scheme::EcashCredential; +use nym_compact_ecash::Base58; use nym_mixnet_contract_common::MixId; +use nym_validator_client::nyxd::AccountId; use rocket::fairing::AdHoc; use sqlx::ConnectOptions; use std::path::Path; @@ -737,4 +740,15 @@ impl NymApiStorage { .await .map_err(|err| err.into()) } + + pub(crate) async fn insert_credential( + &self, + credential: &EcashCredential, + gateway_addr: &AccountId, + ) -> Result<(), NymApiStorageError> { + self.manager + .insert_credential(credential.to_bs58(), gateway_addr.to_string()) + .await + .map_err(|err| err.into()) + } }