use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use thiserror::Error;
use time::{Date, OffsetDateTime};
pub use nym_compact_ecash::{
Base58, BlindedSignature, Bytable, EncodedDate, EncodedTicketType, PartialWallet, PayInfo,
PublicKeyUser, SecretKeyUser, VerificationKeyAuth, WithdrawalRequest,
aggregate_verification_keys, aggregate_wallets, constants, ecash_parameters,
error::CompactEcashError,
generate_keypair_user, generate_keypair_user_from_seed, issue_verify,
scheme::Payment,
scheme::coin_indices_signatures::aggregate_indices_signatures,
scheme::coin_indices_signatures::{
AnnotatedCoinIndexSignature, CoinIndexSignature, CoinIndexSignatureShare,
PartialCoinIndexSignature,
},
scheme::expiration_date_signatures::aggregate_expiration_signatures,
scheme::expiration_date_signatures::{
AnnotatedExpirationDateSignature, ExpirationDateSignature, ExpirationDateSignatureShare,
PartialExpirationDateSignature,
},
scheme::keygen::KeyPairUser,
scheme::withdrawal::RequestInfo,
scheme::{Wallet, WalletSignatures},
withdrawal_request,
};
pub use nym_ecash_time::{EcashTime, ecash_today};
pub use nym_network_defaults::TicketTypeRepr;
use nym_network_defaults::TicketTypeRepr::V1MixnetEntry;
pub const DEFAULT_MIXNET_REQUEST_BANDWIDTH_THRESHOLD: i64 =
(V1MixnetEntry.bandwidth_value() / 5) as i64;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum BandwidthCredential {
ZkNym(Box<CredentialSpendingData>),
UpgradeModeJWT { token: String },
}
impl BandwidthCredential {
pub fn into_zk_nym(self) -> Option<Box<CredentialSpendingData>> {
match self {
BandwidthCredential::ZkNym(credential) => Some(credential),
_ => None,
}
}
}
impl From<CredentialSpendingData> for BandwidthCredential {
fn from(credential: CredentialSpendingData) -> Self {
Self::ZkNym(Box::new(credential))
}
}
#[derive(Debug, Clone)]
pub struct CredentialSigningData {
pub withdrawal_request: WithdrawalRequest,
pub request_info: RequestInfo,
pub ecash_pub_key: PublicKeyUser,
pub expiration_date: Date,
pub ticketbook_type: TicketType,
}
#[derive(Serialize, Deserialize, PartialEq, Clone)]
pub struct CredentialSpendingData {
pub payment: Payment,
pub pay_info: PayInfo,
pub spend_date: Date,
pub epoch_id: u64,
}
impl Debug for CredentialSpendingData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CredentialSpendingData")
.field("payment", &"[REDACTED]")
.field("pay_info", &self.pay_info)
.field("spend_date", &self.spend_date)
.field("epoch_id", &self.epoch_id)
.finish()
}
}
impl CredentialSpendingData {
pub fn verify(&self, verification_key: &VerificationKeyAuth) -> Result<(), CompactEcashError> {
self.payment.spend_verify(
verification_key,
&self.pay_info,
self.spend_date.ecash_unix_timestamp(),
)
}
pub fn encoded_serial_number(&self) -> Vec<u8> {
self.payment.encoded_serial_number()
}
pub fn serial_number_b58(&self) -> String {
self.payment.serial_number_bs58()
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let payment_bytes = self.payment.to_bytes();
bytes.extend_from_slice(&(payment_bytes.len() as u32).to_be_bytes());
bytes.extend_from_slice(&payment_bytes);
bytes.extend_from_slice(&self.pay_info.pay_info_bytes); bytes.extend_from_slice(&self.spend_date.to_julian_day().to_be_bytes());
bytes.extend_from_slice(&self.epoch_id.to_be_bytes());
bytes
}
pub fn try_from_bytes(raw: &[u8]) -> Result<Self, CompactEcashError> {
if raw.len() < 72 + 8 + 4 + 4 {
return Err(CompactEcashError::DeserializationFailure {
object: "EcashCredential".into(),
});
}
let mut index = 0;
let payment_len = u32::from_be_bytes(raw[index..index + 4].try_into().unwrap()) as usize;
index += 4;
if raw[index..].len() != payment_len + 84 {
return Err(CompactEcashError::DeserializationFailure {
object: "EcashCredential".into(),
});
}
let payment = Payment::try_from(&raw[index..index + payment_len])?;
index += payment_len;
let pay_info = PayInfo {
pay_info_bytes: raw[index..index + 72].try_into().unwrap(),
};
index += 72;
let spend_date_julian = i32::from_be_bytes(raw[index..index + 4].try_into().unwrap());
let spend_date = Date::from_julian_day(spend_date_julian).map_err(|_| {
CompactEcashError::DeserializationFailure {
object: "CredentialSpendingData".into(),
}
})?;
index += 4;
if raw[index..].len() != 8 {
return Err(CompactEcashError::DeserializationFailure {
object: "EcashCredential".into(),
});
}
let epoch_id = u64::from_be_bytes(raw[index..].try_into().unwrap());
Ok(CredentialSpendingData {
payment,
pay_info,
spend_date,
epoch_id,
})
}
}
impl Bytable for CredentialSpendingData {
fn to_byte_vec(&self) -> Vec<u8> {
self.to_bytes()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CompactEcashError> {
Self::try_from_bytes(slice)
}
}
impl Base58 for CredentialSpendingData {}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub struct NymPayInfo {
randomness: [u8; 32],
timestamp: i64,
provider_public_key: [u8; 32],
}
impl NymPayInfo {
pub fn generate(provider_pk: [u8; 32]) -> Self {
let mut randomness = [0u8; 32];
rand::thread_rng().fill(&mut randomness[..32]);
let timestamp = OffsetDateTime::now_utc().unix_timestamp();
NymPayInfo {
randomness,
timestamp,
provider_public_key: provider_pk,
}
}
pub fn timestamp(&self) -> i64 {
self.timestamp
}
pub fn pk(&self) -> [u8; 32] {
self.provider_public_key
}
}
impl From<NymPayInfo> for PayInfo {
fn from(value: NymPayInfo) -> Self {
let mut pay_info_bytes = [0u8; 72];
pay_info_bytes[..32].copy_from_slice(&value.randomness);
pay_info_bytes[32..40].copy_from_slice(&value.timestamp.to_be_bytes());
pay_info_bytes[40..].copy_from_slice(&value.provider_public_key);
PayInfo { pay_info_bytes }
}
}
impl From<PayInfo> for NymPayInfo {
fn from(value: PayInfo) -> Self {
let randomness = value.pay_info_bytes[..32].try_into().unwrap();
let timestamp = i64::from_be_bytes(value.pay_info_bytes[32..40].try_into().unwrap());
let provider_public_key = value.pay_info_bytes[40..].try_into().unwrap();
NymPayInfo {
randomness,
timestamp,
provider_public_key,
}
}
}
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
Serialize,
Deserialize,
Hash,
strum_macros::Display,
strum_macros::EnumString,
strum_macros::EnumIter,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum TicketType {
V1MixnetEntry,
V1MixnetExit,
V1WireguardEntry,
V1WireguardExit,
}
#[derive(Debug, Copy, Clone, Error)]
#[error("provided unknown ticketbook type")]
pub struct UnknownTicketType;
impl TicketType {
pub fn to_repr(&self) -> TicketTypeRepr {
(*self).into()
}
pub fn encode(&self) -> EncodedTicketType {
self.to_repr() as EncodedTicketType
}
pub fn try_from_encoded(val: EncodedTicketType) -> Result<Self, UnknownTicketType> {
match val {
n if n == TicketTypeRepr::V1MixnetEntry as u8 => {
Ok(TicketTypeRepr::V1MixnetEntry.into())
}
n if n == TicketTypeRepr::V1MixnetExit as u8 => Ok(TicketTypeRepr::V1MixnetExit.into()),
n if n == TicketTypeRepr::V1WireguardEntry as u8 => {
Ok(TicketTypeRepr::V1WireguardEntry.into())
}
n if n == TicketTypeRepr::V1WireguardExit as u8 => {
Ok(TicketTypeRepr::V1WireguardExit.into())
}
_ => Err(UnknownTicketType),
}
}
}
impl From<TicketType> for TicketTypeRepr {
fn from(value: TicketType) -> Self {
match value {
TicketType::V1MixnetEntry => TicketTypeRepr::V1MixnetEntry,
TicketType::V1MixnetExit => TicketTypeRepr::V1MixnetExit,
TicketType::V1WireguardEntry => TicketTypeRepr::V1WireguardEntry,
TicketType::V1WireguardExit => TicketTypeRepr::V1WireguardExit,
}
}
}
impl From<TicketTypeRepr> for TicketType {
fn from(value: TicketTypeRepr) -> Self {
match value {
TicketTypeRepr::V1MixnetEntry => TicketType::V1MixnetEntry,
TicketTypeRepr::V1MixnetExit => TicketType::V1MixnetExit,
TicketTypeRepr::V1WireguardEntry => TicketType::V1WireguardEntry,
TicketTypeRepr::V1WireguardExit => TicketType::V1WireguardExit,
}
}
}
#[derive(Clone)]
pub struct ClientTicket {
pub spending_data: CredentialSpendingData,
pub ticket_id: i64,
}
impl ClientTicket {
pub fn new(spending_data: CredentialSpendingData, ticket_id: i64) -> Self {
ClientTicket {
spending_data,
ticket_id,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AvailableBandwidth {
pub bytes: i64,
pub expiration: OffsetDateTime,
}
impl AvailableBandwidth {
pub fn expired(&self) -> bool {
self.expiration < ecash_today()
}
}
impl Default for AvailableBandwidth {
fn default() -> Self {
Self {
bytes: 0,
expiration: OffsetDateTime::UNIX_EPOCH,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct Bandwidth {
value: u64,
}
impl Bandwidth {
pub const fn new_unchecked(value: u64) -> Bandwidth {
Bandwidth { value }
}
pub fn ticket_amount(typ: TicketTypeRepr) -> Self {
Bandwidth {
value: typ.bandwidth_value(),
}
}
pub fn value(&self) -> u64 {
self.value
}
}