Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 74 additions & 212 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions common/bandwidth-controller/src/acquire/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0

use crate::error::BandwidthControllerError;
use crate::utils::{get_coin_index_signatures, get_expiration_date_signatures};
use crate::utils::{
get_aggregate_verification_key, get_coin_index_signatures, get_expiration_date_signatures,
};
use log::info;
use nym_credential_storage::storage::Storage;
use nym_credentials::ecash::bandwidth::IssuanceTicketBook;
Expand Down Expand Up @@ -55,7 +57,7 @@ where
))
}

pub async fn query_and_persist_required_global_signatures<S>(
pub async fn query_and_persist_required_global_data<S>(
storage: &S,
epoch_id: EpochId,
expiration_date: Date,
Expand All @@ -65,6 +67,10 @@ where
S: Storage,
<S as Storage>::StorageError: Send + Sync + 'static,
{
log::info!("Getting master verification key");
// this will also persist the key in the storage if was not there already
get_aggregate_verification_key(storage, epoch_id, apis.clone()).await?;

log::info!("Getting expiration date signatures");
// this will also persist the signatures in the storage if they were not there already
get_expiration_date_signatures(storage, epoch_id, expiration_date, apis.clone()).await?;
Expand Down
6 changes: 4 additions & 2 deletions common/bandwidth-controller/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use nym_credential_storage::models::RetrievedTicketbook;
use nym_credential_storage::storage::Storage;
use nym_credentials::ecash::bandwidth::CredentialSpendingData;
use nym_credentials_interface::{
AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, NymPayInfo, VerificationKeyAuth,
AnnotatedCoinIndexSignature, AnnotatedExpirationDateSignature, VerificationKeyAuth,
};
use nym_ecash_time::Date;
use nym_validator_client::nym_api::EpochId;
Expand Down Expand Up @@ -165,7 +165,9 @@ impl<C, St: Storage> BandwidthController<C, St> {
.get_coin_index_signatures(epoch_id, &mut api_clients)
.await?;

let pay_info = NymPayInfo::generate(provider_pk);
let pay_info = retrieved_ticketbook
.ticketbook
.generate_pay_info(provider_pk);

let spend_request = retrieved_ticketbook.ticketbook.prepare_for_spending(
&verification_key,
Expand Down
9 changes: 5 additions & 4 deletions common/commands/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ license.workspace = true

[dependencies]
anyhow = { workspace = true }
base64 = "0.13.0"
base64 = { workspace = true }
bip39 = { workspace = true }
bs58 = { workspace = true }
colored = { workspace = true }
comfy-table = { workspace = true }
cfg-if = { workspace = true }
clap = { workspace = true, features = ["derive"] }
Expand All @@ -21,13 +22,13 @@ humantime-serde = { workspace = true }
inquire = { workspace = true }
k256 = { workspace = true, features = ["ecdsa", "sha256"] }
log = { workspace = true }
rand = {version = "0.6", features = ["std"] }
rand = { workspace = true, features = ["std"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
time = { workspace = true, features = ["parsing", "formatting"] }
tokio = { workspace = true, features = ["sync"]}
toml = "0.5.6"
tokio = { workspace = true, features = ["sync"] }
toml = { workspace = true }
url = { workspace = true }
tap = { workspace = true }
zeroize = { workspace = true }
Expand Down
178 changes: 178 additions & 0 deletions common/commands/src/ecash/generate_ticket.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Copyright 2024 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: Apache-2.0

use crate::utils::CommonConfigsWrapper;
use anyhow::{anyhow, bail};
use clap::Parser;
use colored::Colorize;
use comfy_table::Table;
use nym_credential_storage::initialise_persistent_storage;
use nym_credential_storage::storage::Storage;
use nym_credentials::ecash::bandwidth::serialiser::VersionedSerialise;
use std::path::PathBuf;

#[derive(Debug, Parser)]
pub struct Args {
/// Specify the index of the ticket to retrieve from the ticketbook.
/// By default, the current unspent value is used.
#[clap(long, group = "output")]
pub(crate) ticket_index: Option<u64>,

/// Specify whether we should display payments for ALL available tickets
#[clap(long, group = "output")]
pub(crate) full: bool,

/// Base58-encoded identity of the provider (must be 32 bytes long)
#[clap(long)]
pub(crate) provider: String,

/// Config file of the client that is supposed to use the credential.
#[clap(long, group = "source")]
pub(crate) client_config: Option<PathBuf>,

/// Path to the dedicated credential storage database
#[clap(long, group = "source")]
pub(crate) credential_storage: Option<PathBuf>,
}

pub async fn execute(args: Args) -> anyhow::Result<()> {
let credentials_store = if let Some(explicit) = args.credential_storage {
explicit
} else {
// SAFETY: at least one of them MUST HAVE been specified
let cfg = args.client_config.unwrap();

let loaded = CommonConfigsWrapper::try_load(cfg)?;

if let Ok(id) = loaded.try_get_id() {
println!("loaded config file for client '{id}'");
}

let Ok(credentials_store) = loaded.try_get_credentials_store() else {
bail!("the loaded config does not have a credentials store information")
};
credentials_store
};

let decoded_provider = bs58::decode(&args.provider).into_vec()?;
if decoded_provider.len() != 32 {
bail!("the provided provider information is malformed")
}
let provider_arr: [u8; 32] = decoded_provider.try_into().unwrap();

let persistent_storage = initialise_persistent_storage(&credentials_store).await;
let Some(mut next_ticketbook) = persistent_storage
.get_next_unspent_usable_ticketbook(0)
.await?
else {
bail!(
"there are no valid ticketbooks in the storage at {}",
credentials_store.display()
)
};

let epoch_id = next_ticketbook.ticketbook.epoch_id();
let expiration_date = next_ticketbook.ticketbook.expiration_date();

let verification_key = persistent_storage
.get_master_verification_key(epoch_id)
.await?
.ok_or_else(|| {
anyhow!("ticketbook got incorrectly imported - the master verification key is missing")
})?;
let expiration_signatures = persistent_storage
.get_expiration_date_signatures(expiration_date)
.await?
.ok_or_else(|| {
anyhow!(
"ticketbook got incorrectly imported - the expiration date signatures are missing"
)
})?;
let coin_indices_signatures = persistent_storage
.get_coin_index_signatures(epoch_id)
.await?
.ok_or_else(|| {
anyhow!("ticketbook got incorrectly imported - the coin index signatures are missing")
})?;

let ticketbook_data = next_ticketbook.ticketbook.pack();

let next_ticket = args
.ticket_index
.unwrap_or(next_ticketbook.ticketbook.spent_tickets());
let pay_info = next_ticketbook.ticketbook.generate_pay_info(provider_arr);

println!("{}", "TICKETBOOK DATA:".bold());
println!("{}", bs58::encode(&ticketbook_data.data).into_string());
println!();

// display it only for a single ticket
if !args.full {
println!("attempting to generate payment for ticket {next_ticket}...");
println!();
next_ticketbook.ticketbook.update_spent_tickets(next_ticket);

let req = next_ticketbook.ticketbook.prepare_for_spending(
&verification_key,
pay_info.into(),
&coin_indices_signatures,
&expiration_signatures,
1,
)?;

let payment = req.payment;

println!("{}", format!("PAYMENT FOR TICKET {next_ticket}: ").bold());
println!("{}", bs58::encode(&payment.to_bytes()).into_string());
return Ok(());
}

println!(
"generating payment information for {} tickets. this might take a while!...",
next_ticketbook.ticketbook.params_total_tickets()
);

// otherwise generate all the payments
let last_spent = next_ticketbook.ticketbook.spent_tickets();

let mut table = Table::new();
table.set_header(vec!["index", "binary data", "spend status"]);

for i in 0..next_ticketbook.ticketbook.params_total_tickets() {
let status = if i < last_spent {
"SPENT".red()
} else {
"NOT SPENT".green()
};

next_ticketbook.ticketbook.update_spent_tickets(i);

let req = next_ticketbook.ticketbook.prepare_for_spending(
&verification_key,
pay_info.into(),
&coin_indices_signatures,
&expiration_signatures,
1,
)?;

let payment = req.payment;
let payment_bytes = payment.to_bytes();
let len = payment_bytes.len();
let display_size = 100;
let remaining = len - display_size;

table.add_row(vec![
i.to_string(),
format!(
"{}…{remaining}bytes remaining",
bs58::encode(&payment_bytes[..display_size]).into_string()
),
status.to_string(),
]);
}

println!("{}", "AVAILABLE TICKETS".bold());
println!("{table}");

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use nym_credential_storage::initialise_persistent_storage;
use nym_credential_utils::utils;
use nym_credentials_interface::TicketType;
use nym_crypto::asymmetric::identity;
use rand::rngs::OsRng;
use rand::RngCore;
use std::fs::create_dir_all;
use std::path::PathBuf;

#[derive(Debug, Parser)]
Expand All @@ -18,12 +21,20 @@ pub struct Args {
pub(crate) ticketbook_type: TicketType,

/// Config file of the client that is supposed to use the credential.
#[clap(long)]
pub(crate) client_config: PathBuf,
#[clap(long, group = "output")]
pub(crate) client_config: Option<PathBuf>,

/// Path to the dedicated credential storage database
#[clap(long, group = "output")]
pub(crate) credential_storage: Option<PathBuf>,
}

pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
let loaded = CommonConfigsWrapper::try_load(args.client_config)?;
async fn issue_client_ticketbook(
cfg: PathBuf,
typ: TicketType,
client: SigningClient,
) -> anyhow::Result<()> {
let loaded = CommonConfigsWrapper::try_load(cfg)?;

if let Ok(id) = loaded.try_get_id() {
println!("loaded config file for client '{id}'");
Expand All @@ -48,9 +59,40 @@ pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
&client,
&persistent_storage,
&private_id_key.to_bytes(),
args.ticketbook_type,
typ,
)
.await?;

Ok(())
}

async fn issue_standalone_ticketbook(
credentials_store: PathBuf,
typ: TicketType,
client: SigningClient,
) -> anyhow::Result<()> {
println!("attempting to issue a standalone ticketbook");

let mut rng = OsRng;
let mut random_seed = [0u8; 32];
rng.fill_bytes(&mut random_seed);

if let Some(parent) = credentials_store.parent() {
create_dir_all(parent)?;
}

let persistent_storage = initialise_persistent_storage(credentials_store).await;
utils::issue_credential(&client, &persistent_storage, &random_seed, typ).await?;

Ok(())
}

pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
match (args.client_config, args.credential_storage) {
(Some(cfg), None) => issue_client_ticketbook(cfg, args.ticketbook_type, client).await,
(None, Some(storage)) => {
issue_standalone_ticketbook(storage, args.ticketbook_type, client).await
}
_ => unreachable!("clap should have made this branch impossible to reach!"),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use clap::{Args, Subcommand};

pub mod generate_ticket;
pub mod import_ticket_book;
pub mod issue_ticket_book;
pub mod recover_ticket_book;
Expand All @@ -19,4 +20,5 @@ pub enum EcashCommands {
IssueTicketBook(issue_ticket_book::Args),
RecoverTicketBook(recover_ticket_book::Args),
ImportTicketBook(import_ticket_book::Args),
GenerateTicket(generate_ticket::Args),
}
2 changes: 1 addition & 1 deletion common/commands/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <[email protected]>
// SPDX-License-Identifier: Apache-2.0

pub mod coconut;
pub mod context;
pub mod ecash;
pub mod utils;
pub mod validator;
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ pub struct Args {
}

pub fn decode_mixnode_key(args: Args) {
let b64_decoded = base64::decode(args.key).expect("failed to decode base64 string");
use base64::{engine::general_purpose::STANDARD, Engine as _};

let b64_decoded = STANDARD
.decode(args.key)
.expect("failed to decode base64 string");
let b58_encoded = bs58::encode(&b64_decoded).into_string();

println!("{b58_encoded}")
Expand Down
Loading