add issuance logic client-side

This commit is contained in:
Simon Wicky
2024-05-07 10:41:59 +02:00
parent db3bd66cf1
commit e968a271ce
16 changed files with 378 additions and 273 deletions
+1
View File
@@ -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"
+45 -14
View File
@@ -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());
+3 -3
View File
@@ -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(&timestamp.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(())
}
+28
View File
@@ -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")
}
}
}
+1
View File
@@ -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}")
}
+18 -8
View File
@@ -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,
+4 -4
View File
@@ -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),