diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 9014322797..ef6ff03971 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -9,8 +9,8 @@ use crate::{ ReqwestRpcClient, ValidatorClientError, }; use nym_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse, VerifyCredentialBody, - VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse, + OfflineVerifyCredentialBody, OnlineVerifyCredentialBody, VerifyCredentialResponse, }; use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated}; use nym_api_requests::models::{ @@ -334,11 +334,18 @@ impl NymApiClient { pub async fn verify_offline_credential( &self, - request_body: &VerifyCredentialBody, + request_body: &OfflineVerifyCredentialBody, ) -> Result { Ok(self.nym_api.verify_offline_credential(request_body).await?) } + pub async fn verify_online_credential( + &self, + request_body: &OnlineVerifyCredentialBody, + ) -> Result { + Ok(self.nym_api.verify_online_credential(request_body).await?) + } + pub async fn ecash_parameters(&self) -> Result { Ok(self.nym_api.ecash_parameters().await?) } diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 2539c3ab84..ef3389a6bb 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -6,8 +6,8 @@ use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; use async_trait::async_trait; use http_api_client::{ApiClient, NO_PARAMS}; use nym_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse, VerifyCredentialBody, - VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse, + OfflineVerifyCredentialBody, OnlineVerifyCredentialBody, VerifyCredentialResponse, }; use nym_api_requests::models::{ ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse, @@ -385,7 +385,7 @@ pub trait NymApiClientExt: ApiClient { async fn verify_offline_credential( &self, - request_body: &VerifyCredentialBody, + request_body: &OfflineVerifyCredentialBody, ) -> Result { self.post_json( &[ @@ -400,6 +400,23 @@ pub trait NymApiClientExt: ApiClient { .await } + async fn verify_online_credential( + &self, + request_body: &OnlineVerifyCredentialBody, + ) -> Result { + self.post_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::ECASH_VERIFY_ONLINE_CREDENTIAL, + ], + NO_PARAMS, + request_body, + ) + .await + } + async fn ecash_parameters(&self) -> Result { self.get_json( &[ diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index 8fd7469959..813daf335b 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -17,6 +17,7 @@ pub const BANDWIDTH: &str = "bandwidth"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; pub const ECASH_VERIFY_OFFLINE_CREDENTIAL: &str = "verify-offline-credential"; +pub const ECASH_VERIFY_ONLINE_CREDENTIAL: &str = "verify-online-credential"; pub const ECASH_PARAMETERS: &str = "ecash-parameters"; pub const STATUS_ROUTES: &str = "status"; diff --git a/nym-api/nym-api-requests/src/coconut.rs b/nym-api/nym-api-requests/src/coconut.rs index 6f65faa191..436ccafb38 100644 --- a/nym-api/nym-api-requests/src/coconut.rs +++ b/nym-api/nym-api-requests/src/coconut.rs @@ -12,24 +12,44 @@ use nym_compact_ecash::{ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)] -pub struct VerifyCredentialBody { +pub struct OfflineVerifyCredentialBody { #[getset(get = "pub")] credential: EcashCredential, - //#[getset(get = "pub")] - //proposal_id: u64, #[getset(get = "pub")] gateway_cosmos_addr: AccountId, } -impl VerifyCredentialBody { +impl OfflineVerifyCredentialBody { pub fn new( credential: EcashCredential, - //proposal_id: u64, gateway_cosmos_addr: AccountId, - ) -> VerifyCredentialBody { - VerifyCredentialBody { + ) -> OfflineVerifyCredentialBody { + OfflineVerifyCredentialBody { credential, - //proposal_id, + gateway_cosmos_addr, + } + } +} + +#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)] +pub struct OnlineVerifyCredentialBody { + #[getset(get = "pub")] + credential: EcashCredential, + #[getset(get = "pub")] + proposal_id: u64, + #[getset(get = "pub")] + gateway_cosmos_addr: AccountId, +} + +impl OnlineVerifyCredentialBody { + pub fn new( + credential: EcashCredential, + proposal_id: u64, + gateway_cosmos_addr: AccountId, + ) -> OnlineVerifyCredentialBody { + OnlineVerifyCredentialBody { + credential, + proposal_id, gateway_cosmos_addr, } } diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 9cbf1eea4c..5385051370 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -83,6 +83,12 @@ pub enum CoconutError { #[error("Credentials error - {0}")] CredentialsError(#[from] nym_credentials::error::Error), + #[error("Incorrect credential proposal description: {reason}")] + IncorrectProposal { reason: String }, + + #[error("Invalid status of credential: {status}")] + InvalidCredentialStatus { status: String }, + #[error("DKG error: {0}")] DkgError(#[from] DkgError), diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index 70e8b08691..bef1cd97c8 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -9,11 +9,15 @@ use crate::support::storage::NymApiStorage; use getset::{CopyGetters, Getters}; use keypair::KeyPair; use nym_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse, VerifyCredentialBody, - VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, EcashParametersResponse, + OfflineVerifyCredentialBody, OnlineVerifyCredentialBody, VerifyCredentialResponse, +}; +use nym_coconut_bandwidth_contract_common::spend_credential::{ + funds_from_cosmos_msgs, SpendCredentialStatus, }; use nym_coconut_dkg_common::types::EpochId; +use crate::coconut::helpers::accepted_vote_err; use nym_compact_ecash::error::CompactEcashError; use nym_compact_ecash::scheme::keygen::KeyPairAuth; use nym_compact_ecash::scheme::withdrawal::WithdrawalRequest; @@ -30,6 +34,7 @@ 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::AccountId; +use nym_validator_client::nyxd::{Coin, Fee}; use rand_07::rngs::OsRng; use rocket::fairing::AdHoc; use rocket::serde::json::Json; @@ -50,6 +55,7 @@ pub(crate) mod tests; pub struct State { client: Arc, + mix_denom: String, key_pair: KeyPair, ecash_params: Parameters, comm_channel: Arc, @@ -60,6 +66,7 @@ pub struct State { impl State { pub(crate) fn new( client: C, + mix_denom: String, key_pair: KeyPair, comm_channel: D, storage: NymApiStorage, @@ -76,6 +83,7 @@ impl State { let ecash_params = Parameters::try_from_bs58(params_base58).unwrap(); //SW Waiting for an actual parameters generation scheme. Self { client, + mix_denom, key_pair, ecash_params, comm_channel, @@ -178,6 +186,7 @@ impl InternalSignRequest { pub fn stage( client: C, + mix_denom: String, key_pair: KeyPair, comm_channel: D, storage: NymApiStorage, @@ -186,12 +195,16 @@ impl InternalSignRequest { C: LocalClient + Send + Sync + 'static, D: APICommunicationChannel + Send + Sync + 'static, { - let state = State::new(client, key_pair, comm_channel, storage); + let state = State::new(client, mix_denom, key_pair, comm_channel, storage); AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { rocket.manage(state).mount( // this format! is so ugly... format!("/{}/{}/{}", NYM_API_VERSION, COCONUT_ROUTES, BANDWIDTH), - routes![post_blind_sign, verify_offline_credential], + routes![ + post_blind_sign, + verify_offline_credential, + verify_online_credential + ], ) }) } @@ -248,7 +261,7 @@ pub async fn post_blind_sign( #[post("/verify-offline-credential", data = "")] pub async fn verify_offline_credential( - verify_credential_body: Json, + verify_credential_body: Json, state: &RocketState, ) -> Result> { let verification_key = state @@ -281,6 +294,81 @@ pub async fn verify_offline_credential( Ok(Json(VerifyCredentialResponse::new(true))) } +#[post("/verify-online-credential", data = "")] +pub async fn verify_online_credential( + verify_credential_body: Json, + state: &RocketState, +) -> Result> { + let proposal_id = *verify_credential_body.proposal_id(); + let proposal = state.client.get_proposal(proposal_id).await?; + // Proposal description is the blinded serial number + if !verify_credential_body + .credential() + .has_blinded_serial_number(&proposal.description)? + { + return Err(CoconutError::IncorrectProposal { + reason: String::from("incorrect blinded serial number in description"), + }); + } + let proposed_release_funds = + funds_from_cosmos_msgs(proposal.msgs).ok_or(CoconutError::IncorrectProposal { + reason: String::from("action is not to release funds"), + })?; + // Credential has not been spent before, and is on its way of being spent + let credential_status = state + .client + .get_spent_credential(verify_credential_body.credential().blinded_serial_number()) + .await? + .spend_credential + .ok_or(CoconutError::InvalidCredentialStatus { + status: String::from("Inexistent"), + })? + .status(); + if credential_status != SpendCredentialStatus::InProgress { + return Err(CoconutError::InvalidCredentialStatus { + status: format!("{:?}", credential_status), + }); + } + let verification_key = state + .verification_key(*verify_credential_body.credential().epoch_id()) + .await?; + let mut vote_yes = verify_credential_body + .credential() + .payment() + .spend_verify( + &state.ecash_params, + &verification_key, + verify_credential_body.credential().pay_info(), + ) + .map_err(|_| { + CoconutError::CompactEcashInternalError(CompactEcashError::PaymentVerification) + })?; + + //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, + state.mix_denom.clone(), + ); + + // Vote yes or no on the proposal based on the verification result + let ret = state + .client + .vote_proposal( + proposal_id, + vote_yes, + Some(Fee::new_payer_granter_auto( + None, + None, + Some(verify_credential_body.gateway_cosmos_addr().to_owned()), + )), + ) + .await; + accepted_vote_err(ret)?; + + Ok(Json(VerifyCredentialResponse::new(vote_yes))) +} + #[derive(Getters, CopyGetters)] pub struct EcashParameters { #[getset(get = "pub")] diff --git a/nym-api/src/coconut/tests.rs b/nym-api/src/coconut/tests.rs index 665d31d95b..681db8261f 100644 --- a/nym-api/src/coconut/tests.rs +++ b/nym-api/src/coconut/tests.rs @@ -5,7 +5,8 @@ use super::InternalSignRequest; use crate::coconut::error::{CoconutError, Result}; use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; use nym_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, OnlineVerifyCredentialBody, + VerifyCredentialResponse, }; use nym_coconut::tests::helpers::theta_from_keys_and_attributes; use nym_coconut::{prepare_blind_sign, ttp_keygen, Base58, BlindedSignature, Parameters}; @@ -880,8 +881,11 @@ async fn verification_of_bandwidth_credential() { let proposal_id = 42; // The address is not used, so we can use a duplicate let gateway_cosmos_addr = validator_address.clone(); - let req = - VerifyCredentialBody::new(credential.clone(), proposal_id, gateway_cosmos_addr.clone()); + let req = OnlineVerifyCredentialBody::new( + credential.clone(), + proposal_id, + gateway_cosmos_addr.clone(), + ); // Test endpoint with not proposal for the proposal id let response = client @@ -1032,7 +1036,7 @@ async fn verification_of_bandwidth_credential() { 0, ); let bad_req = - VerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone()); + OnlineVerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone()); let response = client .post(format!( "/{}/{}/{}/{}", diff --git a/nym-api/src/support/http/mod.rs b/nym-api/src/support/http/mod.rs index 2d07492876..f3c7aab9e8 100644 --- a/nym-api/src/support/http/mod.rs +++ b/nym-api/src/support/http/mod.rs @@ -70,6 +70,7 @@ pub(crate) async fn setup_rocket( let comm_channel = QueryCommunicationChannel::new(_nyxd_client.clone()); rocket.attach(InternalSignRequest::stage( _nyxd_client.clone(), + mix_denom, coconut_keypair, comm_channel, storage.clone().unwrap(),