add issuance logic client-side
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<C>(client: &C, amount: Coin) -> Result<State, BandwidthControllerError>
|
||||
pub async fn deposit<C>(client: &C, client_id: &[u8]) -> Result<State, BandwidthControllerError>
|
||||
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
|
||||
<St as Storage>::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());
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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),
|
||||
|
||||
@@ -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" }
|
||||
|
||||
|
||||
@@ -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<PartialExpirationDateSignatureResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.expiration_date_signatures().await?)
|
||||
}
|
||||
|
||||
pub async fn expiration_date_signatures_timestamp(
|
||||
&self,
|
||||
timestamp: u64,
|
||||
) -> Result<PartialExpirationDateSignatureResponse, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.expiration_date_signatures_timestamp(×tamp.to_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn coin_indices_signatures(
|
||||
&self,
|
||||
) -> Result<PartialCoinIndicesSignatureResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.coin_indices_signatures().await?)
|
||||
}
|
||||
|
||||
pub async fn free_pass_nonce(&self) -> Result<FreePassNonceResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.free_pass_nonce().await?)
|
||||
|
||||
@@ -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<ContractVKShare> 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<C>(
|
||||
pub async fn all_ecash_api_clients<C>(
|
||||
client: &C,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Vec<CoconutApiClient>, CoconutApiError>
|
||||
|
||||
@@ -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<PartialExpirationDateSignatureResponse, NymAPIError> {
|
||||
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<PartialExpirationDateSignatureResponse, NymAPIError> {
|
||||
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<PartialCoinIndicesSignatureResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::COCONUT_ROUTES,
|
||||
routes::BANDWIDTH,
|
||||
routes::COIN_INDICES_SIGNATURES,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn epoch_credentials(
|
||||
&self,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -123,6 +123,21 @@ impl CommonConfigsWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_private_id_key(&self) -> anyhow::Result<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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}")
|
||||
}
|
||||
|
||||
|
||||
@@ -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<C>(
|
||||
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::<u128>()?;
|
||||
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) =
|
||||
|
||||
@@ -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<FreePassIssuanceData> for BandwidthCredentialIssuanceDataVariant {
|
||||
fn from(value: FreePassIssuanceData) -> Self {
|
||||
BandwidthCredentialIssuanceDataVariant::FreePass(value)
|
||||
}
|
||||
TicketBook(BandwidthVoucherIssuanceData),
|
||||
FreePass,
|
||||
}
|
||||
|
||||
impl From<BandwidthVoucherIssuanceData> 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<B: Into<BandwidthCredentialIssuanceDataVariant>>(variant_data: B) -> Self {
|
||||
pub fn new<B: Into<BandwidthCredentialIssuanceDataVariant>>(
|
||||
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<Coin>,
|
||||
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<OffsetDateTime>) -> 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<String> {
|
||||
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<Signature, Error> {
|
||||
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<PartialWallet, Error> {
|
||||
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<Option<CredentialSigningData>>,
|
||||
) -> Result<Signature, Error> {
|
||||
// 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<PartialWallet, Error> {
|
||||
// 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<Option<CredentialSigningData>>,
|
||||
) -> Result<Signature, Error> {
|
||||
// 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<PartialWallet, Error> {
|
||||
// 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<Signature, Error> {
|
||||
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<Wallet, Error> {
|
||||
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<ExpirationDateSignature>,
|
||||
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<ExpirationDateSignature>,
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<FreePassIssuedData> for BandwidthCredentialIssuedDataVariant {
|
||||
fn from(value: FreePassIssuedData) -> Self {
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BandwidthVoucherIssuedData> 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<ExpirationDateSignature>,
|
||||
|
||||
///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<ExpirationDateSignature>,
|
||||
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<ExpirationDateSignature> {
|
||||
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<u8> {
|
||||
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<String> {
|
||||
vec![
|
||||
self.variant_data.public_value_plain(),
|
||||
self.typ().to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn prepare_for_spending(
|
||||
&self,
|
||||
verification_key: &VerificationKey,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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),
|
||||
|
||||
Reference in New Issue
Block a user