diff --git a/common/nym_offline_compact_ecash/src/scheme/mod.rs b/common/nym_offline_compact_ecash/src/scheme/mod.rs index c89f3f5a1d..add9e3369d 100644 --- a/common/nym_offline_compact_ecash/src/scheme/mod.rs +++ b/common/nym_offline_compact_ecash/src/scheme/mod.rs @@ -671,6 +671,18 @@ impl EcashCredential { bytes } + + pub fn value(&self) -> u64 { + todo!() //SW todo + } + + pub fn blinded_serial_number(&self) -> String { + todo!() //SW todo + } + + pub fn has_blinded_serial_number(&self, _blinded_serial_number_bs58: &str) -> Result { + todo!() //SW todo + } } impl TryFrom<&[u8]> for EcashCredential { 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 0365619f2d..26e3036a5f 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -66,6 +66,12 @@ pub(crate) enum RequestHandlingError { #[error("Not enough nym API endpoints provided. Needed {needed}, received {received}")] NotEnoughNymAPIs { received: usize, needed: usize }, + #[error("There was a problem with the proposal id: {reason}")] + ProposalIdError { reason: String }, + + #[error("Compact ecash error - {0}")] + CompactEcashError(#[from] nym_compact_ecash::error::CompactEcashError), + #[error("Coconut interface error - {0}")] CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), @@ -251,11 +257,18 @@ where .check_payment(&credential, &aggregated_verification_key) .await?; - self.inner - .ecash_verifier - .post_credential(current_api_clients, credential.clone())?; + if self.inner.offline_credential_verification { + self.inner + .ecash_verifier + .post_credential(current_api_clients, credential.clone())?; - self.inner.storage.insert_credential(credential).await?; + self.inner.storage.insert_credential(credential).await?; + } else { + self.inner + .ecash_verifier + .release_funds(current_api_clients, &credential) + .await?; + } let bandwidth_value = 100000000; //SW MAKE A GLOBAL PARAMETER 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 140144c8db..c0978a4d28 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -7,25 +7,36 @@ use super::authenticated::RequestHandlingError; use chrono::Utc; use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender}; use futures::StreamExt; -use nym_api_requests::coconut::VerifyCredentialBody; +use log::*; +use nym_api_requests::coconut::OfflineVerifyCredentialBody; use nym_compact_ecash::scheme::EcashCredential; use nym_compact_ecash::setup::Parameters; use nym_compact_ecash::{PayInfo, VerificationKeyAuth}; use nym_validator_client::coconut::all_ecash_api_clients; -use nym_validator_client::nyxd::AccountId; +use nym_validator_client::nyxd::{AccountId, Coin, Fee}; use nym_validator_client::{ - nyxd::contract_traits::DkgQueryClient, CoconutApiClient, DirectSigningHttpRpcNyxdClient, - NymApiClient, + nyxd::{ + contract_traits::{ + CoconutBandwidthSigningClient, DkgQueryClient, MultisigQueryClient, + MultisigSigningClient, + }, + cosmwasm_client::logs::{find_attribute, BANDWIDTH_PROPOSAL_ID}, + }, + CoconutApiClient, DirectSigningHttpRpcNyxdClient, NymApiClient, }; use std::sync::Arc; use std::sync::Mutex; +use std::time::SystemTime; use tokio::time::{interval, Duration}; const TIME_RANGE_SEC: i64 = 30; const CRED_SENDING_INTERVAL: u64 = 300; +const ONE_HOUR_SEC: u64 = 3600; +const MAX_FEEGRANT_UNYM: u128 = 10000; pub(crate) struct EcashVerifier { nyxd_client: DirectSigningHttpRpcNyxdClient, + mix_denom_base: String, ecash_parameters: Parameters, pk_bytes: [u8; 32], //bytes represenation of a pub key representing the verifier pay_infos: Arc>>, @@ -39,13 +50,19 @@ impl EcashVerifier { pk_bytes: [u8; 32], shutdown: nym_task::TaskClient, storage: St, + offline_verification: bool, ) -> Self { + let mix_denom_base = nyxd_client.current_chain_details().mix_denom.base.clone(); let (cred_sender, cred_receiver) = mpsc::unbounded(); - let cs = CredentialSender::new(cred_receiver, storage); - cs.start(shutdown); + //SW do not initialize unused elements + if offline_verification { + let cs = CredentialSender::new(cred_receiver, storage); + cs.start(shutdown); + } EcashVerifier { nyxd_client, + mix_denom_base, ecash_parameters, pk_bytes, pay_infos: Arc::new(Mutex::new(Vec::new())), @@ -199,6 +216,87 @@ impl EcashVerifier { } Ok(()) } + + pub async fn release_funds( + &self, + api_clients: Vec, + credential: &EcashCredential, + ) -> Result<(), RequestHandlingError> { + // Use a custom multiplier for revoke, as the default one (1.3) + // isn't enough + let revoke_fee = Some(Fee::Auto(Some(1.5))); + + let res = self + .nyxd_client + .spend_credential( + Coin::new( + credential.value().into(), //SW THIS WILL BE A FIXED VALUE? + self.mix_denom_base.clone(), + ), + credential.blinded_serial_number(), //SW what will this be? + self.nyxd_client.address().to_string(), + None, + ) + .await?; + let proposal_id = find_attribute(&res.logs, "wasm", BANDWIDTH_PROPOSAL_ID) + .ok_or(RequestHandlingError::ProposalIdError { + reason: String::from("proposal id not found"), + })? + .value + .parse::() + .map_err(|_| RequestHandlingError::ProposalIdError { + reason: String::from("proposal id could not be parsed to u64"), + })?; + + let proposal = self.nyxd_client.query_proposal(proposal_id).await?; + if !credential.has_blinded_serial_number(&proposal.description)? { + //SW serial number issue + return Err(RequestHandlingError::ProposalIdError { + reason: String::from("proposal has different serial number"), + }); + } + + let req = nym_api_requests::coconut::OnlineVerifyCredentialBody::new( + credential.clone(), + proposal_id, + self.nyxd_client.address(), + ); + for client in api_clients { + self.nyxd_client + .grant_allowance( + &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 + vec![String::from("/cosmwasm.wasm.v1.MsgExecuteContract")], + "Create allowance to vote the release of funds".to_string(), + None, + ) + .await?; + let ret = client.api_client.verify_online_credential(&req).await; + self.nyxd_client + .revoke_allowance( + &client.cosmos_address, + "Cleanup the previous allowance for releasing funds".to_string(), + revoke_fee.clone(), + ) + .await?; + match ret { + Ok(res) => { + if !res.verification_result { + debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", 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); + } + } + } + + self.nyxd_client.execute_proposal(proposal_id, None).await?; + + Ok(()) + } } #[derive(Clone)] @@ -226,7 +324,7 @@ where async fn send_credential(pending: &PendingCredential) -> bool { let request = - VerifyCredentialBody::new(pending.credential.clone(), pending.address.clone()); + OfflineVerifyCredentialBody::new(pending.credential.clone(), pending.address.clone()); match pending.client.verify_offline_credential(&request).await { Ok(res) => { if !res.verification_result { diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 2df075904c..c532295cd8 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -90,6 +90,7 @@ pub(crate) struct FreshHandler { rng: R, local_identity: Arc, pub(crate) only_coconut_credentials: bool, + pub(crate) offline_credential_verification: bool, pub(crate) active_clients_store: ActiveClientsStore, pub(crate) outbound_mix_sender: MixForwardingSender, pub(crate) socket_connection: SocketStream, @@ -111,6 +112,7 @@ where rng: R, conn: S, only_coconut_credentials: bool, + offline_credential_verification: bool, outbound_mix_sender: MixForwardingSender, local_identity: Arc, storage: St, @@ -121,6 +123,7 @@ where rng, active_clients_store, only_coconut_credentials, + offline_credential_verification, outbound_mix_sender, socket_connection: SocketStream::RawTcp(conn), local_identity, diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 9deecc6593..477b8d309d 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -18,6 +18,7 @@ pub(crate) struct Listener { address: SocketAddr, local_identity: Arc, only_coconut_credentials: bool, + offline_credential_verification: bool, pub(crate) ecash_verifier: Arc, } @@ -26,12 +27,14 @@ impl Listener { address: SocketAddr, local_identity: Arc, only_coconut_credentials: bool, + offline_credential_verification: bool, ecash_verifier: Arc, ) -> Self { Listener { address, local_identity, only_coconut_credentials, + offline_credential_verification, ecash_verifier, } } @@ -72,6 +75,7 @@ impl Listener { OsRng, socket, self.only_coconut_credentials, + self.offline_credential_verification, outbound_mix_sender.clone(), Arc::clone(&self.local_identity), storage.clone(), diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 6c8300bdf3..b98444c3e9 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -231,6 +231,7 @@ impl Gateway { listening_address, Arc::clone(&self.identity_keypair), self.config.gateway.only_coconut_credentials, + self.config.gateway.offline_credential_verification, ecash_verifier, ) .start( @@ -472,6 +473,7 @@ impl Gateway { self.identity_keypair.public_key().to_bytes(), shutdown.subscribe().named("EcashVerifier"), self.storage.clone(), + self.config.gateway.offline_credential_verification, ) }; diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index bef1cd97c8..fed927a27d 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -347,7 +347,7 @@ pub async fn verify_online_credential( //SW THIS WILL HAVE TO CHANGE AS WELL vote_yes &= Coin::from(proposed_release_funds) == Coin::new( - verify_credential_body.credential().voucher_value() as u128, + verify_credential_body.credential().value() as u128, state.mix_denom.clone(), );