Files
nym/common/credential-verification/src/ecash/mod.rs
T
Jędrzej Stuczyński 5a07b73375 feature: hopefully final steps of the smoosh™️ (#5201)
* removed mnemonic from gateway config struct

scaffolding for common mixnet listener

running verloc unconditionally in a nym-node

remove filtering by mixnode

extracted verloc to separate crate

integrated nym-node-http-server more tightly with the binary

most logic for handling forward packets

running all mixnode-related tasks natively inside nymnode

removed gateway storage trait in favour of the only concrete implementation

most logic for handling final hop packets

using nym-node owned socket listener for gateways

utility for sending plain message through mixnet + gateway fix

using common packet forwarding in both modes

nifying nym-node metrics

reproduce behaviour of the console logger

cleaned up cli args

redesigned gateway tasks startup procedure

removing dead code

scaffolding for old config v6

config migration

implemented MixnetMetricsCleaner

* clippy

* require entry/exit for wireguard

* removed dead code in migration code

* updated config template

* use custom user agent for verloc queries

* fixed premature shutdown of gateway tasks

* hidden nym-api flag to allow illegal node ips

* experiment: final hop handing with wireguard

* added additional startup logs

* typo

* fixed legacy stats endpoint data

* additional logs

* apply review comments

* fixed local testnet manager
2024-12-05 17:21:36 +00:00

164 lines
5.5 KiB
Rust

// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::Error;
use credential_sender::CredentialHandler;
use credential_sender::CredentialHandlerConfig;
use error::EcashTicketError;
use futures::channel::mpsc::{self, UnboundedSender};
use nym_credentials::CredentialSpendingData;
use nym_credentials_interface::{ClientTicket, CompactEcashError, NymPayInfo, VerificationKeyAuth};
use nym_gateway_storage::GatewayStorage;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::DirectSigningHttpRpcNyxdClient;
use state::SharedState;
use time::OffsetDateTime;
use tokio::sync::{Mutex, RwLockReadGuard};
use tracing::error;
pub mod credential_sender;
pub mod error;
mod helpers;
mod state;
pub const TIME_RANGE_SEC: i64 = 30;
pub struct EcashManager {
shared_state: SharedState,
pk_bytes: [u8; 32], // bytes representation of a pub key representing the verifier
pay_infos: Mutex<Vec<NymPayInfo>>,
cred_sender: UnboundedSender<ClientTicket>,
}
impl EcashManager {
pub async fn new(
credential_handler_cfg: CredentialHandlerConfig,
nyxd_client: DirectSigningHttpRpcNyxdClient,
pk_bytes: [u8; 32],
shutdown: nym_task::TaskClient,
storage: GatewayStorage,
) -> Result<Self, Error> {
let shared_state = SharedState::new(nyxd_client, storage).await?;
let (cred_sender, cred_receiver) = mpsc::unbounded();
let cs =
CredentialHandler::new(credential_handler_cfg, cred_receiver, shared_state.clone())
.await?;
cs.start(shutdown);
Ok(EcashManager {
shared_state,
pk_bytes,
pay_infos: Default::default(),
cred_sender,
})
}
pub async fn verification_key(
&self,
epoch_id: EpochId,
) -> Result<RwLockReadGuard<VerificationKeyAuth>, EcashTicketError> {
self.shared_state.verification_key(epoch_id).await
}
pub fn storage(&self) -> &GatewayStorage {
&self.shared_state.storage
}
//Check for duplicate pay_info, then check the payment, then insert pay_info if everything succeeded
pub async fn check_payment(
&self,
credential: &CredentialSpendingData,
aggregated_verification_key: &VerificationKeyAuth,
) -> Result<(), EcashTicketError> {
let insert_index = self.verify_pay_info(credential.pay_info.into()).await?;
credential
.verify(aggregated_verification_key)
.map_err(|err| match err {
CompactEcashError::ExpirationDateSignatureValidity => {
EcashTicketError::MalformedTicketInvalidDateSignatures
}
_ => EcashTicketError::MalformedTicket,
})?;
self.insert_pay_info(credential.pay_info.into(), insert_index)
.await
}
pub async fn verify_pay_info(&self, pay_info: NymPayInfo) -> Result<usize, EcashTicketError> {
//Public key check
if pay_info.pk() != self.pk_bytes {
return Err(EcashTicketError::InvalidPayInfoPublicKey);
}
//Timestamp range check
let timestamp = OffsetDateTime::now_utc().unix_timestamp();
let tmin = timestamp - TIME_RANGE_SEC;
let tmax = timestamp + TIME_RANGE_SEC;
if pay_info.timestamp() > tmax || pay_info.timestamp() < tmin {
return Err(EcashTicketError::InvalidPayInfoTimestamp);
}
let mut inner = self.pay_infos.lock().await;
//Cleanup inner
let low = inner.partition_point(|x| x.timestamp() < tmin);
let high = inner.partition_point(|x| x.timestamp() < tmax);
inner.truncate(high);
drop(inner.drain(..low));
//Duplicate check
match inner.binary_search_by(|info| info.timestamp().cmp(&pay_info.timestamp())) {
Result::Err(index) => Ok(index),
Result::Ok(index) => {
if inner[index] == pay_info {
return Err(EcashTicketError::DuplicatePayInfo);
}
//tbh, I don't expect ending up here if all parties are honest
//binary search returns an arbitrary match, so we have to check for potential multiple matches
let mut i = index as i64;
while i >= 0 && inner[i as usize].timestamp() == pay_info.timestamp() {
if inner[i as usize] == pay_info {
return Err(EcashTicketError::DuplicatePayInfo);
}
i -= 1;
}
let mut i = index + 1;
while i < inner.len() && inner[i].timestamp() == pay_info.timestamp() {
if inner[i] == pay_info {
return Err(EcashTicketError::DuplicatePayInfo);
}
i += 1;
}
Ok(index)
}
}
}
async fn insert_pay_info(
&self,
pay_info: NymPayInfo,
index: usize,
) -> Result<(), EcashTicketError> {
let mut inner = self.pay_infos.lock().await;
if index > inner.len() {
inner.push(pay_info);
return Ok(());
}
inner.insert(index, pay_info);
Ok(())
}
pub fn async_verify(&self, ticket: ClientTicket) {
// TODO: I guess do something for shutdowns
let _ = self
.cred_sender
.unbounded_send(ticket)
.inspect_err(|_| error!("failed to send the client ticket for verification task"));
}
}