diff --git a/common/bandwidth-controller/Cargo.toml b/common/bandwidth-controller/Cargo.toml index 284ccbfc77..47e172c4f4 100644 --- a/common/bandwidth-controller/Cargo.toml +++ b/common/bandwidth-controller/Cargo.toml @@ -21,6 +21,7 @@ nym-credentials-interface = { path = "../credentials-interface" } nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] } nym-network-defaults = { path = "../network-defaults" } nym-validator-client = { path = "../client-libs/validator-client", default-features = false } +nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.nym-validator-client] path = "../client-libs/validator-client" diff --git a/common/bandwidth-controller/src/acquire/mod.rs b/common/bandwidth-controller/src/acquire/mod.rs index 4f2431b45b..83559e8e2e 100644 --- a/common/bandwidth-controller/src/acquire/mod.rs +++ b/common/bandwidth-controller/src/acquire/mod.rs @@ -5,21 +5,24 @@ use crate::error::BandwidthControllerError; use nym_credential_storage::models::StorableIssuedCredential; use nym_credential_storage::storage::Storage; use nym_credentials::coconut::bandwidth::{CredentialType, IssuanceBandwidthCredential}; -use nym_credentials::coconut::utils::obtain_aggregate_signature; +use nym_credentials::coconut::utils::{ + obtain_aggregate_signature, obtain_coin_indices_signatures, obtain_expiration_date_signatures, + signatures_to_string, +}; +use nym_credentials::obtain_aggregate_verification_key; use nym_crypto::asymmetric::{encryption, identity}; -use nym_validator_client::coconut::all_coconut_api_clients; -use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient; +use nym_validator_client::coconut::all_ecash_api_clients; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; -use nym_validator_client::nyxd::Coin; +use nym_validator_client::nyxd::contract_traits::EcashSigningClient; use rand::rngs::OsRng; use state::State; use zeroize::Zeroizing; pub mod state; -pub async fn deposit(client: &C, amount: Coin) -> Result +pub async fn deposit(client: &C, client_id: &[u8]) -> Result where - C: CoconutBandwidthSigningClient + Sync, + C: EcashSigningClient + Sync, { let mut rng = OsRng; let signing_key = identity::PrivateKey::new(&mut rng); @@ -27,8 +30,7 @@ where let tx_hash = client .deposit( - amount.clone(), - CredentialType::Voucher.to_string(), + CredentialType::TicketBook.to_string(), signing_key.public_key().to_base58_string(), encryption_key.public_key().to_base58_string(), None, @@ -37,7 +39,7 @@ where .transaction_hash; let voucher = - IssuanceBandwidthCredential::new_voucher(amount, tx_hash, signing_key, encryption_key); + IssuanceBandwidthCredential::new_voucher(tx_hash, client_id, signing_key, encryption_key); let state = State { voucher }; @@ -55,7 +57,7 @@ where ::StorageError: Send + Sync + 'static, { // temporary - assert!(state.voucher.typ().is_voucher()); + assert!(state.voucher.typ().is_ticketbook()); let epoch_id = client.get_current_epoch().await?.epoch_id; let threshold = client @@ -63,11 +65,40 @@ where .await? .ok_or(BandwidthControllerError::NoThreshold)?; - let coconut_api_clients = all_coconut_api_clients(client, epoch_id).await?; + let ecash_api_clients = all_ecash_api_clients(client, epoch_id).await?; - let signature = - obtain_aggregate_signature(&state.voucher, &coconut_api_clients, threshold).await?; - let issued = state.voucher.to_issued_credential(signature, epoch_id); + let verification_key = obtain_aggregate_verification_key(&ecash_api_clients)?; + + log::info!("Querying wallet signatures"); + let wallet = obtain_aggregate_signature(&state.voucher, &ecash_api_clients, threshold).await?; + + log::info!("Querying expiration date signatures"); + let exp_date_sig = + obtain_expiration_date_signatures(&ecash_api_clients, &verification_key, threshold).await?; + + log::info!("Checking coin indices signatures presence"); + if !storage + .is_coin_indices_sig_present(epoch_id.to_string()) + .await + .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))? + { + log::info!("Querying coin indices signatures"); + let coin_indices_signatures = + obtain_coin_indices_signatures(&ecash_api_clients, &verification_key, threshold) + .await?; + + storage + .insert_coin_indices_sig( + epoch_id.to_string(), + signatures_to_string(&coin_indices_signatures), + ) + .await + .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?; + } + + let issued = state + .voucher + .to_issued_credential(wallet, exp_date_sig, epoch_id); // make sure the data gets zeroized after persisting it let credential_data = Zeroizing::new(issued.pack_v1()); diff --git a/common/bandwidth-controller/src/error.rs b/common/bandwidth-controller/src/error.rs index 44832aaab4..7d04380937 100644 --- a/common/bandwidth-controller/src/error.rs +++ b/common/bandwidth-controller/src/error.rs @@ -1,9 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_coconut::CoconutError; use nym_credential_storage::error::StorageError; use nym_credentials::error::Error as CredentialsError; +use nym_credentials_interface::CompactEcashError; use nym_crypto::asymmetric::encryption::KeyRecoveryError; use nym_crypto::asymmetric::identity::Ed25519RecoveryError; use nym_validator_client::coconut::CoconutApiError; @@ -28,8 +28,8 @@ pub enum BandwidthControllerError { #[error(transparent)] StorageError(#[from] StorageError), - #[error("Coconut error - {0}")] - CoconutError(#[from] CoconutError), + #[error("Ecash error - {0}")] + EcashError(#[from] CompactEcashError), #[error("Validator client error - {0}")] ValidatorError(#[from] ValidatorClientError), diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 158062138a..79db925b92 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -18,6 +18,7 @@ nym-ephemera-common = { path = "../../cosmwasm-smart-contracts/ephemera" } nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" } nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } +nym-ecash-contract-common = { path = "../../cosmwasm-smart-contracts/ecash-contract" } nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } nym-name-service-common = { path = "../../cosmwasm-smart-contracts/name-service" } nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" } @@ -32,7 +33,7 @@ url = { workspace = true, features = ["serde"] } tokio = { workspace = true, features = ["sync", "time"] } futures = { workspace = true } -nym-coconut = { path = "../../nymcoconut" } +nym-compact-ecash = { path = "../../nym_offline_compact_ecash" } nym-network-defaults = { path = "../../network-defaults" } nym-api-requests = { path = "../../../nym-api/nym-api-requests" } diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 968a9ddbd3..bad5af90d5 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -10,8 +10,7 @@ use crate::{ }; use nym_api_requests::coconut::models::FreePassNonceResponse; use nym_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, FreePassRequest, VerifyCredentialBody, - VerifyCredentialResponse, + PartialCoinIndicesSignatureResponse, PartialExpirationDateSignatureResponse, }; use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated}; use nym_api_requests::models::{ @@ -350,6 +349,27 @@ impl NymApiClient { .verify_bandwidth_credential(request_body) .await?) } + pub async fn expiration_date_signatures( + &self, + ) -> Result { + Ok(self.nym_api.expiration_date_signatures().await?) + } + + pub async fn expiration_date_signatures_timestamp( + &self, + timestamp: u64, + ) -> Result { + Ok(self + .nym_api + .expiration_date_signatures_timestamp(×tamp.to_string()) + .await?) + } + + pub async fn coin_indices_signatures( + &self, + ) -> Result { + Ok(self.nym_api.coin_indices_signatures().await?) + } pub async fn free_pass_nonce(&self) -> Result { Ok(self.nym_api.free_pass_nonce().await?) diff --git a/common/client-libs/validator-client/src/coconut/mod.rs b/common/client-libs/validator-client/src/coconut/mod.rs index 66f5d0e05a..4676bf449a 100644 --- a/common/client-libs/validator-client/src/coconut/mod.rs +++ b/common/client-libs/validator-client/src/coconut/mod.rs @@ -4,9 +4,10 @@ use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient}; use crate::nyxd::error::NyxdError; use crate::NymApiClient; -use nym_coconut::{Base58, CoconutError, VerificationKey}; use nym_coconut_dkg_common::types::{EpochId, NodeIndex}; use nym_coconut_dkg_common::verification_key::ContractVKShare; +use nym_compact_ecash::error::CompactEcashError; +use nym_compact_ecash::{Base58, VerificationKeyAuth}; use thiserror::Error; use url::Url; @@ -14,7 +15,7 @@ use url::Url; #[derive(Clone)] pub struct CoconutApiClient { pub api_client: NymApiClient, - pub verification_key: VerificationKey, + pub verification_key: VerificationKeyAuth, pub node_id: NodeIndex, pub cosmos_address: cosmrs::AccountId, } @@ -43,7 +44,7 @@ pub enum CoconutApiError { #[error("the provided verification key is malformed: {source}")] MalformedVerificationKey { #[from] - source: CoconutError, + source: CompactEcashError, }, #[error("the provided account address is malformed: {source}")] @@ -65,14 +66,14 @@ impl TryFrom for CoconutApiClient { Ok(CoconutApiClient { api_client: NymApiClient::new(url_address), - verification_key: VerificationKey::try_from_bs58(&share.share)?, + verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?, node_id: share.node_index, cosmos_address: share.owner.as_str().parse()?, }) } } -pub async fn all_coconut_api_clients( +pub async fn all_ecash_api_clients( client: &C, epoch_id: EpochId, ) -> Result, CoconutApiError> 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 22b13ef576..f982d5f9c7 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -11,7 +11,7 @@ pub use nym_api_requests::{ IssuedCredentialResponse, IssuedCredentialsResponse, }, BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, - VerifyCredentialBody, VerifyCredentialResponse, + PartialCoinIndicesSignatureResponse, PartialExpirationDateSignatureResponse, }, models::{ ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse, @@ -438,6 +438,52 @@ pub trait NymApiClientExt: ApiClient { ) .await } + async fn expiration_date_signatures( + &self, + ) -> Result { + self.get_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::EXPIRATION_DATE_SIGNATURES, + ], + NO_PARAMS, + ) + .await + } + + async fn expiration_date_signatures_timestamp( + &self, + timestamp: &str, + ) -> Result { + self.get_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::EXPIRATION_DATE_SIGNATURES, + timestamp, + ], + NO_PARAMS, + ) + .await + } + + async fn coin_indices_signatures( + &self, + ) -> Result { + self.get_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COIN_INDICES_SIGNATURES, + ], + NO_PARAMS, + ) + .await + } async fn epoch_credentials( &self, 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 212ae0a8c0..bdba32bdff 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -19,6 +19,9 @@ pub const COCONUT_FREE_PASS: &str = "free-pass"; pub const COCONUT_FREE_PASS_NONCE: &str = "free-pass-nonce"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential"; +pub const EXPIRATION_DATE_SIGNATURES: &str = "expiration-date-signatures"; +pub const EXPIRATION_DATE_SIGNATURES_TIMESTAMP: &str = "expiration-date-signatures-ts"; +pub const COIN_INDICES_SIGNATURES: &str = "coin-indices-signatures"; pub const COCONUT_EPOCH_CREDENTIALS: &str = "epoch-credentials"; pub const COCONUT_ISSUED_CREDENTIAL: &str = "issued-credential"; pub const COCONUT_ISSUED_CREDENTIALS: &str = "issued-credentials"; diff --git a/common/commands/src/coconut/issue_credentials.rs b/common/commands/src/coconut/issue_credentials.rs index 9533af614e..58198082f0 100644 --- a/common/commands/src/coconut/issue_credentials.rs +++ b/common/commands/src/coconut/issue_credentials.rs @@ -7,7 +7,7 @@ use anyhow::bail; use clap::Parser; use nym_credential_storage::initialise_persistent_storage; use nym_credential_utils::utils; -use nym_validator_client::nyxd::Coin; +use nym_crypto::asymmetric::identity; use std::path::PathBuf; #[derive(Debug, Parser)] @@ -16,20 +16,12 @@ pub struct Args { #[clap(long)] pub(crate) client_config: PathBuf, - /// The amount of utokens the credential will hold. - #[clap(long, default_value = "0")] - pub(crate) amount: u64, - /// Path to a directory used to store recovery files for unconsumed deposits #[clap(long)] pub(crate) recovery_dir: PathBuf, } pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> { - if args.amount == 0 { - bail!("did not specify credential amount") - } - let loaded = CommonConfigsWrapper::try_load(args.client_config)?; if let Ok(id) = loaded.try_get_id() { @@ -40,16 +32,24 @@ pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> { bail!("the loaded config does not have a credentials store information") }; + let Ok(private_id_key) = loaded.try_get_private_id_key() else { + bail!("the loaded config does not have a public id key information") + }; + println!( "using credentials store at '{}'", credentials_store.display() ); - let denom = &client.current_chain_details().mix_denom.base; - let coin = Coin::new(args.amount as u128, denom); - let persistent_storage = initialise_persistent_storage(credentials_store).await; - utils::issue_credential(&client, coin, &persistent_storage, args.recovery_dir).await?; + let private_id_key: identity::PrivateKey = nym_pemstore::load_key(private_id_key)?; + utils::issue_credential( + &client, + &private_id_key.to_bytes(), + &persistent_storage, + args.recovery_dir, + ) + .await?; Ok(()) } diff --git a/common/commands/src/utils.rs b/common/commands/src/utils.rs index 25797c71e9..0d6e40ffbc 100644 --- a/common/commands/src/utils.rs +++ b/common/commands/src/utils.rs @@ -123,6 +123,21 @@ impl CommonConfigsWrapper { } } + pub(crate) fn try_get_private_id_key(&self) -> anyhow::Result { + match self { + CommonConfigsWrapper::NymClients(cfg) => Ok(cfg + .storage_paths + .inner + .keys + .private_identity_key_file + .clone()), + CommonConfigsWrapper::NymApi(_cfg) => { + todo!() //SW this will depend on the new network monitor structure. Ping @Drazen + } + CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_private_id_key(), + } + } + pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result { match self { CommonConfigsWrapper::NymClients(cfg) => { @@ -225,4 +240,17 @@ impl UnknownConfigWrapper { bail!("no 'credentials_database_path' field present in the config") } } + + pub(crate) fn try_get_private_id_key(&self) -> anyhow::Result { + let id_val = self + .find_value("keys.private_identity_key_file") + .ok_or_else(|| { + anyhow!("no 'keys.private_identity_key_file' field present in the config") + })?; + if let toml::Value::String(pub_id_key) = id_val { + Ok(pub_id_key.parse()?) + } else { + bail!("no 'keys.private_identity_key_file' field present in the config") + } + } } diff --git a/common/credential-utils/Cargo.toml b/common/credential-utils/Cargo.toml index 232c9cdb1f..61bfae0e94 100644 --- a/common/credential-utils/Cargo.toml +++ b/common/credential-utils/Cargo.toml @@ -18,3 +18,4 @@ nym-credential-storage = { path = "../../common/credential-storage" } nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-config = { path = "../../common/config" } nym-client-core = { path = "../../common/client-core" } +nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } diff --git a/common/credential-utils/src/recovery_storage.rs b/common/credential-utils/src/recovery_storage.rs index 0344a3ad86..11803c3b26 100644 --- a/common/credential-utils/src/recovery_storage.rs +++ b/common/credential-utils/src/recovery_storage.rs @@ -53,7 +53,7 @@ impl RecoveryStorage { pub fn voucher_filename(voucher: &IssuanceBandwidthCredential) -> String { let prefix = voucher.typ().to_string(); - let suffix = voucher.blinded_serial_number_bs58(); + let suffix = voucher.ecash_pubkey_bs58(); format!("{prefix}-{suffix}.{DUMPED_VOUCHER_EXTENSION}") } diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs index 2b2aaba9f7..123d953fb5 100644 --- a/common/credential-utils/src/utils.rs +++ b/common/credential-utils/src/utils.rs @@ -7,9 +7,8 @@ use nym_config::DEFAULT_DATA_DIR; use nym_credential_storage::persistent_storage::PersistentStorage; use nym_credentials::coconut::bandwidth::CredentialType; use nym_validator_client::nyxd::contract_traits::{ - dkg_query_client::EpochState, CoconutBandwidthSigningClient, DkgQueryClient, + dkg_query_client::EpochState, DkgQueryClient, EcashSigningClient, }; -use nym_validator_client::nyxd::Coin; use std::path::PathBuf; use std::process::exit; use std::time::{Duration, SystemTime}; @@ -18,12 +17,12 @@ const SAFETY_BUFFER_SECS: u64 = 60; // 1 minute pub async fn issue_credential( client: &C, - amount: Coin, + client_id: &[u8], persistent_storage: &PersistentStorage, recovery_storage_path: PathBuf, ) -> Result<()> where - C: DkgQueryClient + CoconutBandwidthSigningClient + Send + Sync, + C: DkgQueryClient + EcashSigningClient + Send + Sync, { let recovery_storage = setup_recovery_storage(recovery_storage_path).await; @@ -42,7 +41,8 @@ where } }; - let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?; + let state = nym_bandwidth_controller::acquire::deposit(client, client_id).await?; + info!("Deposit done"); if nym_bandwidth_controller::acquire::get_bandwidth_voucher(&state, client, persistent_storage) .await @@ -63,7 +63,7 @@ where )); } - info!("Succeeded adding a credential with amount {amount}"); + info!("Succeeded adding a ticketbook credential"); Ok(()) } @@ -130,15 +130,25 @@ where let mut recovered_amount: u128 = 0; for voucher in recovery_storage.unconsumed_vouchers()? { let voucher_value = match voucher.typ() { - CredentialType::Voucher => voucher.get_bandwidth_attribute(), + CredentialType::TicketBook => voucher.value(), + CredentialType::Voucher => { + error!("Impossible to recover old coconut voucher"); + continue; + } CredentialType::FreePass => { error!("unimplemented recovery of free pass credentials"); continue; } }; - recovered_amount += voucher_value.parse::()?; + recovered_amount += voucher_value; let voucher_name = RecoveryStorage::voucher_filename(&voucher); + + if voucher.check_expiration_date() { + //We did change the expiration + warn!("Deposit {} was made with a different expiration date, it's validity will be shorter than the max one", voucher_name); + } + let state = State::new(voucher); if let Err(e) = diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 53c89a300b..fe4a9ae3be 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -7,70 +7,44 @@ use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData; use crate::coconut::bandwidth::{ bandwidth_credential_params, CredentialSigningData, CredentialType, }; -use crate::coconut::utils::scalar_serde_helper; +use crate::coconut::utils::{cred_exp_date_timestamp, freepass_exp_date_timestamp}; use crate::error::Error; +use log::error; use nym_credentials_interface::{ - aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSerialNumber, - BlindedSignature, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, - VerificationKey, + aggregate_wallets, constants, generate_keypair_user, generate_keypair_user_from_seed, + issue_verify, setup, withdrawal_request, BlindedSignature, ExpirationDateSignature, + KeyPairUser, Parameters, PartialWallet, VerificationKeyAuth, Wallet, }; use nym_crypto::asymmetric::{encryption, identity}; use nym_validator_client::nym_api::EpochId; -use nym_validator_client::nyxd::{Coin, Hash}; +use nym_validator_client::nyxd::Hash; use nym_validator_client::signing::AccountData; use serde::{Deserialize, Serialize}; -use time::OffsetDateTime; use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] pub enum BandwidthCredentialIssuanceDataVariant { - Voucher(BandwidthVoucherIssuanceData), - FreePass(FreePassIssuanceData), -} - -impl From for BandwidthCredentialIssuanceDataVariant { - fn from(value: FreePassIssuanceData) -> Self { - BandwidthCredentialIssuanceDataVariant::FreePass(value) - } + TicketBook(BandwidthVoucherIssuanceData), + FreePass, } impl From for BandwidthCredentialIssuanceDataVariant { fn from(value: BandwidthVoucherIssuanceData) -> Self { - BandwidthCredentialIssuanceDataVariant::Voucher(value) + BandwidthCredentialIssuanceDataVariant::TicketBook(value) } } impl BandwidthCredentialIssuanceDataVariant { pub fn info(&self) -> CredentialType { match self { - BandwidthCredentialIssuanceDataVariant::Voucher(..) => CredentialType::Voucher, - BandwidthCredentialIssuanceDataVariant::FreePass(..) => CredentialType::FreePass, - } - } - - // currently this works under the assumption of there being a single unique public attribute for given variant - pub fn public_value(&self) -> &Attribute { - match self { - BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => voucher.value_attribute(), - BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => { - freepass.expiry_date_attribute() - } - } - } - - // currently this works under the assumption of there being a single unique public attribute for given variant - pub fn public_value_plain(&self) -> String { - match self { - BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => voucher.value_plain(), - BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => { - freepass.expiry_date_plain() - } + BandwidthCredentialIssuanceDataVariant::TicketBook(..) => CredentialType::TicketBook, + BandwidthCredentialIssuanceDataVariant::FreePass => CredentialType::FreePass, } } pub fn voucher_data(&self) -> Option<&BandwidthVoucherIssuanceData> { match self { - BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => Some(voucher), + BandwidthCredentialIssuanceDataVariant::TicketBook(voucher) => Some(voucher), _ => None, } } @@ -79,102 +53,103 @@ impl BandwidthCredentialIssuanceDataVariant { // all types of bandwidth credentials contain serial number and binding number #[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] pub struct IssuanceBandwidthCredential { - // private attributes - /// a random secret value generated by the client used for double-spending detection - #[serde(with = "scalar_serde_helper")] - serial_number: PrivateAttribute, - - /// a random secret value generated by the client used to bind multiple credentials together - #[serde(with = "scalar_serde_helper")] - binding_number: PrivateAttribute, - /// data specific to given bandwidth credential, for example a value for bandwidth voucher and expiry date for the free pass variant_data: BandwidthCredentialIssuanceDataVariant, - /// type of the bandwdith credential hashed onto a scalar - #[serde(with = "scalar_serde_helper")] - type_prehashed: PublicAttribute, + ///ecash keypair related to the credential + ecash_keypair: KeyPairUser, + + ///expiration_date of that credential + expiration_date: u64, } impl IssuanceBandwidthCredential { - pub const PUBLIC_ATTRIBUTES: u32 = 2; - pub const PRIVATE_ATTRIBUTES: u32 = 2; - pub const ENCODED_ATTRIBUTES: u32 = Self::PUBLIC_ATTRIBUTES + Self::PRIVATE_ATTRIBUTES; - pub fn default_parameters() -> Parameters { - // safety: the unwrap is fine here as Self::ENCODED_ATTRIBUTES is non-zero - Parameters::new(Self::ENCODED_ATTRIBUTES).unwrap() + setup(constants::NB_TICKETS) } - pub fn new>(variant_data: B) -> Self { + pub fn new>( + variant_data: B, + identifier: Option<&[u8]>, + expiration_date: u64, + ) -> Self { let variant_data = variant_data.into(); - let type_prehashed = hash_to_scalar(variant_data.info().to_string()); - - let params = bandwidth_credential_params(); - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); + let params = bandwidth_credential_params().grp(); + let ecash_keypair = if let Some(id) = identifier { + generate_keypair_user_from_seed(params, id) + } else { + generate_keypair_user(params) + }; IssuanceBandwidthCredential { - serial_number, - binding_number, variant_data, - type_prehashed, + ecash_keypair, + expiration_date, } } pub fn new_voucher( - value: impl Into, deposit_tx_hash: Hash, + identifier: &[u8], signing_key: identity::PrivateKey, unused_ed25519: encryption::PrivateKey, ) -> Self { - Self::new(BandwidthVoucherIssuanceData::new( - value, - deposit_tx_hash, - signing_key, - unused_ed25519, - )) + Self::new( + BandwidthVoucherIssuanceData::new(deposit_tx_hash, signing_key, unused_ed25519), + Some(identifier), + cred_exp_date_timestamp(), + ) } - pub fn new_freepass(expiry_date: Option) -> Self { - Self::new(FreePassIssuanceData::new(expiry_date)) + pub fn new_freepass(timestamp: u64) -> Self { + let exp_timestamp = if timestamp > freepass_exp_date_timestamp() { + error!( + "the provided free pass request has too long expiry, setting it to max possible" + ); + freepass_exp_date_timestamp() + } else { + timestamp + }; + Self::new( + BandwidthCredentialIssuanceDataVariant::FreePass, + None, + exp_timestamp, + ) } - pub fn blind_serial_number(&self) -> BlindedSerialNumber { - (bandwidth_credential_params().gen2() * self.serial_number).into() - } - - pub fn blinded_serial_number_bs58(&self) -> String { + pub fn ecash_pubkey_bs58(&self) -> String { use nym_credentials_interface::Base58; - self.blind_serial_number().to_bs58() + self.ecash_keypair.public_key().to_bs58() } pub fn typ(&self) -> CredentialType { self.variant_data.info() } - pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> { - vec![&self.serial_number, &self.binding_number] - } - - pub fn get_public_attributes(&self) -> Vec<&PublicAttribute> { - vec![self.variant_data.public_value(), &self.type_prehashed] - } - - pub fn get_plain_public_attributes(&self) -> Vec { - vec![ - self.variant_data.public_value_plain(), - self.typ().to_string(), - ] + pub fn expiration_date(&self) -> u64 { + self.expiration_date } pub fn get_variant_data(&self) -> &BandwidthCredentialIssuanceDataVariant { &self.variant_data } - pub fn get_bandwidth_attribute(&self) -> String { - self.variant_data.public_value_plain() + pub fn value(&self) -> u128 { + if let BandwidthCredentialIssuanceDataVariant::TicketBook(data) = &self.variant_data { + data.value() + } else { + 0_u128 + } + } + + pub fn check_expiration_date(&self) -> bool { + let old_expiration_date = self.expiration_date; + let new_expiration_date = match self.get_variant_data() { + BandwidthCredentialIssuanceDataVariant::TicketBook(_) => cred_exp_date_timestamp(), + BandwidthCredentialIssuanceDataVariant::FreePass => freepass_exp_date_timestamp(), + }; + old_expiration_date != new_expiration_date } pub fn prepare_for_signing(&self) -> CredentialSigningData { @@ -182,38 +157,37 @@ impl IssuanceBandwidthCredential { // safety: the creation of the request can only fail if one provided invalid parameters // and we created then specific to this type of the credential so the unwrap is fine - let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign( - params, - &[&self.serial_number, &self.binding_number], - &self.get_public_attributes(), + let (withdrawal_request, request_info) = withdrawal_request( + params.grp(), + &self.ecash_keypair.secret_key(), + self.expiration_date, ) .unwrap(); CredentialSigningData { - pedersen_commitments_openings, - blind_sign_request, - public_attributes_plain: self.get_plain_public_attributes(), + withdrawal_request, + request_info, + ecash_pub_key: self.ecash_keypair.public_key(), typ: self.typ(), + expiration_date: self.expiration_date, } } pub fn unblind_signature( &self, - validator_vk: &VerificationKey, + validator_vk: &VerificationKeyAuth, signing_data: &CredentialSigningData, blinded_signature: BlindedSignature, - ) -> Result { - let public_attributes = self.get_public_attributes(); - let private_attributes = self.get_private_attributes(); - - let params = bandwidth_credential_params(); - let unblinded_signature = blinded_signature.unblind_and_verify( + signer_index: u64, + ) -> Result { + let params = bandwidth_credential_params().grp(); + let unblinded_signature = issue_verify( params, validator_vk, - &private_attributes, - &public_attributes, - &signing_data.blind_sign_request.get_commitment_hash(), - &signing_data.pedersen_commitments_openings, + &self.ecash_keypair.secret_key(), + &blinded_signature, + &signing_data.request_info, + signer_index, )?; Ok(unblinded_signature) @@ -222,88 +196,88 @@ impl IssuanceBandwidthCredential { pub async fn obtain_partial_freepass_credential( &self, client: &nym_validator_client::client::NymApiClient, + signer_index: u64, account_data: &AccountData, - validator_vk: &VerificationKey, - signing_data: impl Into>, - ) -> Result { - // if we provided signing data, do use them, otherwise generate fresh data - let signing_data = signing_data - .into() - .unwrap_or_else(|| self.prepare_for_signing()); + validator_vk: &VerificationKeyAuth, + signing_data: CredentialSigningData, + ) -> Result { + // We need signing data, because they will be use at the aggregation step let blinded_signature = match &self.variant_data { - BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => { - freepass - .request_blinded_credential(&signing_data, account_data, client) - .await? + BandwidthCredentialIssuanceDataVariant::FreePass => { + FreePassIssuanceData::request_blinded_credential( + &signing_data, + account_data, + client, + ) + .await? } _ => return Err(Error::NotAFreePass), }; - self.unblind_signature(validator_vk, &signing_data, blinded_signature) + self.unblind_signature(validator_vk, &signing_data, blinded_signature, signer_index) } // ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers pub async fn obtain_partial_bandwidth_voucher_credential( &self, client: &nym_validator_client::client::NymApiClient, - validator_vk: &VerificationKey, - signing_data: impl Into>, - ) -> Result { - // if we provided signing data, do use them, otherwise generate fresh data - let signing_data = signing_data - .into() - .unwrap_or_else(|| self.prepare_for_signing()); + signer_index: u64, + validator_vk: &VerificationKeyAuth, + signing_data: CredentialSigningData, + ) -> Result { + // We need signing data, because they will be use at the aggregation step let blinded_signature = match &self.variant_data { - BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => { + BandwidthCredentialIssuanceDataVariant::TicketBook(voucher) => { // TODO: the request can be re-used between different apis let request = voucher.create_blind_sign_request_body(&signing_data); voucher.obtain_blinded_credential(client, &request).await? } _ => return Err(Error::NotABandwdithVoucher), }; - self.unblind_signature(validator_vk, &signing_data, blinded_signature) + self.unblind_signature(validator_vk, &signing_data, blinded_signature, signer_index) } pub fn aggregate_signature_shares( &self, - verification_key: &VerificationKey, - shares: &[SignatureShare], - ) -> Result { - let public_attributes = self.get_public_attributes(); - let private_attributes = self.get_private_attributes(); - - let params = bandwidth_credential_params(); - - let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); - attributes.extend_from_slice(&private_attributes); - attributes.extend_from_slice(&public_attributes); - - aggregate_signature_shares(params, verification_key, &attributes, shares) - .map_err(Error::SignatureAggregationError) + verification_key: &VerificationKeyAuth, + shares: &[PartialWallet], + signing_data: CredentialSigningData, + ) -> Result { + let params = bandwidth_credential_params().grp(); + aggregate_wallets( + params, + verification_key, + &self.ecash_keypair.secret_key(), + shares, + &signing_data.request_info, + ) + .map_err(Error::SignatureAggregationError) } // also drops self after the conversion pub fn into_issued_credential( self, - aggregate_signature: Signature, + wallet: Wallet, + exp_date_signatures: Vec, epoch_id: EpochId, ) -> IssuedBandwidthCredential { - self.to_issued_credential(aggregate_signature, epoch_id) + self.to_issued_credential(wallet, exp_date_signatures, epoch_id) } pub fn to_issued_credential( &self, - aggregate_signature: Signature, + wallet: Wallet, + exp_date_signatures: Vec, epoch_id: EpochId, ) -> IssuedBandwidthCredential { IssuedBandwidthCredential::new( - self.serial_number, - self.binding_number, - aggregate_signature, + wallet, (&self.variant_data).into(), - self.type_prehashed, epoch_id, + self.ecash_keypair.secret_key(), + exp_date_signatures, + self.expiration_date, ) } diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index 5e2951d1d6..4fd010c62c 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -2,70 +2,54 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::bandwidth::bandwidth_credential_params; -use crate::coconut::bandwidth::freepass::FreePassIssuedData; use crate::coconut::bandwidth::issuance::{ BandwidthCredentialIssuanceDataVariant, IssuanceBandwidthCredential, }; use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuedData; use crate::coconut::bandwidth::{CredentialSpendingData, CredentialType}; -use crate::coconut::utils::scalar_serde_helper; +use crate::coconut::utils::today_timestamp; use crate::error::Error; -use nym_credentials_interface::prove_bandwidth_credential; use nym_credentials_interface::{ - Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey, + constants, date_scalar, CoinIndexSignature, ExpirationDateSignature, Parameters, PayInfo, + SecretKeyUser, VerificationKeyAuth, Wallet, }; use nym_validator_client::nym_api::EpochId; use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; use zeroize::{Zeroize, ZeroizeOnDrop}; pub const CURRENT_SERIALIZATION_REVISION: u8 = 1; #[derive(Debug, Zeroize, Serialize, Deserialize)] pub enum BandwidthCredentialIssuedDataVariant { - Voucher(BandwidthVoucherIssuedData), - FreePass(FreePassIssuedData), + TicketBook(BandwidthVoucherIssuedData), + FreePass, } impl<'a> From<&'a BandwidthCredentialIssuanceDataVariant> for BandwidthCredentialIssuedDataVariant { fn from(value: &'a BandwidthCredentialIssuanceDataVariant) -> Self { match value { - BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => { - BandwidthCredentialIssuedDataVariant::Voucher(voucher.into()) + BandwidthCredentialIssuanceDataVariant::TicketBook(voucher) => { + BandwidthCredentialIssuedDataVariant::TicketBook(voucher.into()) } - BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => { - BandwidthCredentialIssuedDataVariant::FreePass(freepass.into()) + BandwidthCredentialIssuanceDataVariant::FreePass => { + BandwidthCredentialIssuedDataVariant::FreePass } } } } -impl From for BandwidthCredentialIssuedDataVariant { - fn from(value: FreePassIssuedData) -> Self { - BandwidthCredentialIssuedDataVariant::FreePass(value) - } -} - impl From for BandwidthCredentialIssuedDataVariant { fn from(value: BandwidthVoucherIssuedData) -> Self { - BandwidthCredentialIssuedDataVariant::Voucher(value) + BandwidthCredentialIssuedDataVariant::TicketBook(value) } } impl BandwidthCredentialIssuedDataVariant { pub fn info(&self) -> CredentialType { match self { - BandwidthCredentialIssuedDataVariant::Voucher(..) => CredentialType::Voucher, - BandwidthCredentialIssuedDataVariant::FreePass(..) => CredentialType::FreePass, - } - } - - // currently this works under the assumption of there being a single unique public attribute for given variant - pub fn public_value_plain(&self) -> String { - match self { - BandwidthCredentialIssuedDataVariant::Voucher(voucher) => voucher.value_plain(), - BandwidthCredentialIssuedDataVariant::FreePass(freepass) => { - freepass.expiry_date_plain() - } + BandwidthCredentialIssuedDataVariant::TicketBook(..) => CredentialType::TicketBook, + BandwidthCredentialIssuedDataVariant::FreePass => CredentialType::FreePass, } } } @@ -73,46 +57,42 @@ impl BandwidthCredentialIssuedDataVariant { // the only important thing to zeroize here are the private attributes, the rest can be made fully public for what we're concerned #[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] pub struct IssuedBandwidthCredential { - // private attributes - /// a random secret value generated by the client used for double-spending detection - #[serde(with = "scalar_serde_helper")] - serial_number: PrivateAttribute, - - /// a random secret value generated by the client used to bind multiple credentials together - #[serde(with = "scalar_serde_helper")] - binding_number: PrivateAttribute, - - /// the underlying aggregated signature on the attributes - #[zeroize(skip)] - signature: Signature, + /// the underlying wallet + wallet: Wallet, /// data specific to given bandwidth credential, for example a value for bandwidth voucher and expiry date for the free pass - variant_data: BandwidthCredentialIssuedDataVariant, - - /// type of the bandwdith credential hashed onto a scalar - #[serde(with = "scalar_serde_helper")] - type_prehashed: PublicAttribute, + variant_data: BandwidthCredentialIssuedDataVariant, //SW NOTE: freepass has no info, maybe put value directly here /// Specifies the (DKG) epoch id when this credential has been issued epoch_id: EpochId, + + ///secret ecash key used to generate this wallet + ecash_secret_key: SecretKeyUser, + + ///signatures on expiration dates used to spend tickets + #[zeroize(skip)] + exp_date_signatures: Vec, + + ///expiration_date for easier discarding + expiration_date: u64, } impl IssuedBandwidthCredential { pub fn new( - serial_number: PrivateAttribute, - binding_number: PrivateAttribute, - signature: Signature, + wallet: Wallet, variant_data: BandwidthCredentialIssuedDataVariant, - type_prehashed: PublicAttribute, epoch_id: EpochId, + ecash_secret_key: SecretKeyUser, + exp_date_signatures: Vec, + expiration_date: u64, ) -> Self { IssuedBandwidthCredential { - serial_number, - binding_number, - signature, + wallet, variant_data, - type_prehashed, epoch_id, + ecash_secret_key, + exp_date_signatures, + expiration_date, } } @@ -137,6 +117,27 @@ impl IssuedBandwidthCredential { CURRENT_SERIALIZATION_REVISION } + pub fn expiration_date(&self) -> u64 { + self.expiration_date + } + + pub fn expiration_date_formatted(&self) -> OffsetDateTime { + //SAFETY : expiration date is encoded as a u64 but it is a unix timestamp. The unwrap is guaranteed to succeed for at least 290 million more years + OffsetDateTime::from_unix_timestamp(self.expiration_date.try_into().unwrap()).unwrap() + } + + pub fn expired(&self) -> bool { + self.expiration_date < today_timestamp() + } + + pub fn exp_date_sigs(&self) -> Vec { + self.exp_date_signatures.clone() + } + + pub fn wallet(&self) -> &Wallet { + &self.wallet + } + /// Pack (serialize) this credential data into a stream of bytes using v1 serializer. pub fn pack_v1(&self) -> Vec { use bincode::Options; @@ -155,11 +156,6 @@ impl IssuedBandwidthCredential { }) } - pub fn randomise_signature(&mut self) { - let signature_prime = self.signature.randomise(bandwidth_credential_params()); - self.signature = signature_prime.0 - } - pub fn default_parameters() -> Parameters { IssuanceBandwidthCredential::default_parameters() } @@ -168,13 +164,6 @@ impl IssuedBandwidthCredential { self.variant_data.info() } - pub fn get_plain_public_attributes(&self) -> Vec { - vec![ - self.variant_data.public_value_plain(), - self.typ().to_string(), - ] - } - pub fn prepare_for_spending( &self, verification_key: &VerificationKey, diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 6487d23870..4c922c5589 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_credentials_interface::CoconutError; +use nym_credentials_interface::CompactEcashError; use nym_crypto::asymmetric::encryption::KeyRecoveryError; use nym_validator_client::ValidatorClientError; @@ -32,8 +32,8 @@ pub enum Error { #[error("Could not contact any validator")] NoValidatorsAvailable, - #[error("Ran into a coconut error - {0}")] - CoconutError(#[from] CoconutError), + #[error("Ran into a Compact ecash error - {0}")] + CompactEcashError(#[from] CompactEcashError), #[error("Ran into a validator client error - {0}")] ValidatorClientError(#[from] ValidatorClientError), @@ -51,7 +51,7 @@ pub enum Error { NotEnoughShares, #[error("Could not aggregate signature shares - {0}. Try again using the recovery command")] - SignatureAggregationError(CoconutError), + SignatureAggregationError(CompactEcashError), #[error("Could not deserialize bandwidth voucher - {0}")] BandwidthVoucherDeserializationError(String),