6b2bb3029b
* squashing feat: merge intermediate upgrade mode changes #6174 to more easily resolve merge conflicts during rebasing added additional v2 query for metadata endpoint for requesting upgrade mode recheck added additional message to v6 authenticator to request explicit upgrade mode recheck clippy test fixes due to updated keys updated assertion for upgrading v1 top up request to v2 compare attester public key against the expected value within the credential proxy use pre-generated attestation public keys within nym-nodes remove version deprecation bugfix: default bandwidth response for authenticator expose upgrade mode information in authenticator responses adding tests for new v2 server passing upgrade mode information in metadata endpoint v2 wireguard private metadata bugfix: make sure to immediately poll for attestation after spawning task fix gateway probe and remove code duplication for finalizing registration squashing before rebasing post rebasing fixes AuthenticatorVersion helpers additional nits allow unwraps in mocks fixed linux build clippy integrating upgrade mode into authenticator fixed build after adding wrappers to response types conditionally updating peer handle bandwidth cleanup negotiate initial protocol during registration change auth to use highest protocol handler for JWT message dont meter client bandwidth in upgrade mode handling recheck requests sending information about upgrade_mode on client messages gateway watching for upgrade mode attestation wip: gateways to disable bandwidth metering on upgrade mode * fixed ServerResponse deserialisation * fixed incorrect swagger path for upgrade mode check endpoint * moved upgrade mode endpoint out of bandwidth routes * chore: remove unused error variant * removed re-export of UpgradeModeAttestation from credentials-interface * chore: define single source of truth for minimum bandwidth threshold value * moved type definitions out of traits.rs * updated v6 versioning to point to niolo release instead * fixed incorrect error mapping
383 lines
11 KiB
Rust
383 lines
11 KiB
Rust
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
use rand::Rng;
|
|
use serde::{Deserialize, Serialize};
|
|
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;
|
|
|
|
/// Default bandwidth amount under which [mixnet] clients will attempt to send additional zk-nyms
|
|
/// to increase their allowance.
|
|
// currently defined as 20% of entry ticket value
|
|
// clients are, of course, free to override this value
|
|
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(Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
pub struct CredentialSpendingData {
|
|
pub payment: Payment,
|
|
|
|
pub pay_info: PayInfo,
|
|
|
|
pub spend_date: Date,
|
|
|
|
// pub value: u64,
|
|
/// The (DKG) epoch id under which the credential has been issued so that the verifier could use correct verification key for validation.
|
|
pub epoch_id: u64,
|
|
}
|
|
|
|
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> {
|
|
// simple length prefixed serialization
|
|
// TODO: change it to a standard format instead
|
|
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); //this is 72 bytes long
|
|
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> {
|
|
// minimum length: 72 (pay_info) + 8 (epoch_id) + 4 (spend date) + 4 (payment length prefix)
|
|
if raw.len() < 72 + 8 + 4 + 4 {
|
|
return Err(CompactEcashError::DeserializationFailure {
|
|
object: "EcashCredential".into(),
|
|
});
|
|
}
|
|
let mut index = 0;
|
|
//SAFETY : casting a slice of length 4 into an array of size 4
|
|
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 {
|
|
//SAFETY : casting a slice of length 72 into an array of size 72
|
|
pay_info_bytes: raw[index..index + 72].try_into().unwrap(),
|
|
};
|
|
index += 72;
|
|
|
|
//SAFETY : casting a slice of length 4 into an array of size 4
|
|
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(),
|
|
});
|
|
}
|
|
|
|
//SAFETY : casting a slice of length 8 into an array of size 8
|
|
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 {
|
|
/// Generates a new `NymPayInfo` instance with random bytes, a timestamp, and a provider public key.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `provider_pk` - The public key of the payment provider.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new `NymPayInfo` instance.
|
|
///
|
|
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 {
|
|
//SAFETY : slice to array of same length
|
|
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
|
|
}
|
|
}
|