From b764fcc756f874c4e2ed4c249f6eaca742b2e35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 6 Feb 2024 17:34:34 +0000 Subject: [PATCH 01/49] revamped BandwidthVoucher to allow for different kinds of bandwidth credentials --- Cargo.lock | 2 + .../bandwidth-controller/src/acquire/mod.rs | 41 +- .../bandwidth-controller/src/acquire/state.rs | 15 +- common/bandwidth-controller/src/lib.rs | 57 +- common/bandwidth-controller/src/utils.rs | 41 ++ common/coconut-interface/src/lib.rs | 17 +- .../20240206120000_add_credential_types.sql | 5 + .../src/ephemeral_storage.rs | 8 +- common/credential-storage/src/models.rs | 21 +- .../src/persistent_storage.rs | 8 +- common/credential-storage/src/storage.rs | 9 +- .../credential-utils/src/recovery_storage.rs | 23 +- common/credential-utils/src/utils.rs | 41 +- common/credentials/Cargo.toml | 2 + common/credentials/src/coconut/bandwidth.rs | 428 -------------- .../src/coconut/bandwidth/freepass.rs | 77 +++ .../src/coconut/bandwidth/issuance.rs | 246 ++++++++ .../src/coconut/bandwidth/issued.rs | 140 +++++ .../credentials/src/coconut/bandwidth/mod.rs | 62 ++ .../src/coconut/bandwidth/voucher.rs | 541 ++++++++++++++++++ common/credentials/src/coconut/credential.rs | 10 + common/credentials/src/coconut/mod.rs | 3 +- common/credentials/src/coconut/utils.rs | 86 +-- common/network-defaults/src/lib.rs | 3 +- common/nymcoconut/src/impls/clone.rs | 4 +- common/nymcoconut/src/impls/serde.rs | 5 +- common/nymcoconut/src/lib.rs | 2 +- common/nymcoconut/src/scheme/verification.rs | 31 +- common/nymcoconut/src/tests/helpers.rs | 2 +- nym-api/src/coconut/api_routes/mod.rs | 6 +- nym-api/src/coconut/deposit.rs | 13 +- nym-api/src/coconut/state.rs | 14 +- nym-connect/desktop/Cargo.lock | 2 + .../rewarder/credential_issuance/monitor.rs | 13 +- sdk/rust/nym-sdk/src/bandwidth/client.rs | 4 +- 35 files changed, 1323 insertions(+), 659 deletions(-) create mode 100644 common/bandwidth-controller/src/utils.rs create mode 100644 common/credential-storage/migrations/20240206120000_add_credential_types.sql delete mode 100644 common/credentials/src/coconut/bandwidth.rs create mode 100644 common/credentials/src/coconut/bandwidth/freepass.rs create mode 100644 common/credentials/src/coconut/bandwidth/issuance.rs create mode 100644 common/credentials/src/coconut/bandwidth/issued.rs create mode 100644 common/credentials/src/coconut/bandwidth/mod.rs create mode 100644 common/credentials/src/coconut/bandwidth/voucher.rs create mode 100644 common/credentials/src/coconut/credential.rs diff --git a/Cargo.lock b/Cargo.lock index ac8415dbcf..7d92bcd044 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5383,7 +5383,9 @@ dependencies = [ "nym-crypto", "nym-validator-client", "rand 0.7.3", + "serde", "thiserror", + "time", "zeroize", ] diff --git a/common/bandwidth-controller/src/acquire/mod.rs b/common/bandwidth-controller/src/acquire/mod.rs index 116634cf41..9bf1f371a3 100644 --- a/common/bandwidth-controller/src/acquire/mod.rs +++ b/common/bandwidth-controller/src/acquire/mod.rs @@ -4,10 +4,9 @@ use crate::error::BandwidthControllerError; use nym_coconut_interface::Base58; use nym_credential_storage::storage::Storage; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::{IssuanceBandwidthCredential, VOUCHER_INFO_TYPE}; use nym_credentials::coconut::utils::obtain_aggregate_signature; use nym_crypto::asymmetric::{encryption, identity}; -use nym_network_defaults::VOUCHER_INFO; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; @@ -24,13 +23,11 @@ where let mut rng = OsRng; let signing_key = identity::PrivateKey::new(&mut rng); let encryption_key = encryption::PrivateKey::new(&mut rng); - let params = BandwidthVoucher::default_parameters(); - let voucher_value = amount.amount.to_string(); let tx_hash = client .deposit( - amount, - String::from(VOUCHER_INFO), + amount.clone(), + VOUCHER_INFO_TYPE.to_string(), signing_key.public_key().to_base58_string(), encryption_key.public_key().to_base58_string(), None, @@ -38,16 +35,10 @@ where .await? .transaction_hash; - let voucher = BandwidthVoucher::new( - ¶ms, - voucher_value, - VOUCHER_INFO.to_string(), - tx_hash, - signing_key, - encryption_key, - ); + let voucher = + IssuanceBandwidthCredential::new_voucher(amount, tx_hash, signing_key, encryption_key); - let state = State { voucher, params }; + let state = State { voucher }; Ok(state) } @@ -62,6 +53,9 @@ where St: Storage, ::StorageError: Send + Sync + 'static, { + // temporary + assert!(!state.voucher.typ().is_free_pass()); + let epoch_id = client.get_current_epoch().await?.epoch_id; let threshold = client .get_current_epoch_threshold() @@ -70,17 +64,16 @@ where let coconut_api_clients = all_coconut_api_clients(client, epoch_id).await?; - let signature = obtain_aggregate_signature( - &state.params, - &state.voucher, - &coconut_api_clients, - threshold, - ) - .await?; + let signature = + obtain_aggregate_signature(&state.voucher, &coconut_api_clients, threshold).await?; + + // we asserted the that the bandwidth credential we obtained is **NOT** the free pass + // so the first public attribute must be the value + let voucher_value = state.voucher.get_plain_public_attributes()[0].clone(); storage .insert_coconut_credential( - state.voucher.get_voucher_value(), - VOUCHER_INFO.to_string(), + voucher_value, + VOUCHER_INFO_TYPE.to_string(), state.voucher.get_private_attributes()[0].to_bs58(), state.voucher.get_private_attributes()[1].to_bs58(), signature.to_bs58(), diff --git a/common/bandwidth-controller/src/acquire/state.rs b/common/bandwidth-controller/src/acquire/state.rs index 7c6cc31c05..68945289b7 100644 --- a/common/bandwidth-controller/src/acquire/state.rs +++ b/common/bandwidth-controller/src/acquire/state.rs @@ -1,19 +1,14 @@ -// Copyright 2022-2023 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_coconut_interface::Parameters; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential; pub struct State { - pub voucher: BandwidthVoucher, - pub params: Parameters, + pub voucher: IssuanceBandwidthCredential, } impl State { - pub fn new(voucher: BandwidthVoucher) -> Self { - State { - voucher, - params: BandwidthVoucher::default_parameters(), - } + pub fn new(voucher: IssuanceBandwidthCredential) -> Self { + State { voucher } } } diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index b7d61e2935..f114dcab83 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BandwidthControllerError; +use crate::utils::stored_credential_to_issued_bandwidth; use nym_credential_storage::error::StorageError; use nym_credential_storage::storage::Storage; use nym_validator_client::coconut::all_coconut_api_clients; @@ -10,13 +11,12 @@ use std::str::FromStr; use zeroize::Zeroizing; use { nym_coconut_interface::Base58, - nym_credentials::coconut::{ - bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key, - }, + nym_credentials::coconut::utils::obtain_aggregate_verification_key, }; pub mod acquire; pub mod error; +mod utils; pub struct BandwidthController { storage: St, @@ -39,42 +39,37 @@ impl BandwidthController { C: DkgQueryClient + Sync + Send, ::StorageError: Send + Sync + 'static, { - let bandwidth_credential = self + let retrieved_credential = self .storage - .get_next_coconut_credential() + .get_next_unspent_credential() .await .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?; - let voucher_value = u64::from_str(&bandwidth_credential.voucher_value) - .map_err(|_| StorageError::InconsistentData)?; - let voucher_info = bandwidth_credential.voucher_info.clone(); - let serial_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58( - bandwidth_credential.serial_number, - )?); - let binding_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58( - bandwidth_credential.binding_number, - )?); - let signature = - nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?; - let epoch_id = u64::from_str(&bandwidth_credential.epoch_id) + + let epoch_id = u64::from_str(&retrieved_credential.epoch_id) .map_err(|_| StorageError::InconsistentData)?; + let issued_bandwidth = stored_credential_to_issued_bandwidth(retrieved_credential)?; + let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?; - let verification_key = obtain_aggregate_verification_key(&coconut_api_clients).await?; - // the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff) - Ok(( - prepare_for_spending( - voucher_value, - voucher_info, - &serial_number, - &binding_number, - epoch_id, - &signature, - &verification_key, - )?, - bandwidth_credential.id, - )) + let spend_request = issued_bandwidth.prepare_for_spending(&verification_key)?; + + todo!() + + // // the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff) + // Ok(( + // prepare_for_spending( + // voucher_value, + // voucher_info, + // &serial_number, + // &binding_number, + // epoch_id, + // &signature, + // &verification_key, + // )?, + // bandwidth_credential.id, + // )) } pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError> diff --git a/common/bandwidth-controller/src/utils.rs b/common/bandwidth-controller/src/utils.rs new file mode 100644 index 0000000000..7f24f8e141 --- /dev/null +++ b/common/bandwidth-controller/src/utils.rs @@ -0,0 +1,41 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::BandwidthControllerError; +use nym_credential_storage::models::StoredIssuedCredential; +use nym_credentials::coconut::bandwidth::IssuedBandwidthCredential; +use nym_validator_client::nym_api::EpochId; + +pub fn stored_credential_to_issued_bandwidth( + cred: StoredIssuedCredential, +) -> Result { + /* + let bandwidth_credential = self + .storage + .get_next_coconut_credential() + .await + .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?; + let voucher_value = u64::from_str(&bandwidth_credential.voucher_value) + .map_err(|_| StorageError::InconsistentData)?; + let voucher_info = bandwidth_credential.voucher_info.clone(); + let serial_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58( + bandwidth_credential.serial_number, + )?); + let binding_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58( + bandwidth_credential.binding_number, + )?); + let signature = + nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?; + let epoch_id = u64::from_str(&bandwidth_credential.epoch_id) + .map_err(|_| StorageError::InconsistentData)?; + + */ + todo!() +} + +pub fn issued_bandwidth_to_stored_credential( + issued: IssuedBandwidthCredential, + epoch_id: EpochId, +) -> StoredIssuedCredential { + todo!() +} diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 30037e29e0..2a655a2980 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -1,12 +1,11 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod error; - +use error::CoconutInterfaceError; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; -use error::CoconutInterfaceError; +pub mod error; // We list these explicity instead of glob export due to shadowing warnings with the pub tests // module. @@ -14,7 +13,8 @@ pub use nym_coconut::{ aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute, - PublicAttribute, SecretKey, Signature, SignatureShare, Theta, VerificationKey, + PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey, + VerifyCredentialRequest, }; #[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)] @@ -23,7 +23,7 @@ pub struct Credential { n_params: u32, #[getset(get = "pub")] - theta: Theta, + theta: VerifyCredentialRequest, voucher_value: u64, @@ -32,10 +32,11 @@ pub struct Credential { #[getset(get = "pub")] epoch_id: u64, } + impl Credential { pub fn new( n_params: u32, - theta: Theta, + theta: VerifyCredentialRequest, voucher_value: u64, voucher_info: String, epoch_id: u64, @@ -114,7 +115,7 @@ impl Credential { "To few bytes in credential", ))); } - let theta = Theta::from_bytes(&bytes[12..12 + theta_len as usize]) + let theta = VerifyCredentialRequest::from_bytes(&bytes[12..12 + theta_len as usize]) .map_err(|e| CoconutError::Deserialization(e.to_string()))?; eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]); let voucher_value = u64::from_be_bytes(eight_byte); diff --git a/common/credential-storage/migrations/20240206120000_add_credential_types.sql b/common/credential-storage/migrations/20240206120000_add_credential_types.sql new file mode 100644 index 0000000000..a72048344f --- /dev/null +++ b/common/credential-storage/migrations/20240206120000_add_credential_types.sql @@ -0,0 +1,5 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + diff --git a/common/credential-storage/src/ephemeral_storage.rs b/common/credential-storage/src/ephemeral_storage.rs index 577a2de8c5..9a4dd561ba 100644 --- a/common/credential-storage/src/ephemeral_storage.rs +++ b/common/credential-storage/src/ephemeral_storage.rs @@ -3,7 +3,7 @@ use crate::backends::memory::CoconutCredentialManager; use crate::error::StorageError; -use crate::models::CoconutCredential; +use crate::models::{CoconutCredential, StoredIssuedCredential}; use crate::storage::Storage; use async_trait::async_trait; @@ -60,6 +60,12 @@ impl Storage for EphemeralStorage { Ok(credential) } + async fn get_next_unspent_credential( + &self, + ) -> Result { + todo!() + } + async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> { self.coconut_credential_manager .consume_coconut_credential(id) diff --git a/common/credential-storage/src/models.rs b/common/credential-storage/src/models.rs index 014054f3fa..7a1686b3d5 100644 --- a/common/credential-storage/src/models.rs +++ b/common/credential-storage/src/models.rs @@ -1,6 +1,8 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use sqlx::FromRow; + #[derive(Clone)] pub struct CoconutCredential { #[allow(dead_code)] @@ -13,3 +15,20 @@ pub struct CoconutCredential { pub epoch_id: String, pub consumed: bool, } + +#[derive(FromRow)] +pub struct StoredIssuedCredential { + #[allow(dead_code)] + pub id: i64, + + pub serial_number: String, + pub binding_number: String, + + pub signature: String, + + pub variant_type: String, + pub serialized_variant_data: String, + + pub epoch_id: String, + pub consumed: bool, +} diff --git a/common/credential-storage/src/persistent_storage.rs b/common/credential-storage/src/persistent_storage.rs index f214e40345..4fffc60777 100644 --- a/common/credential-storage/src/persistent_storage.rs +++ b/common/credential-storage/src/persistent_storage.rs @@ -5,7 +5,7 @@ use crate::backends::sqlite::CoconutCredentialManager; use crate::error::StorageError; use crate::storage::Storage; -use crate::models::CoconutCredential; +use crate::models::{CoconutCredential, StoredIssuedCredential}; use async_trait::async_trait; use log::{debug, error}; use sqlx::ConnectOptions; @@ -91,6 +91,12 @@ impl Storage for PersistentStorage { Ok(credential) } + async fn get_next_unspent_credential( + &self, + ) -> Result { + todo!() + } + async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> { self.coconut_credential_manager .consume_coconut_credential(id) diff --git a/common/credential-storage/src/storage.rs b/common/credential-storage/src/storage.rs index e4fa9cbcba..af16a4a8de 100644 --- a/common/credential-storage/src/storage.rs +++ b/common/credential-storage/src/storage.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::models::CoconutCredential; +use crate::models::{CoconutCredential, StoredIssuedCredential}; use async_trait::async_trait; use std::error::Error; @@ -19,6 +19,7 @@ pub trait Storage: Send + Sync { /// * `binding_number`: Binding number of the credential. /// * `signature`: Coconut credential in the form of a signature. /// * `epoch_id`: The epoch when it was signed. + #[deprecated] async fn insert_coconut_credential( &self, voucher_value: String, @@ -30,8 +31,14 @@ pub trait Storage: Send + Sync { ) -> Result<(), Self::StorageError>; /// Tries to retrieve one of the stored, unused credentials. + #[deprecated] async fn get_next_coconut_credential(&self) -> Result; + /// Tries to retrieve one of the stored, unused credentials. + async fn get_next_unspent_credential( + &self, + ) -> Result; + /// Marks as consumed in the database the specified credential. /// /// # Arguments diff --git a/common/credential-utils/src/recovery_storage.rs b/common/credential-utils/src/recovery_storage.rs index 1793553c62..6dc6ec76bd 100644 --- a/common/credential-utils/src/recovery_storage.rs +++ b/common/credential-utils/src/recovery_storage.rs @@ -3,7 +3,7 @@ use crate::errors::Result; use log::error; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential; use std::fs::{create_dir_all, read_dir, File}; use std::io::{Read, Write}; use std::path::PathBuf; @@ -18,7 +18,7 @@ impl RecoveryStorage { Ok(Self { recovery_dir }) } - pub fn unconsumed_vouchers(&self) -> Result> { + pub fn unconsumed_vouchers(&self) -> Result> { let entries = read_dir(&self.recovery_dir)?; let mut paths = vec![]; @@ -34,7 +34,7 @@ impl RecoveryStorage { if let Ok(mut file) = File::open(&path) { let mut buff = Vec::new(); if file.read_to_end(&mut buff).is_ok() { - match BandwidthVoucher::try_from_bytes(&buff) { + match IssuanceBandwidthCredential::try_from_bytes(&buff) { Ok(voucher) => vouchers.push(voucher), Err(err) => { error!("failed to parse the voucher at {}: {err}", path.display()) @@ -47,14 +47,15 @@ impl RecoveryStorage { Ok(vouchers) } - pub fn insert_voucher(&self, voucher: &BandwidthVoucher) -> Result { - let file_name = voucher.tx_hash().to_string(); - let file_path = self.recovery_dir.join(file_name); - let mut file = File::create(&file_path)?; - let buff = voucher.to_bytes(); - file.write_all(&buff)?; - - Ok(file_path) + pub fn insert_voucher(&self, voucher: &IssuanceBandwidthCredential) -> Result { + todo!() + // let file_name = voucher.tx_hash().to_string(); + // let file_path = self.recovery_dir.join(file_name); + // let mut file = File::create(&file_path)?; + // let buff = voucher.to_bytes(); + // file.write_all(&buff)?; + // + // Ok(file_path) } pub fn remove_voucher(&self, file_name: String) -> Result<()> { diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs index e1e7c09850..7668b5f1b7 100644 --- a/common/credential-utils/src/utils.rs +++ b/common/credential-utils/src/utils.rs @@ -126,24 +126,25 @@ pub async fn recover_credentials( where C: DkgQueryClient + Send + Sync, { - let mut recovered_amount: u128 = 0; - for voucher in recovery_storage.unconsumed_vouchers()? { - let voucher_value = voucher.get_voucher_value(); - recovered_amount += voucher_value.parse::()?; - - let state = State::new(voucher); - let voucher = state.voucher.tx_hash(); - if let Err(e) = - nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await - { - error!("Could not recover deposit {voucher} due to {e}, try again later",) - } else { - info!("Converted deposit {voucher} to a credential, removing recovery data for it",); - if let Err(e) = recovery_storage.remove_voucher(voucher.to_string()) { - warn!("Could not remove recovery data: {e}"); - } - } - } - - Ok(recovered_amount) + todo!() + // let mut recovered_amount: u128 = 0; + // for voucher in recovery_storage.unconsumed_vouchers()? { + // let voucher_value = voucher.get_voucher_value(); + // recovered_amount += voucher_value.parse::()?; + // + // let state = State::new(voucher); + // let voucher = state.voucher.tx_hash(); + // if let Err(e) = + // nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await + // { + // error!("Could not recover deposit {voucher} due to {e}, try again later",) + // } else { + // info!("Converted deposit {voucher} to a credential, removing recovery data for it",); + // if let Err(e) = recovery_storage.remove_voucher(voucher.to_string()) { + // warn!("Could not remove recovery data: {e}"); + // } + // } + // } + // + // Ok(recovered_amount) } diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 8e96ce7b30..9c42f08418 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -11,6 +11,7 @@ bls12_381 = { workspace = true, default-features = false, features = ["pairings" cosmrs = { workspace = true } thiserror = { workspace = true } log = { workspace = true } +time = { workspace = true, features = ["serde"] } zeroize = { workspace = true } # I guess temporarily until we get serde support in coconut up and running @@ -18,6 +19,7 @@ nym-coconut-interface = { path = "../coconut-interface" } nym-crypto = { path = "../crypto", features = ["rand", "asymmetric"] } nym-api-requests = { path = "../../nym-api/nym-api-requests" } nym-validator-client = { path = "../client-libs/validator-client", default-features = false } +serde = { version = "1.0.189", features = ["derive"] } [dev-dependencies] rand = "0.7.3" diff --git a/common/credentials/src/coconut/bandwidth.rs b/common/credentials/src/coconut/bandwidth.rs deleted file mode 100644 index 9646860a74..0000000000 --- a/common/credentials/src/coconut/bandwidth.rs +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -// for time being assume the bandwidth credential consists of public identity of the requester -// and private (though known... just go along with it) infinite bandwidth value -// right now this has no double-spending protection, spender binding, etc -// it's the simplest possible case - -use cosmrs::tendermint::hash::Algorithm; -use cosmrs::tendermint::Hash; -use nym_coconut_interface::{ - hash_to_scalar, prepare_blind_sign, Attribute, BlindSignRequest, Credential, Parameters, - PrivateAttribute, PublicAttribute, Signature, VerificationKey, -}; -use nym_crypto::asymmetric::{encryption, identity}; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -use super::utils::prepare_credential_for_spending; -use crate::error::Error; - -#[derive(Zeroize, ZeroizeOnDrop)] -pub struct BandwidthVoucher { - // private attributes - /// a random secret value generated by the client used for double-spending detection - serial_number: PrivateAttribute, - - /// a random secret value generated by the client used to bind multiple credentials together - binding_number: PrivateAttribute, - - // public atttributes: - /// the plain text value (e.g., bandwidth) encoded in this voucher - // TODO: in another PR change the value from `"1000"` to `"1000unym"` - voucher_value_plain: String, - - /// the plain text information - voucher_info_plain: String, - - /// the precomputed value (e.g., bandwidth) encoded in this voucher - _voucher_value_prehashed: PublicAttribute, - - /// the precomputed field with public information, e.g., type of voucher, interval etc. - _voucher_info_prehashed: PublicAttribute, - - /// the hash of the deposit transaction - #[zeroize(skip)] - tx_hash: Hash, - - /// base58 encoded private key ensuring the depositer requested these attributes - signing_key: identity::PrivateKey, - - /// base58 encoded private key ensuring only this client receives the signature share - unused_ed25519: encryption::PrivateKey, - - pedersen_commitments_openings: Vec, - - #[zeroize(skip)] - blind_sign_request: BlindSignRequest, -} - -impl BandwidthVoucher { - pub const PUBLIC_ATTRIBUTES: u32 = 2; - pub const PRIVATE_ATTRIBUTES: u32 = 2; - pub const ENCODED_ATTRIBUTES: u32 = 4; - - pub fn default_parameters() -> Parameters { - // safety: the unwrap is fine here as Self::ENCODED_ATTRIBUTES is non-zero - Parameters::new(Self::ENCODED_ATTRIBUTES).unwrap() - } - - pub fn new( - params: &Parameters, - voucher_value: String, - voucher_info: String, - tx_hash: Hash, - signing_key: identity::PrivateKey, - encryption_key: encryption::PrivateKey, - ) -> Self { - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); - let voucher_value_plain = voucher_value.clone(); - let voucher_info_plain = voucher_info.clone(); - - let _voucher_value_prehashed = hash_to_scalar(voucher_value); - let _voucher_info_prehashed = hash_to_scalar(voucher_info); - - let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign( - params, - &[&serial_number, &binding_number], - &[&_voucher_value_prehashed, &_voucher_info_prehashed], - ) - .unwrap(); - BandwidthVoucher { - serial_number, - binding_number, - _voucher_value_prehashed, - voucher_value_plain, - _voucher_info_prehashed, - voucher_info_plain, - tx_hash, - signing_key, - unused_ed25519: encryption_key, - pedersen_commitments_openings, - blind_sign_request, - } - } - - pub fn to_bytes(&self) -> Vec { - let serial_number_b = self.serial_number.to_bytes(); - let binding_number_b = self.binding_number.to_bytes(); - let voucher_value_plain_b = self.voucher_value_plain.as_bytes(); - let voucher_info_plain_b = self.voucher_info_plain.as_bytes(); - let tx_hash_b = self.tx_hash.as_bytes(); - let signing_key_b = self.signing_key.to_bytes(); - let encryption_key_b = self.unused_ed25519.to_bytes(); - let blind_sign_request_b = self.blind_sign_request.to_bytes(); - - let mut ret = Vec::new(); - - ret.extend_from_slice(&serial_number_b); - ret.extend_from_slice(&binding_number_b); - ret.extend_from_slice(tx_hash_b); - ret.extend_from_slice(&signing_key_b); - ret.extend_from_slice(&encryption_key_b); - ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes()); - ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes()); - ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes()); - ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes()); - ret.extend_from_slice(voucher_value_plain_b); - ret.extend_from_slice(voucher_info_plain_b); - ret.extend_from_slice(&blind_sign_request_b); - for commitment in self.pedersen_commitments_openings.iter() { - ret.extend_from_slice(&commitment.to_bytes()); - } - - ret - } - - pub fn try_from_bytes(bytes: &[u8]) -> Result { - if bytes.len() < 32 * 5 + 4 * 8 { - return Err(Error::BandwidthVoucherDeserializationError(format!( - "Less then {} bytes needed", - 32 * 5 + 4 * 8 - ))); - } - let mut buff = [0u8; 32]; - let mut small_buff = [0u8; 8]; - let scalar_err = - || Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar")); - buff.copy_from_slice(&bytes[..32]); - let serial_number = Option::::from(PrivateAttribute::from_bytes(&buff)) - .ok_or_else(scalar_err)?; - buff.copy_from_slice(&bytes[32..2 * 32]); - let binding_number = Option::::from(PrivateAttribute::from_bytes(&buff)) - .ok_or_else(scalar_err)?; - buff.copy_from_slice(&bytes[2 * 32..3 * 32]); - let tx_hash = Hash::from_bytes(Algorithm::Sha256, &buff).map_err(|_| { - Error::BandwidthVoucherDeserializationError(String::from("Invalid transaction Hash")) - })?; - buff.copy_from_slice(&bytes[3 * 32..4 * 32]); - let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| { - Error::BandwidthVoucherDeserializationError(String::from("Invalid key")) - })?; - buff.copy_from_slice(&bytes[4 * 32..5 * 32]); - let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| { - Error::BandwidthVoucherDeserializationError(String::from("Invalid key")) - })?; - small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]); - let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize; - small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]); - let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize; - small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]); - let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize; - small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]); - let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize; - - let total_length = 32 * 5 - + 4 * 8 - + voucher_value_plain_no - + voucher_info_plain_no - + blind_sign_request_no - + pedersen_commitments_openings_no * 32; - if bytes.len() != total_length { - return Err(Error::BandwidthVoucherDeserializationError(format!( - "Expected {total_length} bytes", - ))); - } - - let utf_err = |_| { - Err(Error::BandwidthVoucherDeserializationError(String::from( - "Invalid UTF8 string", - ))) - }; - let mut var_length_pointer = 5 * 32 + 4 * 8; - let voucher_value_plain = String::from_utf8( - bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(), - ) - .or_else(utf_err)?; - let _voucher_value_prehashed = hash_to_scalar(&voucher_value_plain); - var_length_pointer += voucher_value_plain_no; - let voucher_info_plain = String::from_utf8( - bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(), - ) - .or_else(utf_err)?; - let _voucher_info_prehashed = hash_to_scalar(&voucher_info_plain); - var_length_pointer += voucher_info_plain_no; - let blind_sign_request = BlindSignRequest::from_bytes( - &bytes[var_length_pointer..var_length_pointer + blind_sign_request_no], - )?; - var_length_pointer += blind_sign_request_no; - - let mut pedersen_commitments_openings = Vec::new(); - for _ in 0..pedersen_commitments_openings_no { - buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]); - let commitment = - Option::::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?; - var_length_pointer += 32; - pedersen_commitments_openings.push(commitment); - } - - Ok(Self { - serial_number, - binding_number, - _voucher_value_prehashed, - voucher_value_plain, - _voucher_info_prehashed, - voucher_info_plain, - tx_hash, - signing_key, - unused_ed25519: encryption_key, - pedersen_commitments_openings, - blind_sign_request, - }) - } - - /// Check if the plain values correspond to the PublicAttributes - pub fn verify_against_plain(values: &[&PublicAttribute], plain_values: &[String]) -> bool { - values.len() == 2 - && plain_values.len() == 2 - && values[0] == &hash_to_scalar(&plain_values[0]) - && values[1] == &hash_to_scalar(&plain_values[1]) - } - - pub fn tx_hash(&self) -> Hash { - self.tx_hash - } - - pub fn get_public_attributes(&self) -> Vec<&PublicAttribute> { - vec![ - &self._voucher_value_prehashed, - &self._voucher_info_prehashed, - ] - } - - pub fn identity_key(&self) -> &identity::PrivateKey { - &self.signing_key - } - - pub fn encryption_key(&self) -> &encryption::PrivateKey { - &self.unused_ed25519 - } - - pub fn pedersen_commitments_openings(&self) -> &Vec { - &self.pedersen_commitments_openings - } - - pub fn blind_sign_request(&self) -> &BlindSignRequest { - &self.blind_sign_request - } - - pub fn get_voucher_value(&self) -> String { - self.voucher_value_plain.clone() - } - - pub fn get_public_attributes_plain(&self) -> Vec { - vec![ - self.voucher_value_plain.clone(), - self.voucher_info_plain.clone(), - ] - } - - pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> { - vec![&self.serial_number, &self.binding_number] - } - - pub fn signable_plaintext(request: &BlindSignRequest, tx_hash: Hash) -> Vec { - let mut message = request.to_bytes(); - message.extend_from_slice(tx_hash.as_bytes()); - message - } - - pub fn sign(&self) -> identity::Signature { - let message = Self::signable_plaintext(&self.blind_sign_request, self.tx_hash); - self.signing_key.sign(message) - } -} - -pub fn prepare_for_spending( - voucher_value: u64, - voucher_info: String, - serial_number: &PrivateAttribute, - binding_number: &PrivateAttribute, - epoch_id: u64, - signature: &Signature, - verification_key: &VerificationKey, -) -> Result { - let params = Parameters::new(BandwidthVoucher::ENCODED_ATTRIBUTES)?; - - prepare_credential_for_spending( - ¶ms, - voucher_value, - voucher_info, - serial_number, - binding_number, - epoch_id, - signature, - verification_key, - ) -} - -#[cfg(test)] -mod test { - use super::*; - use cosmrs::tendermint::hash::Algorithm; - use nym_coconut_interface::Base58; - use rand::rngs::OsRng; - - fn voucher_fixture() -> BandwidthVoucher { - let params = Parameters::new(4).unwrap(); - let mut rng = OsRng; - BandwidthVoucher::new( - ¶ms, - "1234".to_string(), - "voucher info".to_string(), - Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap(), - identity::PrivateKey::from_base58_string( - identity::KeyPair::new(&mut rng) - .private_key() - .to_base58_string(), - ) - .unwrap(), - encryption::PrivateKey::from_bytes( - &encryption::KeyPair::new(&mut rng).private_key().to_bytes(), - ) - .unwrap(), - ) - } - - #[test] - fn serde_voucher() { - let voucher = voucher_fixture(); - let bytes = voucher.to_bytes(); - let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap(); - assert_eq!(voucher.serial_number, deserialized_voucher.serial_number); - assert_eq!(voucher.binding_number, deserialized_voucher.binding_number); - assert_eq!( - voucher.voucher_value_plain, - deserialized_voucher.voucher_value_plain - ); - assert_eq!( - voucher.voucher_info_plain, - deserialized_voucher.voucher_info_plain - ); - assert_eq!( - voucher._voucher_value_prehashed, - deserialized_voucher._voucher_value_prehashed - ); - assert_eq!( - voucher._voucher_info_prehashed, - deserialized_voucher._voucher_info_prehashed - ); - assert_eq!(voucher.tx_hash, deserialized_voucher.tx_hash); - assert_eq!( - voucher.signing_key.to_string(), - deserialized_voucher.signing_key.to_string() - ); - assert_eq!( - voucher.unused_ed25519.to_string(), - deserialized_voucher.unused_ed25519.to_string() - ); - assert_eq!( - voucher.pedersen_commitments_openings, - deserialized_voucher.pedersen_commitments_openings - ); - assert_eq!( - voucher.blind_sign_request.to_bs58(), - deserialized_voucher.blind_sign_request.to_bs58() - ); - } - - #[test] - fn voucher_consistency() { - let voucher = voucher_fixture(); - assert!(!BandwidthVoucher::verify_against_plain( - &[], - &voucher.get_public_attributes_plain() - )); - assert!(!BandwidthVoucher::verify_against_plain( - &voucher.get_public_attributes(), - &[], - )); - assert!(!BandwidthVoucher::verify_against_plain( - &voucher.get_public_attributes(), - &[ - voucher.get_public_attributes_plain()[0].clone(), - String::new() - ] - )); - assert!(!BandwidthVoucher::verify_against_plain( - &voucher.get_public_attributes(), - &[ - String::new(), - voucher.get_public_attributes_plain()[1].clone() - ] - )); - assert!(!BandwidthVoucher::verify_against_plain( - &[voucher.get_public_attributes()[0], &Attribute::one()], - &voucher.get_public_attributes_plain() - )); - assert!(!BandwidthVoucher::verify_against_plain( - &[&Attribute::one(), voucher.get_public_attributes()[1]], - &voucher.get_public_attributes_plain() - )); - assert!(BandwidthVoucher::verify_against_plain( - &voucher.get_public_attributes(), - &voucher.get_public_attributes_plain() - )); - } -} diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs new file mode 100644 index 0000000000..d0e17ba55b --- /dev/null +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -0,0 +1,77 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_coconut_interface::{hash_to_scalar, Attribute, PublicAttribute}; +use serde::{Deserialize, Serialize}; +use time::{Duration, OffsetDateTime, Time}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +pub const MAX_FREE_PASS_VALIDITY: Duration = Duration::WEEK; // 1 week + +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +pub struct FreePassIssuedData { + /// the plain validity value of this credential expressed as unix timestamp + #[zeroize(skip)] + expiry_date: OffsetDateTime, +} + +impl<'a> From<&'a FreePassIssuanceData> for FreePassIssuedData { + fn from(value: &'a FreePassIssuanceData) -> Self { + FreePassIssuedData { + expiry_date: value.expiry_date, + } + } +} + +impl FreePassIssuedData { + pub fn expiry_date_plain(&self) -> String { + self.expiry_date.unix_timestamp().to_string() + } +} + +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct FreePassIssuanceData { + /// the plain validity value of this credential expressed as unix timestamp + #[zeroize(skip)] + expiry_date: OffsetDateTime, + + // the expiry date, as unix timestamp, hashed into a scalar + expiry_date_prehashed: PublicAttribute, +} + +impl FreePassIssuanceData { + pub fn new(expiry_date: Option) -> Self { + // ideally we should have implemented a proper error handling here, sure. + // but given it's meant to only be used by nym, imo it's fine to just panic here in case of invalid arguments + let expiry_date = if let Some(provided) = expiry_date { + if provided - OffsetDateTime::now_utc() > MAX_FREE_PASS_VALIDITY { + panic!("the provided expiry date is bigger than the maximum value of {MAX_FREE_PASS_VALIDITY}"); + } + + provided + } else { + Self::default_expiry_date() + }; + + let expiry_date_prehashed = hash_to_scalar(expiry_date.unix_timestamp().to_string()); + + FreePassIssuanceData { + expiry_date, + expiry_date_prehashed, + } + } + + pub fn default_expiry_date() -> OffsetDateTime { + // set it to furthest midnight in the future such as it's no more than a week away, + // i.e. if it's currently for example 9:43 on 2nd March 2024, it will set it to 0:00 on 9th March 2024 + (OffsetDateTime::now_utc() + MAX_FREE_PASS_VALIDITY).replace_time(Time::MIDNIGHT) + } + + pub fn expiry_date_attribute(&self) -> &Attribute { + &self.expiry_date_prehashed + } + + pub fn expiry_date_plain(&self) -> String { + self.expiry_date.unix_timestamp().to_string() + } +} diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs new file mode 100644 index 0000000000..ba694d37f8 --- /dev/null +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -0,0 +1,246 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::bandwidth::freepass::FreePassIssuanceData; +use crate::coconut::bandwidth::issued::IssuedBandwidthCredential; +use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData; +use crate::coconut::bandwidth::{ + bandwidth_voucher_params, CredentialSigningData, CredentialSpendingData, CredentialType, +}; +use crate::error::Error; +use nym_coconut_interface::{ + aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, prove_bandwidth_credential, + Attribute, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, + VerificationKey, +}; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_validator_client::nyxd::{Coin, Hash}; +use time::OffsetDateTime; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[derive(Zeroize, ZeroizeOnDrop)] +pub enum BandwidthCredentialIssuanceDataVariant { + Voucher(BandwidthVoucherIssuanceData), + FreePass(FreePassIssuanceData), +} + +impl From for BandwidthCredentialIssuanceDataVariant { + fn from(value: FreePassIssuanceData) -> Self { + BandwidthCredentialIssuanceDataVariant::FreePass(value) + } +} + +impl From for BandwidthCredentialIssuanceDataVariant { + fn from(value: BandwidthVoucherIssuanceData) -> Self { + BandwidthCredentialIssuanceDataVariant::Voucher(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() + } + } + } +} + +// all types of bandwidth credentials contain serial number and binding number +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct IssuanceBandwidthCredential { + // private attributes + /// a random secret value generated by the client used for double-spending detection + serial_number: PrivateAttribute, + + /// a random secret value generated by the client used to bind multiple credentials together + 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 + type_prehashed: PublicAttribute, +} + +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; + + // just keep this value on hand for any possible future changes so that we could preserve backwards compatibility + pub const ENCODING_VERSION: u8 = 1; + + pub fn default_parameters() -> Parameters { + // safety: the unwrap is fine here as Self::ENCODED_ATTRIBUTES is non-zero + Parameters::new(Self::ENCODED_ATTRIBUTES).unwrap() + } + + pub fn new>(variant_data: B) -> Self { + let variant_data = variant_data.into(); + let type_prehashed = hash_to_scalar(variant_data.info().to_string()); + + let params = bandwidth_voucher_params(); + let serial_number = params.random_scalar(); + let binding_number = params.random_scalar(); + + IssuanceBandwidthCredential { + serial_number, + binding_number, + variant_data, + type_prehashed, + } + } + + pub fn new_voucher( + value: Coin, + deposit_tx_hash: Hash, + signing_key: identity::PrivateKey, + unused_ed25519: encryption::PrivateKey, + ) -> Self { + Self::new(BandwidthVoucherIssuanceData::new( + value, + deposit_tx_hash, + signing_key, + unused_ed25519, + )) + } + + pub fn new_freepass(expiry_date: Option) -> Self { + Self::new(FreePassIssuanceData::new(expiry_date)) + } + + pub fn typ(&self) -> CredentialType { + self.variant_data.info() + } + + pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> { + vec![&self.serial_number, &self.binding_number] + } + + pub fn get_public_attributes(&self) -> Vec<&PublicAttribute> { + vec![self.variant_data.public_value(), &self.type_prehashed] + } + + pub fn get_plain_public_attributes(&self) -> Vec { + vec![ + self.variant_data.public_value_plain(), + self.typ().to_string(), + ] + } + + pub fn prepare_for_signing(&self) -> CredentialSigningData { + let params = bandwidth_voucher_params(); + + // 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(), + ) + .unwrap(); + + CredentialSigningData { + pedersen_commitments_openings, + blind_sign_request, + public_attributes_plain: self.get_plain_public_attributes(), + } + } + + pub async fn obtain_partial_credential( + &self, + client: &nym_validator_client::client::NymApiClient, + validator_vk: &VerificationKey, + signing_data: impl Into>, + ) -> Result { + // if we provided signing data, do use them, otherwise generate fresh data + let signing_data = signing_data + .into() + .unwrap_or_else(|| self.prepare_for_signing()); + + let blinded_signature = match &self.variant_data { + BandwidthCredentialIssuanceDataVariant::FreePass(_freepass) => unimplemented!(), + BandwidthCredentialIssuanceDataVariant::Voucher(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? + } + }; + + let public_attributes = self.get_public_attributes(); + let private_attributes = self.get_private_attributes(); + + let params = bandwidth_voucher_params(); + let unblinded_signature = blinded_signature.unblind_and_verify( + params, + validator_vk, + &private_attributes, + &public_attributes, + &signing_data.blind_sign_request.get_commitment_hash(), + &signing_data.pedersen_commitments_openings, + )?; + + Ok(unblinded_signature) + } + + pub fn aggregate_signature_shares( + &self, + verification_key: &VerificationKey, + shares: &[SignatureShare], + ) -> Result { + let public_attributes = self.get_public_attributes(); + let private_attributes = self.get_private_attributes(); + + let params = bandwidth_voucher_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) + } + + pub fn into_issued_credential( + self, + aggregate_signature: Signature, + ) -> IssuedBandwidthCredential { + IssuedBandwidthCredential::new( + self.serial_number, + self.binding_number, + aggregate_signature, + (&self.variant_data).into(), + self.type_prehashed, + ) + } + + // TODO: is that actually needed? + pub fn to_bytes(&self) -> Vec { + todo!() + } + + // TODO: is that actually needed? + pub fn try_from_bytes(bytes: &[u8]) -> Result { + todo!() + } +} diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs new file mode 100644 index 0000000000..2c541d2701 --- /dev/null +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -0,0 +1,140 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::bandwidth::freepass::FreePassIssuedData; +use crate::coconut::bandwidth::issuance::{ + BandwidthCredentialIssuanceDataVariant, IssuanceBandwidthCredential, +}; +use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuedData; +use crate::coconut::bandwidth::{bandwidth_voucher_params, CredentialSpendingData, CredentialType}; +use crate::error::Error; +use nym_coconut_interface::{ + prove_bandwidth_credential, Attribute, Parameters, PrivateAttribute, PublicAttribute, + Signature, VerificationKey, +}; +use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +pub enum BandwidthCredentialIssuedDataVariant { + Voucher(BandwidthVoucherIssuedData), + FreePass(FreePassIssuedData), +} + +impl<'a> From<&'a BandwidthCredentialIssuanceDataVariant> for BandwidthCredentialIssuedDataVariant { + fn from(value: &'a BandwidthCredentialIssuanceDataVariant) -> Self { + match value { + BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => { + BandwidthCredentialIssuedDataVariant::Voucher(voucher.into()) + } + BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => { + BandwidthCredentialIssuedDataVariant::FreePass(freepass.into()) + } + } + } +} + +impl From for BandwidthCredentialIssuedDataVariant { + fn from(value: FreePassIssuedData) -> Self { + BandwidthCredentialIssuedDataVariant::FreePass(value) + } +} + +impl From for BandwidthCredentialIssuedDataVariant { + fn from(value: BandwidthVoucherIssuedData) -> Self { + BandwidthCredentialIssuedDataVariant::Voucher(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() + } + } + } +} + +// 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)] +pub struct IssuedBandwidthCredential { + // private attributes + /// a random secret value generated by the client used for double-spending detection + serial_number: PrivateAttribute, + + /// a random secret value generated by the client used to bind multiple credentials together + binding_number: PrivateAttribute, + + /// the underlying aggregated signature on the attributes + #[zeroize(skip)] + signature: Signature, + + /// 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 + type_prehashed: PublicAttribute, +} + +impl IssuedBandwidthCredential { + pub fn new( + serial_number: PrivateAttribute, + binding_number: PrivateAttribute, + signature: Signature, + variant_data: BandwidthCredentialIssuedDataVariant, + type_prehashed: PublicAttribute, + ) -> Self { + IssuedBandwidthCredential { + serial_number, + binding_number, + signature, + variant_data, + type_prehashed, + } + } + + pub fn default_parameters() -> Parameters { + IssuanceBandwidthCredential::default_parameters() + } + + pub fn typ(&self) -> CredentialType { + self.variant_data.info() + } + + pub fn get_plain_public_attributes(&self) -> Vec { + vec![ + self.variant_data.public_value_plain(), + self.typ().to_string(), + ] + } + + pub fn prepare_for_spending( + &self, + verification_key: &VerificationKey, + ) -> Result { + let params = bandwidth_voucher_params(); + + let verify_credential_request = prove_bandwidth_credential( + params, + verification_key, + &self.signature, + &self.serial_number, + &self.binding_number, + )?; + + Ok(CredentialSpendingData { + verify_credential_request, + public_attributes_plain: self.get_plain_public_attributes(), + }) + } +} diff --git a/common/credentials/src/coconut/bandwidth/mod.rs b/common/credentials/src/coconut/bandwidth/mod.rs new file mode 100644 index 0000000000..84101bc9cc --- /dev/null +++ b/common/credentials/src/coconut/bandwidth/mod.rs @@ -0,0 +1,62 @@ +// Copyright 2021-2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bls12_381::Scalar; +use nym_coconut_interface::{BlindSignRequest, Parameters, VerifyCredentialRequest}; +use std::fmt::{Display, Formatter}; +use std::sync::OnceLock; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +pub use issuance::IssuanceBandwidthCredential; +pub use issued::IssuedBandwidthCredential; + +pub mod freepass; +pub mod issuance; +pub mod issued; +pub mod voucher; + +pub const VOUCHER_INFO_TYPE: &str = "BandwidthVoucher"; +pub const FREE_PASS_INFO_TYPE: &str = "FreeBandwidthPass"; + +// works under the assumption of having 4 attributes in the underlying credential(s) +pub fn bandwidth_voucher_params() -> &'static Parameters { + static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock = OnceLock::new(); + BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(IssuanceBandwidthCredential::default_parameters) +} + +#[derive(Zeroize, ZeroizeOnDrop, Clone, Debug)] +pub enum CredentialType { + Voucher, + FreePass, +} + +impl CredentialType { + pub fn is_free_pass(&self) -> bool { + matches!(self, CredentialType::FreePass) + } +} + +impl Display for CredentialType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CredentialType::Voucher => VOUCHER_INFO_TYPE.fmt(f), + CredentialType::FreePass => FREE_PASS_INFO_TYPE.fmt(f), + } + } +} + +#[derive(Debug, Clone)] +pub struct CredentialSigningData { + pub(crate) pedersen_commitments_openings: Vec, + + pub(crate) blind_sign_request: BlindSignRequest, + + pub(crate) public_attributes_plain: Vec, +} + +#[derive(Debug)] +pub struct CredentialSpendingData { + pub(crate) verify_credential_request: VerifyCredentialRequest, + + pub(crate) public_attributes_plain: Vec, +} diff --git a/common/credentials/src/coconut/bandwidth/voucher.rs b/common/credentials/src/coconut/bandwidth/voucher.rs new file mode 100644 index 0000000000..cce7726b60 --- /dev/null +++ b/common/credentials/src/coconut/bandwidth/voucher.rs @@ -0,0 +1,541 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::bandwidth::CredentialSigningData; +use crate::error::Error; +use nym_api_requests::coconut::BlindSignRequestBody; +use nym_coconut_interface::{ + hash_to_scalar, Attribute, BlindSignRequest, BlindedSignature, PublicAttribute, +}; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_validator_client::nyxd::{Coin, Hash}; +use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +pub struct BandwidthVoucherIssuedData { + /// the plain value (e.g., bandwidth) encoded in this voucher + // note: for legacy reasons we're only using the value of the coin and ignoring the denom + #[zeroize(skip)] + value: Coin, +} + +impl<'a> From<&'a BandwidthVoucherIssuanceData> for BandwidthVoucherIssuedData { + fn from(value: &'a BandwidthVoucherIssuanceData) -> Self { + BandwidthVoucherIssuedData { + value: value.value.clone(), + } + } +} + +impl BandwidthVoucherIssuedData { + pub fn value_plain(&self) -> String { + self.value.amount.to_string() + } +} + +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct BandwidthVoucherIssuanceData { + /// the plain value (e.g., bandwidth) encoded in this voucher + // note: for legacy reasons we're only using the value of the coin and ignoring the denom + #[zeroize(skip)] + value: Coin, + + // note: as mentioned above, we're only hashing the value of the coin! + value_prehashed: PublicAttribute, + + /// the hash of the deposit transaction + #[zeroize(skip)] + deposit_tx_hash: Hash, + + /// base58 encoded private key ensuring the depositer requested these attributes + signing_key: identity::PrivateKey, + + /// base58 encoded private key ensuring only this client receives the signature share + unused_ed25519: encryption::PrivateKey, +} + +impl BandwidthVoucherIssuanceData { + pub fn new( + value: Coin, + deposit_tx_hash: Hash, + signing_key: identity::PrivateKey, + unused_ed25519: encryption::PrivateKey, + ) -> Self { + let value_prehashed = hash_to_scalar(value.amount.to_string()); + + BandwidthVoucherIssuanceData { + value, + value_prehashed, + deposit_tx_hash, + signing_key, + unused_ed25519, + } + } + + pub fn request_plaintext(request: &BlindSignRequest, tx_hash: Hash) -> Vec { + let mut message = request.to_bytes(); + message.extend_from_slice(tx_hash.as_bytes()); + message + } + + fn request_signature(&self, signing_request: &CredentialSigningData) -> identity::Signature { + let message = + Self::request_plaintext(&signing_request.blind_sign_request, self.deposit_tx_hash); + self.signing_key.sign(message) + } + + pub fn create_blind_sign_request_body( + &self, + signing_request: &CredentialSigningData, + ) -> BlindSignRequestBody { + let request_signature = self.request_signature(signing_request); + + BlindSignRequestBody::new( + signing_request.blind_sign_request.clone(), + self.deposit_tx_hash, + request_signature, + signing_request.public_attributes_plain.clone(), + ) + } + + pub async fn obtain_blinded_credential( + &self, + client: &nym_validator_client::client::NymApiClient, + request_body: &BlindSignRequestBody, + ) -> Result { + let server_response = client.blind_sign(request_body).await?; + Ok(server_response.blinded_signature) + } + + pub fn value_plain(&self) -> String { + self.value.amount.to_string() + } + + pub fn value_attribute(&self) -> &Attribute { + &self.value_prehashed + } + + pub fn tx_hash(&self) -> Hash { + self.deposit_tx_hash + } + + pub fn identity_key(&self) -> &identity::PrivateKey { + &self.signing_key + } + + pub fn encryption_key(&self) -> &encryption::PrivateKey { + &self.unused_ed25519 + } +} +// +// #[deprecated] +// #[derive(Zeroize, ZeroizeOnDrop)] +// pub struct BandwidthVoucher { +// // private attributes +// /// a random secret value generated by the client used for double-spending detection +// serial_number: PrivateAttribute, +// +// /// a random secret value generated by the client used to bind multiple credentials together +// binding_number: PrivateAttribute, +// +// // public atttributes: +// /// the plain text value (e.g., bandwidth) encoded in this voucher +// // TODO: in another PR change the value from `"1000"` to `"1000unym"` +// voucher_value_plain: String, +// +// /// the plain text information +// voucher_info_plain: String, +// +// /// the precomputed value (e.g., bandwidth) encoded in this voucher +// _voucher_value_prehashed: PublicAttribute, +// +// /// the precomputed field with public information, e.g., type of voucher, interval etc. +// _voucher_info_prehashed: PublicAttribute, +// +// /// the hash of the deposit transaction +// #[zeroize(skip)] +// tx_hash: Hash, +// +// /// base58 encoded private key ensuring the depositer requested these attributes +// signing_key: identity::PrivateKey, +// +// /// base58 encoded private key ensuring only this client receives the signature share +// unused_ed25519: encryption::PrivateKey, +// +// pedersen_commitments_openings: Vec, +// +// #[zeroize(skip)] +// blind_sign_request: BlindSignRequest, +// } +// +// impl BandwidthVoucher { +// 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() +// } +// +// pub fn new( +// params: &Parameters, +// voucher_value: String, +// voucher_info: String, +// tx_hash: Hash, +// signing_key: identity::PrivateKey, +// encryption_key: encryption::PrivateKey, +// ) -> Self { +// let serial_number = params.random_scalar(); +// let binding_number = params.random_scalar(); +// let voucher_value_plain = voucher_value.clone(); +// let voucher_info_plain = voucher_info.clone(); +// +// let _voucher_value_prehashed = hash_to_scalar(voucher_value); +// let _voucher_info_prehashed = hash_to_scalar(voucher_info); +// +// let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign( +// params, +// &[&serial_number, &binding_number], +// &[&_voucher_value_prehashed, &_voucher_info_prehashed], +// ) +// .unwrap(); +// BandwidthVoucher { +// serial_number, +// binding_number, +// _voucher_value_prehashed, +// voucher_value_plain, +// _voucher_info_prehashed, +// voucher_info_plain, +// tx_hash, +// signing_key, +// unused_ed25519: encryption_key, +// pedersen_commitments_openings, +// blind_sign_request, +// } +// } +// +// pub fn to_bytes(&self) -> Vec { +// let serial_number_b = self.serial_number.to_bytes(); +// let binding_number_b = self.binding_number.to_bytes(); +// let voucher_value_plain_b = self.voucher_value_plain.as_bytes(); +// let voucher_info_plain_b = self.voucher_info_plain.as_bytes(); +// let tx_hash_b = self.tx_hash.as_bytes(); +// let signing_key_b = self.signing_key.to_bytes(); +// let encryption_key_b = self.unused_ed25519.to_bytes(); +// let blind_sign_request_b = self.blind_sign_request.to_bytes(); +// +// let mut ret = Vec::new(); +// +// ret.extend_from_slice(&serial_number_b); +// ret.extend_from_slice(&binding_number_b); +// ret.extend_from_slice(tx_hash_b); +// ret.extend_from_slice(&signing_key_b); +// ret.extend_from_slice(&encryption_key_b); +// ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes()); +// ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes()); +// ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes()); +// ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes()); +// ret.extend_from_slice(voucher_value_plain_b); +// ret.extend_from_slice(voucher_info_plain_b); +// ret.extend_from_slice(&blind_sign_request_b); +// for commitment in self.pedersen_commitments_openings.iter() { +// ret.extend_from_slice(&commitment.to_bytes()); +// } +// +// ret +// } +// +// pub fn try_from_bytes(bytes: &[u8]) -> Result { +// if bytes.len() < 32 * 5 + 4 * 8 { +// return Err(Error::BandwidthVoucherDeserializationError(format!( +// "Less then {} bytes needed", +// 32 * 5 + 4 * 8 +// ))); +// } +// let mut buff = [0u8; 32]; +// let mut small_buff = [0u8; 8]; +// let scalar_err = +// || Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar")); +// buff.copy_from_slice(&bytes[..32]); +// let serial_number = Option::::from(PrivateAttribute::from_bytes(&buff)) +// .ok_or_else(scalar_err)?; +// buff.copy_from_slice(&bytes[32..2 * 32]); +// let binding_number = Option::::from(PrivateAttribute::from_bytes(&buff)) +// .ok_or_else(scalar_err)?; +// buff.copy_from_slice(&bytes[2 * 32..3 * 32]); +// let tx_hash = Hash::from_bytes(Algorithm::Sha256, &buff).map_err(|_| { +// Error::BandwidthVoucherDeserializationError(String::from("Invalid transaction Hash")) +// })?; +// buff.copy_from_slice(&bytes[3 * 32..4 * 32]); +// let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| { +// Error::BandwidthVoucherDeserializationError(String::from("Invalid key")) +// })?; +// buff.copy_from_slice(&bytes[4 * 32..5 * 32]); +// let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| { +// Error::BandwidthVoucherDeserializationError(String::from("Invalid key")) +// })?; +// small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]); +// let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize; +// small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]); +// let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize; +// small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]); +// let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize; +// small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]); +// let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize; +// +// let total_length = 32 * 5 +// + 4 * 8 +// + voucher_value_plain_no +// + voucher_info_plain_no +// + blind_sign_request_no +// + pedersen_commitments_openings_no * 32; +// if bytes.len() != total_length { +// return Err(Error::BandwidthVoucherDeserializationError(format!( +// "Expected {total_length} bytes", +// ))); +// } +// +// let utf_err = |_| { +// Err(Error::BandwidthVoucherDeserializationError(String::from( +// "Invalid UTF8 string", +// ))) +// }; +// let mut var_length_pointer = 5 * 32 + 4 * 8; +// let voucher_value_plain = String::from_utf8( +// bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(), +// ) +// .or_else(utf_err)?; +// let _voucher_value_prehashed = hash_to_scalar(&voucher_value_plain); +// var_length_pointer += voucher_value_plain_no; +// let voucher_info_plain = String::from_utf8( +// bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(), +// ) +// .or_else(utf_err)?; +// let _voucher_info_prehashed = hash_to_scalar(&voucher_info_plain); +// var_length_pointer += voucher_info_plain_no; +// let blind_sign_request = BlindSignRequest::from_bytes( +// &bytes[var_length_pointer..var_length_pointer + blind_sign_request_no], +// )?; +// var_length_pointer += blind_sign_request_no; +// +// let mut pedersen_commitments_openings = Vec::new(); +// for _ in 0..pedersen_commitments_openings_no { +// buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]); +// let commitment = +// Option::::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?; +// var_length_pointer += 32; +// pedersen_commitments_openings.push(commitment); +// } +// +// Ok(Self { +// serial_number, +// binding_number, +// _voucher_value_prehashed, +// voucher_value_plain, +// _voucher_info_prehashed, +// voucher_info_plain, +// tx_hash, +// signing_key, +// unused_ed25519: encryption_key, +// pedersen_commitments_openings, +// blind_sign_request, +// }) +// } +// +// /// Check if the plain values correspond to the PublicAttributes +// pub fn verify_against_plain(values: &[&PublicAttribute], plain_values: &[String]) -> bool { +// values.len() == 2 +// && plain_values.len() == 2 +// && values[0] == &hash_to_scalar(&plain_values[0]) +// && values[1] == &hash_to_scalar(&plain_values[1]) +// } +// +// pub fn get_public_attributes(&self) -> Vec<&PublicAttribute> { +// vec![ +// &self._voucher_value_prehashed, +// &self._voucher_info_prehashed, +// ] +// } +// +// pub fn tx_hash(&self) -> Hash { +// self.tx_hash +// } +// +// pub fn identity_key(&self) -> &identity::PrivateKey { +// &self.signing_key +// } +// +// pub fn encryption_key(&self) -> &encryption::PrivateKey { +// &self.unused_ed25519 +// } +// +// pub fn pedersen_commitments_openings(&self) -> &Vec { +// &self.pedersen_commitments_openings +// } +// +// pub fn blind_sign_request(&self) -> &BlindSignRequest { +// &self.blind_sign_request +// } +// +// pub fn get_voucher_value(&self) -> String { +// self.voucher_value_plain.clone() +// } +// +// pub fn get_public_attributes_plain(&self) -> Vec { +// vec![ +// self.voucher_value_plain.clone(), +// self.voucher_info_plain.clone(), +// ] +// } +// +// pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> { +// vec![&self.serial_number, &self.binding_number] +// } +// +// pub fn signable_plaintext(request: &BlindSignRequest, tx_hash: Hash) -> Vec { +// let mut message = request.to_bytes(); +// message.extend_from_slice(tx_hash.as_bytes()); +// message +// } +// +// pub fn sign(&self) -> identity::Signature { +// let message = Self::signable_plaintext(&self.blind_sign_request, self.tx_hash); +// self.signing_key.sign(message) +// } +// } + +// pub fn prepare_for_spending( +// voucher_value: u64, +// voucher_info: String, +// serial_number: &PrivateAttribute, +// binding_number: &PrivateAttribute, +// epoch_id: u64, +// signature: &Signature, +// verification_key: &VerificationKey, +// ) -> Result { +// todo!() +// // let params = Parameters::new(BandwidthVoucher::ENCODED_ATTRIBUTES)?; +// // +// // prepare_credential_for_spending( +// // ¶ms, +// // voucher_value, +// // voucher_info, +// // serial_number, +// // binding_number, +// // epoch_id, +// // signature, +// // verification_key, +// // ) +// } + +#[cfg(test)] +mod test { + use super::*; + use cosmrs::tendermint::hash::Algorithm; + use nym_coconut_interface::Base58; + use rand::rngs::OsRng; + + fn voucher_fixture() -> BandwidthVoucher { + let params = Parameters::new(4).unwrap(); + let mut rng = OsRng; + BandwidthVoucher::new( + ¶ms, + "1234".to_string(), + "voucher info".to_string(), + Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap(), + identity::PrivateKey::from_base58_string( + identity::KeyPair::new(&mut rng) + .private_key() + .to_base58_string(), + ) + .unwrap(), + encryption::PrivateKey::from_bytes( + &encryption::KeyPair::new(&mut rng).private_key().to_bytes(), + ) + .unwrap(), + ) + } + + #[test] + fn serde_voucher() { + let voucher = voucher_fixture(); + let bytes = voucher.to_bytes(); + let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap(); + assert_eq!(voucher.serial_number, deserialized_voucher.serial_number); + assert_eq!(voucher.binding_number, deserialized_voucher.binding_number); + assert_eq!( + voucher.voucher_value_plain, + deserialized_voucher.voucher_value_plain + ); + assert_eq!( + voucher.voucher_info_plain, + deserialized_voucher.voucher_info_plain + ); + assert_eq!( + voucher._voucher_value_prehashed, + deserialized_voucher._voucher_value_prehashed + ); + assert_eq!( + voucher._voucher_info_prehashed, + deserialized_voucher._voucher_info_prehashed + ); + assert_eq!(voucher.tx_hash, deserialized_voucher.tx_hash); + assert_eq!( + voucher.signing_key.to_string(), + deserialized_voucher.signing_key.to_string() + ); + assert_eq!( + voucher.unused_ed25519.to_string(), + deserialized_voucher.unused_ed25519.to_string() + ); + assert_eq!( + voucher.pedersen_commitments_openings, + deserialized_voucher.pedersen_commitments_openings + ); + assert_eq!( + voucher.blind_sign_request.to_bs58(), + deserialized_voucher.blind_sign_request.to_bs58() + ); + } + + #[test] + fn voucher_consistency() { + let voucher = voucher_fixture(); + assert!(!BandwidthVoucher::verify_against_plain( + &[], + &voucher.get_public_attributes_plain() + )); + assert!(!BandwidthVoucher::verify_against_plain( + &voucher.get_public_attributes(), + &[], + )); + assert!(!BandwidthVoucher::verify_against_plain( + &voucher.get_public_attributes(), + &[ + voucher.get_public_attributes_plain()[0].clone(), + String::new() + ] + )); + assert!(!BandwidthVoucher::verify_against_plain( + &voucher.get_public_attributes(), + &[ + String::new(), + voucher.get_public_attributes_plain()[1].clone() + ] + )); + assert!(!BandwidthVoucher::verify_against_plain( + &[voucher.get_public_attributes()[0], &Attribute::one()], + &voucher.get_public_attributes_plain() + )); + assert!(!BandwidthVoucher::verify_against_plain( + &[&Attribute::one(), voucher.get_public_attributes()[1]], + &voucher.get_public_attributes_plain() + )); + assert!(BandwidthVoucher::verify_against_plain( + &voucher.get_public_attributes(), + &voucher.get_public_attributes_plain() + )); + } +} diff --git a/common/credentials/src/coconut/credential.rs b/common/credentials/src/coconut/credential.rs new file mode 100644 index 0000000000..6863200e17 --- /dev/null +++ b/common/credentials/src/coconut/credential.rs @@ -0,0 +1,10 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub trait NymCredential { + fn prove_credential(&self); + + // pub attr + // hashed + // private +} diff --git a/common/credentials/src/coconut/mod.rs b/common/credentials/src/coconut/mod.rs index bf480ad58a..c02b932712 100644 --- a/common/credentials/src/coconut/mod.rs +++ b/common/credentials/src/coconut/mod.rs @@ -1,5 +1,6 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 pub mod bandwidth; +pub mod credential; pub mod utils; diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 367f62e2c3..b1247881ff 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -1,13 +1,12 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::coconut::bandwidth::BandwidthVoucher; +use crate::coconut::bandwidth::IssuanceBandwidthCredential; use crate::error::Error; use log::{debug, warn}; -use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut_interface::{ - aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute, - Credential, Parameters, Signature, SignatureShare, VerificationKey, + aggregate_verification_keys, prove_bandwidth_credential, Attribute, Credential, Parameters, + Signature, SignatureShare, VerificationKey, }; use nym_validator_client::client::CoconutApiClient; @@ -30,44 +29,8 @@ pub async fn obtain_aggregate_verification_key( Ok(aggregate_verification_keys(&shares, Some(&indices))?) } -async fn obtain_partial_credential( - params: &Parameters, - voucher: &BandwidthVoucher, - client: &nym_validator_client::client::NymApiClient, - validator_vk: &VerificationKey, -) -> Result { - let public_attributes_plain = voucher.get_public_attributes_plain(); - let blind_sign_request = voucher.blind_sign_request(); - let request_signature = voucher.sign(); - - let blind_sign_request_body = BlindSignRequestBody::new( - blind_sign_request.clone(), - voucher.tx_hash(), - request_signature, - public_attributes_plain, - ); - let response = client.blind_sign(&blind_sign_request_body).await?; - - let blinded_signature = response.blinded_signature; - - let public_attributes = voucher.get_public_attributes(); - let private_attributes = voucher.get_private_attributes(); - - let unblinded_signature = blinded_signature.unblind_and_verify( - params, - validator_vk, - &private_attributes, - &public_attributes, - &blind_sign_request.get_commitment_hash(), - voucher.pedersen_commitments_openings(), - )?; - - Ok(unblinded_signature) -} - pub async fn obtain_aggregate_signature( - params: &Parameters, - voucher: &BandwidthVoucher, + voucher: &IssuanceBandwidthCredential, coconut_api_clients: &[CoconutApiClient], threshold: u64, ) -> Result { @@ -75,16 +38,9 @@ pub async fn obtain_aggregate_signature( return Err(Error::NoValidatorsAvailable); } let mut shares = Vec::with_capacity(coconut_api_clients.len()); - let validators_partial_vks: Vec<_> = coconut_api_clients - .iter() - .map(|api_client| api_client.verification_key.clone()) - .collect(); - let indices: Vec<_> = coconut_api_clients - .iter() - .map(|api_client| api_client.node_id) - .collect(); - let verification_key = - aggregate_verification_keys(&validators_partial_vks, Some(indices.as_ref()))?; + let verification_key = obtain_aggregate_verification_key(coconut_api_clients).await?; + + let request = voucher.prepare_for_signing(); for coconut_api_client in coconut_api_clients.iter() { debug!( @@ -92,13 +48,13 @@ pub async fn obtain_aggregate_signature( coconut_api_client.api_client.api_url() ); - match obtain_partial_credential( - params, - voucher, - &coconut_api_client.api_client, - &coconut_api_client.verification_key, - ) - .await + match voucher + .obtain_partial_credential( + &coconut_api_client.api_client, + &coconut_api_client.verification_key, + Some(request.clone()), + ) + .await { Ok(signature) => { let share = SignatureShare::new(signature, coconut_api_client.node_id); @@ -116,15 +72,7 @@ pub async fn obtain_aggregate_signature( return Err(Error::NotEnoughShares); } - let public_attributes = voucher.get_public_attributes(); - let private_attributes = voucher.get_private_attributes(); - - 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) + voucher.aggregate_signature_shares(&verification_key, &shares) } // TODO: better type flow @@ -148,7 +96,7 @@ pub fn prepare_credential_for_spending( )?; Ok(Credential::new( - BandwidthVoucher::ENCODED_ATTRIBUTES, + IssuanceBandwidthCredential::ENCODED_ATTRIBUTES, theta, voucher_value, voucher_info, diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index ea265e83d3..4404b0e51f 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -466,10 +466,9 @@ pub const UTOKENS_TO_BURN: u64 = TOKENS_TO_BURN * 1000000; /// Default bandwidth (in bytes) that we try to buy pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN; +#[deprecated] pub const VOUCHER_INFO: &str = "BandwidthVoucher"; -pub const ETH_MIN_BLOCK_DEPTH: usize = 7; - /// Defaults Cosmos Hub/ATOM path pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0"; // as set by validators in their configs diff --git a/common/nymcoconut/src/impls/clone.rs b/common/nymcoconut/src/impls/clone.rs index fdcee5c0de..88ee6664c0 100644 --- a/common/nymcoconut/src/impls/clone.rs +++ b/common/nymcoconut/src/impls/clone.rs @@ -1,4 +1,4 @@ -use crate::{BlindSignRequest, BlindedSignature, Bytable, Theta}; +use crate::{BlindSignRequest, BlindedSignature, Bytable, VerifyCredentialRequest}; macro_rules! impl_clone { ($struct:ident) => { @@ -12,4 +12,4 @@ macro_rules! impl_clone { impl_clone!(BlindSignRequest); impl_clone!(BlindedSignature); -impl_clone!(Theta); +impl_clone!(VerifyCredentialRequest); diff --git a/common/nymcoconut/src/impls/serde.rs b/common/nymcoconut/src/impls/serde.rs index 91dbf41f46..7d1e020cb1 100644 --- a/common/nymcoconut/src/impls/serde.rs +++ b/common/nymcoconut/src/impls/serde.rs @@ -1,7 +1,8 @@ use crate::elgamal::PrivateKey; use crate::scheme::SecretKey; use crate::{ - Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, Theta, VerificationKey, + Base58, BlindSignRequest, BlindedSignature, PublicKey, Signature, VerificationKey, + VerifyCredentialRequest, }; use serde::de::Unexpected; use serde::{de::Error, de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; @@ -53,4 +54,4 @@ impl_serde!(PrivateKey, V4); impl_serde!(BlindSignRequest, V5); impl_serde!(BlindedSignature, V6); impl_serde!(Signature, V7); -impl_serde!(Theta, V8); +impl_serde!(VerifyCredentialRequest, V8); diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs index 7f1631f801..ed258cd557 100644 --- a/common/nymcoconut/src/lib.rs +++ b/common/nymcoconut/src/lib.rs @@ -23,7 +23,7 @@ pub use scheme::setup::Parameters; pub use scheme::verification::check_vk_pairing; pub use scheme::verification::prove_bandwidth_credential; pub use scheme::verification::verify_credential; -pub use scheme::verification::Theta; +pub use scheme::verification::VerifyCredentialRequest; pub use scheme::BlindedSignature; pub use scheme::Signature; pub use scheme::SignatureShare; diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index 1373c30bc7..cbf36fde2f 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -21,7 +21,7 @@ use crate::Attribute; // TODO NAMING: this whole thing // Theta #[derive(Debug, PartialEq, Eq)] -pub struct Theta { +pub struct VerifyCredentialRequest { // blinded_message (kappa) pub blinded_message: G2Projective, // blinded serial number (zeta) @@ -32,10 +32,10 @@ pub struct Theta { pub pi_v: ProofKappaZeta, } -impl TryFrom<&[u8]> for Theta { +impl TryFrom<&[u8]> for VerifyCredentialRequest { type Error = CoconutError; - fn try_from(bytes: &[u8]) -> Result { + fn try_from(bytes: &[u8]) -> Result { if bytes.len() < 288 { return Err( CoconutError::Deserialization( @@ -66,7 +66,7 @@ impl TryFrom<&[u8]> for Theta { let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?; - Ok(Theta { + Ok(VerifyCredentialRequest { blinded_message, blinded_serial_number, credential, @@ -75,7 +75,7 @@ impl TryFrom<&[u8]> for Theta { } } -impl Theta { +impl VerifyCredentialRequest { fn verify_proof(&self, params: &Parameters, verification_key: &VerificationKey) -> bool { self.pi_v.verify( params, @@ -107,8 +107,8 @@ impl Theta { bytes } - pub fn from_bytes(bytes: &[u8]) -> Result { - Theta::try_from(bytes) + pub fn from_bytes(bytes: &[u8]) -> Result { + VerifyCredentialRequest::try_from(bytes) } pub fn blinded_serial_number_bs58(&self) -> String { @@ -119,17 +119,17 @@ impl Theta { } } -impl Bytable for Theta { +impl Bytable for VerifyCredentialRequest { fn to_byte_vec(&self) -> Vec { self.to_bytes() } fn try_from_byte_slice(slice: &[u8]) -> Result { - Theta::try_from(slice) + VerifyCredentialRequest::try_from(slice) } } -impl Base58 for Theta {} +impl Base58 for VerifyCredentialRequest {} pub fn compute_kappa( params: &Parameters, @@ -156,7 +156,7 @@ pub fn prove_bandwidth_credential( signature: &Signature, serial_number: &Attribute, binding_number: &Attribute, -) -> Result { +) -> Result { if verification_key.beta_g2.len() < 2 { return Err( CoconutError::Verification( @@ -196,7 +196,7 @@ pub fn prove_bandwidth_credential( &blinded_serial_number, ); - Ok(Theta { + Ok(VerifyCredentialRequest { blinded_message, blinded_serial_number, credential: signature_prime, @@ -256,7 +256,7 @@ pub fn check_vk_pairing( pub fn verify_credential( params: &Parameters, verification_key: &VerificationKey, - theta: &Theta, + theta: &VerifyCredentialRequest, public_attributes: &[&Attribute], ) -> bool { if public_attributes.len() + theta.pi_v.private_attributes_len() @@ -358,6 +358,9 @@ mod tests { .unwrap(); let bytes = theta.to_bytes(); - assert_eq!(Theta::try_from(bytes.as_slice()).unwrap(), theta); + assert_eq!( + VerifyCredentialRequest::try_from(bytes.as_slice()).unwrap(), + theta + ); } } diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs index e0f58ba33b..9b3b16d44c 100644 --- a/common/nymcoconut/src/tests/helpers.rs +++ b/common/nymcoconut/src/tests/helpers.rs @@ -12,7 +12,7 @@ pub fn theta_from_keys_and_attributes( coconut_keypairs: &Vec, indices: &[scheme::SignerIndex], public_attributes: &[&PublicAttribute], -) -> Result { +) -> Result { let serial_number = params.random_scalar(); let binding_number = params.random_scalar(); let private_attributes = vec![&serial_number, &binding_number]; diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 12fa660f51..c8a739319d 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only use crate::coconut::api_routes::helpers::build_credentials_response; @@ -17,7 +17,7 @@ use nym_coconut_bandwidth_contract_common::spend_credential::{ funds_from_cosmos_msgs, SpendCredentialStatus, }; use nym_coconut_dkg_common::types::EpochId; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential; use nym_validator_client::nyxd::Coin; use rocket::serde::json::Json; use rocket::State as RocketState; @@ -36,7 +36,7 @@ pub async fn post_blind_sign( // early check: does the request have the expected number of public attributes? debug!("performing basic request validation"); if blind_sign_request_body.public_attributes_plain.len() - != BandwidthVoucher::PUBLIC_ATTRIBUTES as usize + != IssuanceBandwidthCredential::PUBLIC_ATTRIBUTES as usize { return Err(CoconutError::InconsistentPublicAttributes); } diff --git a/nym-api/src/coconut/deposit.rs b/nym-api/src/coconut/deposit.rs index 22f59fc969..ab1c345991 100644 --- a/nym-api/src/coconut/deposit.rs +++ b/nym-api/src/coconut/deposit.rs @@ -7,13 +7,16 @@ use nym_coconut_bandwidth_contract_common::events::{ COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, DEPOSIT_VALUE, }; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData; +use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential; use nym_crypto::asymmetric::identity; use nym_validator_client::nyxd::helpers::find_tx_attribute; use nym_validator_client::nyxd::TxResponse; pub async fn validate_deposit_tx(request: &BlindSignRequestBody, tx: TxResponse) -> Result<()> { - if request.public_attributes_plain.len() != BandwidthVoucher::PUBLIC_ATTRIBUTES as usize { + if request.public_attributes_plain.len() + != IssuanceBandwidthCredential::PUBLIC_ATTRIBUTES as usize + { return Err(CoconutError::InconsistentPublicAttributes); } @@ -58,8 +61,10 @@ pub async fn validate_deposit_tx(request: &BlindSignRequestBody, tx: TxResponse) // verify signature let x25519 = identity::PublicKey::from_base58_string(x25519_raw)?; - let plaintext = - BandwidthVoucher::signable_plaintext(&request.inner_sign_request, request.tx_hash); + let plaintext = BandwidthVoucherIssuanceData::request_plaintext( + &request.inner_sign_request, + request.tx_hash, + ); x25519.verify(plaintext, &request.signature)?; Ok(()) diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs index de38ca614f..72fad9b27d 100644 --- a/nym-api/src/coconut/state.rs +++ b/nym-api/src/coconut/state.rs @@ -10,23 +10,13 @@ use crate::coconut::storage::CoconutStorageExt; use crate::support::storage::NymApiStorage; use nym_api_requests::coconut::helpers::issued_credential_plaintext; use nym_api_requests::coconut::BlindSignRequestBody; -use nym_coconut::Parameters; use nym_coconut_dkg_common::types::EpochId; use nym_coconut_interface::{BlindedSignature, VerificationKey}; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; use nym_crypto::asymmetric::identity; use nym_validator_client::nyxd::{Hash, TxResponse}; -use std::sync::{Arc, OnceLock}; +use std::sync::Arc; -// keep it as a global static due to relatively high cost of computing the curve points; -// plus we expect all clients to use the same set of parameters -// -// future note: once we allow for credentials with variable number of attributes, just create Parameters(max_allowed_attributes) -// and take as many hs elements as required (since they will match for all variants) -pub(crate) fn bandwidth_voucher_params() -> &'static Parameters { - static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock = OnceLock::new(); - BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(BandwidthVoucher::default_parameters) -} +pub use nym_credentials::coconut::bandwidth::bandwidth_voucher_params; pub struct State { pub(crate) client: Arc, diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index c284610844..9083541347 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3937,7 +3937,9 @@ dependencies = [ "nym-coconut-interface", "nym-crypto", "nym-validator-client", + "serde", "thiserror", + "time", "zeroize", ] diff --git a/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs b/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs index 96bdef6aae..251489b3b5 100644 --- a/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs +++ b/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs @@ -1,4 +1,4 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::config; @@ -11,25 +11,18 @@ use crate::rewarder::nyxd_client::NyxdClient; use bip39::rand::prelude::SliceRandom; use bip39::rand::thread_rng; use nym_coconut::{ - hash_to_scalar, verify_partial_blind_signature, Base58, G1Projective, Parameters, - VerificationKey, + hash_to_scalar, verify_partial_blind_signature, Base58, G1Projective, VerificationKey, }; use nym_coconut_dkg_common::types::EpochId; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::bandwidth_voucher_params; use nym_task::TaskClient; use nym_validator_client::nym_api::{IssuedCredential, IssuedCredentialBody, NymApiClientExt}; use nym_validator_client::nyxd::Hash; use std::cmp::max; use std::collections::HashMap; -use std::sync::OnceLock; use tokio::time::interval; use tracing::{debug, error, info, instrument, trace, warn}; -pub(crate) fn bandwidth_voucher_params() -> &'static Parameters { - static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock = OnceLock::new(); - BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(BandwidthVoucher::default_parameters) -} - pub struct CredentialIssuanceMonitor { nyxd_client: NyxdClient, monitoring_results: MonitoringResults, diff --git a/sdk/rust/nym-sdk/src/bandwidth/client.rs b/sdk/rust/nym-sdk/src/bandwidth/client.rs index 7171282a71..2733279e58 100644 --- a/sdk/rust/nym-sdk/src/bandwidth/client.rs +++ b/sdk/rust/nym-sdk/src/bandwidth/client.rs @@ -4,7 +4,7 @@ use crate::error::{Error, Result}; use nym_bandwidth_controller::acquire::state::State; use nym_credential_storage::storage::Storage; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential; use nym_network_defaults::NymNetworkDetails; use nym_validator_client::nyxd::Coin; use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; @@ -68,7 +68,7 @@ where /// In case of an error in the mid of the acquire process, this function should be used for /// later retries to recover the bandwidth credential, either immediately or after some time. pub async fn recover(&self, voucher_blob: &VoucherBlob) -> Result<()> { - let voucher = BandwidthVoucher::try_from_bytes(voucher_blob) + let voucher = IssuanceBandwidthCredential::try_from_bytes(voucher_blob) .map_err(|_| Error::InvalidVoucherBlob)?; let state = State::new(voucher); nym_bandwidth_controller::acquire::get_credential(&state, &self.client, self.storage) From 36242fa2571ead3c09ceed3dcfa9701049e88ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 6 Feb 2024 17:58:21 +0000 Subject: [PATCH 02/49] serde for 'IssuanceBandwidthCredential' --- .../src/coconut/bandwidth/freepass.rs | 4 +- .../src/coconut/bandwidth/issuance.rs | 9 +++- .../src/coconut/bandwidth/voucher.rs | 4 +- common/credentials/src/coconut/utils.rs | 45 ++++++++----------- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs index d0e17ba55b..5783a8c25d 100644 --- a/common/credentials/src/coconut/bandwidth/freepass.rs +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::coconut::utils::scalar_serde_helper; use nym_coconut_interface::{hash_to_scalar, Attribute, PublicAttribute}; use serde::{Deserialize, Serialize}; use time::{Duration, OffsetDateTime, Time}; @@ -29,13 +30,14 @@ impl FreePassIssuedData { } } -#[derive(Zeroize, ZeroizeOnDrop)] +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] pub struct FreePassIssuanceData { /// the plain validity value of this credential expressed as unix timestamp #[zeroize(skip)] expiry_date: OffsetDateTime, // the expiry date, as unix timestamp, hashed into a scalar + #[serde(with = "scalar_serde_helper")] expiry_date_prehashed: PublicAttribute, } diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index ba694d37f8..36d540c493 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -7,6 +7,7 @@ use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData; use crate::coconut::bandwidth::{ bandwidth_voucher_params, CredentialSigningData, CredentialSpendingData, CredentialType, }; +use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; use nym_coconut_interface::{ aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, prove_bandwidth_credential, @@ -15,10 +16,11 @@ use nym_coconut_interface::{ }; use nym_crypto::asymmetric::{encryption, identity}; use nym_validator_client::nyxd::{Coin, Hash}; +use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use zeroize::{Zeroize, ZeroizeOnDrop}; -#[derive(Zeroize, ZeroizeOnDrop)] +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] pub enum BandwidthCredentialIssuanceDataVariant { Voucher(BandwidthVoucherIssuanceData), FreePass(FreePassIssuanceData), @@ -66,19 +68,22 @@ impl BandwidthCredentialIssuanceDataVariant { } // all types of bandwidth credentials contain serial number and binding number -#[derive(Zeroize, ZeroizeOnDrop)] +#[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, } diff --git a/common/credentials/src/coconut/bandwidth/voucher.rs b/common/credentials/src/coconut/bandwidth/voucher.rs index cce7726b60..06f88d2414 100644 --- a/common/credentials/src/coconut/bandwidth/voucher.rs +++ b/common/credentials/src/coconut/bandwidth/voucher.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::bandwidth::CredentialSigningData; +use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut_interface::{ @@ -34,7 +35,7 @@ impl BandwidthVoucherIssuedData { } } -#[derive(Zeroize, ZeroizeOnDrop)] +#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] pub struct BandwidthVoucherIssuanceData { /// the plain value (e.g., bandwidth) encoded in this voucher // note: for legacy reasons we're only using the value of the coin and ignoring the denom @@ -42,6 +43,7 @@ pub struct BandwidthVoucherIssuanceData { value: Coin, // note: as mentioned above, we're only hashing the value of the coin! + #[serde(with = "scalar_serde_helper")] value_prehashed: PublicAttribute, /// the hash of the deposit transaction diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index b1247881ff..2a59d0fc2d 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -75,31 +75,24 @@ pub async fn obtain_aggregate_signature( voucher.aggregate_signature_shares(&verification_key, &shares) } -// TODO: better type flow -#[allow(clippy::too_many_arguments)] -pub fn prepare_credential_for_spending( - params: &Parameters, - voucher_value: u64, - voucher_info: String, - serial_number: &Attribute, - binding_number: &Attribute, - epoch_id: u64, - signature: &Signature, - verification_key: &VerificationKey, -) -> Result { - let theta = prove_bandwidth_credential( - params, - verification_key, - signature, - serial_number, - binding_number, - )?; +pub(crate) mod scalar_serde_helper { + use bls12_381::Scalar; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use zeroize::Zeroizing; - Ok(Credential::new( - IssuanceBandwidthCredential::ENCODED_ATTRIBUTES, - theta, - voucher_value, - voucher_info, - epoch_id, - )) + pub fn serialize(scalar: &Scalar, serializer: S) -> Result { + scalar.to_bytes().serialize(serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + let b = <[u8; 32]>::deserialize(deserializer)?; + + // make sure the bytes get zeroed + let bytes = Zeroizing::new(b); + + let maybe_scalar: Option = Scalar::from_bytes(&bytes).into(); + maybe_scalar.ok_or(serde::de::Error::custom( + "did not construct a valid bls12-381 scalar out of the provided bytes", + )) + } } From 7a7fbce8ea98a2c7943cd068543afe3b7ca95e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 6 Feb 2024 18:10:25 +0000 Subject: [PATCH 03/49] using bincode serialization --- .../credential-utils/src/recovery_storage.rs | 2 +- common/credentials/Cargo.toml | 1 + .../src/coconut/bandwidth/issuance.rs | 22 +++++++++---------- .../src/coconut/bandwidth/issued.rs | 4 ++-- common/credentials/src/coconut/utils.rs | 10 +++++++-- common/credentials/src/error.rs | 3 +++ sdk/rust/nym-sdk/src/bandwidth/client.rs | 4 ++-- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/common/credential-utils/src/recovery_storage.rs b/common/credential-utils/src/recovery_storage.rs index 6dc6ec76bd..3c9b8c17af 100644 --- a/common/credential-utils/src/recovery_storage.rs +++ b/common/credential-utils/src/recovery_storage.rs @@ -34,7 +34,7 @@ impl RecoveryStorage { if let Ok(mut file) = File::open(&path) { let mut buff = Vec::new(); if file.read_to_end(&mut buff).is_ok() { - match IssuanceBandwidthCredential::try_from_bytes(&buff) { + match IssuanceBandwidthCredential::try_from_recovered_bytes(&buff) { Ok(voucher) => vouchers.push(voucher), Err(err) => { error!("failed to parse the voucher at {}: {err}", path.display()) diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 9c42f08418..f08a3a5cd5 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] } +bincode = "1.3.3" cosmrs = { workspace = true } thiserror = { workspace = true } log = { workspace = true } diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 36d540c493..e889fcef22 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -4,15 +4,12 @@ use crate::coconut::bandwidth::freepass::FreePassIssuanceData; use crate::coconut::bandwidth::issued::IssuedBandwidthCredential; use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData; -use crate::coconut::bandwidth::{ - bandwidth_voucher_params, CredentialSigningData, CredentialSpendingData, CredentialType, -}; -use crate::coconut::utils::scalar_serde_helper; +use crate::coconut::bandwidth::{bandwidth_voucher_params, CredentialSigningData, CredentialType}; +use crate::coconut::utils::{make_bincode_serializer, scalar_serde_helper}; use crate::error::Error; use nym_coconut_interface::{ - aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, prove_bandwidth_credential, - Attribute, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, - VerificationKey, + aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, Parameters, + PrivateAttribute, PublicAttribute, Signature, SignatureShare, VerificationKey, }; use nym_crypto::asymmetric::{encryption, identity}; use nym_validator_client::nyxd::{Coin, Hash}; @@ -240,12 +237,15 @@ impl IssuanceBandwidthCredential { } // TODO: is that actually needed? - pub fn to_bytes(&self) -> Vec { - todo!() + pub fn to_recovery_bytes(&self) -> Vec { + use bincode::Options; + // safety: our data format is stable and thus the serialization should not fail + make_bincode_serializer().serialize(self).unwrap() } // TODO: is that actually needed? - pub fn try_from_bytes(bytes: &[u8]) -> Result { - todo!() + pub fn try_from_recovered_bytes(bytes: &[u8]) -> Result { + use bincode::Options; + Ok(make_bincode_serializer().deserialize(bytes)?) } } diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index 2c541d2701..e12cacb6c4 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -9,8 +9,8 @@ use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuedData; use crate::coconut::bandwidth::{bandwidth_voucher_params, CredentialSpendingData, CredentialType}; use crate::error::Error; use nym_coconut_interface::{ - prove_bandwidth_credential, Attribute, Parameters, PrivateAttribute, PublicAttribute, - Signature, VerificationKey, + prove_bandwidth_credential, Parameters, PrivateAttribute, PublicAttribute, Signature, + VerificationKey, }; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 2a59d0fc2d..08379c9017 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -5,8 +5,7 @@ use crate::coconut::bandwidth::IssuanceBandwidthCredential; use crate::error::Error; use log::{debug, warn}; use nym_coconut_interface::{ - aggregate_verification_keys, prove_bandwidth_credential, Attribute, Credential, Parameters, - Signature, SignatureShare, VerificationKey, + aggregate_verification_keys, Signature, SignatureShare, VerificationKey, }; use nym_validator_client::client::CoconutApiClient; @@ -96,3 +95,10 @@ pub(crate) mod scalar_serde_helper { )) } } + +pub(crate) fn make_bincode_serializer() -> impl bincode::Options { + use bincode::Options; + bincode::DefaultOptions::new() + .with_big_endian() + .with_varint_encoding() +} diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 4c10971e3d..c89667aad6 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -12,6 +12,9 @@ pub enum Error { #[error("IO error")] IOError(#[from] std::io::Error), + #[error("failed to (de)serialize credential structure: {0}")] + SerializationFailure(#[from] bincode::Error), + #[error("The detailed description is yet to be determined")] BandwidthCredentialError, diff --git a/sdk/rust/nym-sdk/src/bandwidth/client.rs b/sdk/rust/nym-sdk/src/bandwidth/client.rs index 2733279e58..c5756e0cdc 100644 --- a/sdk/rust/nym-sdk/src/bandwidth/client.rs +++ b/sdk/rust/nym-sdk/src/bandwidth/client.rs @@ -61,14 +61,14 @@ where .await .map_err(|reason| Error::UnconvertedDeposit { reason, - voucher_blob: state.voucher.to_bytes(), + voucher_blob: state.voucher.to_recovery_bytes(), }) } /// In case of an error in the mid of the acquire process, this function should be used for /// later retries to recover the bandwidth credential, either immediately or after some time. pub async fn recover(&self, voucher_blob: &VoucherBlob) -> Result<()> { - let voucher = IssuanceBandwidthCredential::try_from_bytes(voucher_blob) + let voucher = IssuanceBandwidthCredential::try_from_recovered_bytes(voucher_blob) .map_err(|_| Error::InvalidVoucherBlob)?; let state = State::new(voucher); nym_bandwidth_controller::acquire::get_credential(&state, &self.client, self.storage) From 6f3dd9f778c89f6961e79e6689ee8277367cf427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 7 Feb 2024 11:32:26 +0000 Subject: [PATCH 04/49] wip --- Cargo.lock | 4 +- common/bandwidth-controller/Cargo.toml | 1 + common/bandwidth-controller/src/lib.rs | 71 ++++++++++----- common/client-libs/gateway-client/Cargo.toml | 2 +- .../client-libs/gateway-client/src/client.rs | 18 ++-- common/coconut-interface/src/lib.rs | 8 +- common/credential-storage/src/models.rs | 5 +- .../src/coconut/bandwidth/issuance.rs | 29 ++++--- .../src/coconut/bandwidth/issued.rs | 8 +- .../credentials/src/coconut/bandwidth/mod.rs | 39 ++++++++- common/credentials/src/coconut/utils.rs | 7 -- common/credentials/src/lib.rs | 4 + gateway/gateway-requests/src/lib.rs | 1 + gateway/gateway-requests/src/models.rs | 30 +++++++ gateway/gateway-requests/src/types.rs | 87 +++++++++---------- nym-api/src/coconut/dkg/key_derivation.rs | 6 +- nym-api/src/coconut/dkg/key_validation.rs | 4 +- nym-api/src/coconut/helpers.rs | 4 +- nym-api/src/coconut/keys/persistence.rs | 4 +- nym-api/src/coconut/state.rs | 2 +- nym-connect/desktop/Cargo.lock | 4 +- .../rewarder/credential_issuance/monitor.rs | 4 +- 22 files changed, 217 insertions(+), 125 deletions(-) create mode 100644 gateway/gateway-requests/src/models.rs diff --git a/Cargo.lock b/Cargo.lock index 7d92bcd044..468a7c6790 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5015,6 +5015,7 @@ name = "nym-bandwidth-controller" version = "0.1.0" dependencies = [ "bip39", + "log", "nym-coconut-interface", "nym-credential-storage", "nym-credentials", @@ -5375,6 +5376,7 @@ dependencies = [ name = "nym-credentials" version = "0.1.0" dependencies = [ + "bincode", "bls12_381", "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "log", @@ -5561,8 +5563,8 @@ dependencies = [ "gloo-utils", "log", "nym-bandwidth-controller", - "nym-coconut-interface", "nym-credential-storage", + "nym-credentials", "nym-crypto", "nym-gateway-requests", "nym-network-defaults", diff --git a/common/bandwidth-controller/Cargo.toml b/common/bandwidth-controller/Cargo.toml index 75050e6e75..116f8d20c4 100644 --- a/common/bandwidth-controller/Cargo.toml +++ b/common/bandwidth-controller/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true [dependencies] bip39 = { workspace = true } +log = { workspace = true } rand = "0.7.3" thiserror = { workspace = true } url = { workspace = true } diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index f114dcab83..d6f5dfb731 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -3,16 +3,16 @@ use crate::error::BandwidthControllerError; use crate::utils::stored_credential_to_issued_bandwidth; +use log::{error, warn}; +use nym_coconut_interface::VerificationKey; use nym_credential_storage::error::StorageError; use nym_credential_storage::storage::Storage; +use nym_credentials::coconut::bandwidth::CredentialSpendingData; +use nym_credentials::coconut::utils::obtain_aggregate_verification_key; use nym_validator_client::coconut::all_coconut_api_clients; +use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use std::str::FromStr; -use zeroize::Zeroizing; -use { - nym_coconut_interface::Base58, - nym_credentials::coconut::utils::obtain_aggregate_verification_key, -}; pub mod acquire; pub mod error; @@ -23,6 +23,18 @@ pub struct BandwidthController { client: C, } +pub struct PreparedCredential { + /// The cryptographic material required for spending the underlying credential. + pub data: CredentialSpendingData, + + /// The (DKG) epoch id under which the credential has been issued so that the verifier + /// could use correct verification key for validation. + pub epoch_id: EpochId, + + /// The database id of the stored credential. + pub credential_id: i64, +} + impl BandwidthController { pub fn new(storage: St, client: C) -> Self { BandwidthController { storage, client } @@ -32,9 +44,21 @@ impl BandwidthController { &self.storage } + async fn get_aggregate_verification_key( + &self, + epoch_id: EpochId, + ) -> Result + where + C: DkgQueryClient + Sync + Send, + ::StorageError: Send + Sync + 'static, + { + let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?; + Ok(obtain_aggregate_verification_key(&coconut_api_clients).await?) + } + pub async fn prepare_coconut_credential( &self, - ) -> Result<(nym_coconut_interface::Credential, i64), BandwidthControllerError> + ) -> Result where C: DkgQueryClient + Sync + Send, ::StorageError: Send + Sync + 'static, @@ -47,29 +71,28 @@ impl BandwidthController { let epoch_id = u64::from_str(&retrieved_credential.epoch_id) .map_err(|_| StorageError::InconsistentData)?; + let credential_id = retrieved_credential.id; let issued_bandwidth = stored_credential_to_issued_bandwidth(retrieved_credential)?; - let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?; - let verification_key = obtain_aggregate_verification_key(&coconut_api_clients).await?; + let verification_key = match self.get_aggregate_verification_key(epoch_id).await { + Ok(key) => key, + Err(err) => { + warn!("failed to obtain master verification key: {err}. Putting the credential back into the database"); + + // TODO: ERROR RECOVERY: + error!("unimplemented: putting the credential back into the database"); + return Err(err); + } + }; let spend_request = issued_bandwidth.prepare_for_spending(&verification_key)?; - todo!() - - // // the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff) - // Ok(( - // prepare_for_spending( - // voucher_value, - // voucher_info, - // &serial_number, - // &binding_number, - // epoch_id, - // &signature, - // &verification_key, - // )?, - // bandwidth_credential.id, - // )) + Ok(PreparedCredential { + data: spend_request, + epoch_id, + credential_id, + }) } pub async fn consume_credential(&self, id: i64) -> Result<(), BandwidthControllerError> @@ -88,7 +111,7 @@ impl BandwidthController { impl Clone for BandwidthController where C: Clone, - St: Storage + Clone, + St: Clone, { fn clone(&self) -> Self { BandwidthController { diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 67297687e4..44b9a58e82 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -19,7 +19,7 @@ tokio = { version = "1.24.1", features = ["macros"] } # internal nym-bandwidth-controller = { path = "../../bandwidth-controller" } -nym-coconut-interface = { path = "../../coconut-interface" } +nym-credentials = { path = "../../credentials" } nym-credential-storage = { path = "../../credential-storage" } nym-crypto = { path = "../../crypto" } nym-gateway-requests = { path = "../../../gateway/gateway-requests" } diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index f65e61c29f..08d25beed7 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -1,4 +1,4 @@ -// Copyright 2021-2023 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::error::GatewayClientError; @@ -12,9 +12,9 @@ use crate::{cleanup_socket_message, try_decrypt_binary_message}; use futures::{SinkExt, StreamExt}; use log::*; use nym_bandwidth_controller::BandwidthController; -use nym_coconut_interface::Credential; use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage; use nym_credential_storage::storage::Storage as CredentialStorage; +use nym_credentials::CredentialSpendingData; use nym_crypto::asymmetric::identity; use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use nym_gateway_requests::iv::IV; @@ -23,6 +23,7 @@ use nym_gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; +use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use rand::rngs::OsRng; use std::convert::TryFrom; @@ -515,13 +516,15 @@ impl GatewayClient { async fn claim_coconut_bandwidth( &mut self, - credential: Credential, + credential: CredentialSpendingData, + epoch_id: EpochId, ) -> Result<(), GatewayClientError> { let mut rng = OsRng; let iv = IV::new_random(&mut rng); let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential( - &credential, + credential, + epoch_id, self.shared_key.as_ref().unwrap(), iv, ) @@ -567,18 +570,19 @@ impl GatewayClient { return self.try_claim_testnet_bandwidth().await; } - let (credential, credential_id) = self + let prepared_credential = self .bandwidth_controller .as_ref() .unwrap() .prepare_coconut_credential() .await?; - self.claim_coconut_bandwidth(credential).await?; + self.claim_coconut_bandwidth(prepared_credential.data, prepared_credential.epoch_id) + .await?; self.bandwidth_controller .as_ref() .unwrap() - .consume_credential(credential_id) + .consume_credential(prepared_credential.credential_id) .await?; Ok(()) diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 2a655a2980..4ac0706463 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -11,12 +11,14 @@ pub mod error; // module. pub use nym_coconut::{ aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, - prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest, - BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute, - PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey, + prepare_blind_sign, prove_bandwidth_credential, verify_credential, Attribute, Base58, + BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, + PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey, VerifyCredentialRequest, }; +// TODO: maybe just remove this sucker? +#[deprecated] #[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)] pub struct Credential { #[getset(get = "pub")] diff --git a/common/credential-storage/src/models.rs b/common/credential-storage/src/models.rs index 7a1686b3d5..47372dd2b6 100644 --- a/common/credential-storage/src/models.rs +++ b/common/credential-storage/src/models.rs @@ -1,8 +1,6 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use sqlx::FromRow; - #[derive(Clone)] pub struct CoconutCredential { #[allow(dead_code)] @@ -16,9 +14,8 @@ pub struct CoconutCredential { pub consumed: bool, } -#[derive(FromRow)] +#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))] pub struct StoredIssuedCredential { - #[allow(dead_code)] pub id: i64, pub serial_number: String, diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index e889fcef22..736555bfdc 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -4,8 +4,10 @@ use crate::coconut::bandwidth::freepass::FreePassIssuanceData; use crate::coconut::bandwidth::issued::IssuedBandwidthCredential; use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData; -use crate::coconut::bandwidth::{bandwidth_voucher_params, CredentialSigningData, CredentialType}; -use crate::coconut::utils::{make_bincode_serializer, scalar_serde_helper}; +use crate::coconut::bandwidth::{ + bandwidth_credential_params, CredentialSigningData, CredentialType, +}; +use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; use nym_coconut_interface::{ aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, Parameters, @@ -89,9 +91,6 @@ impl IssuanceBandwidthCredential { pub const PRIVATE_ATTRIBUTES: u32 = 2; pub const ENCODED_ATTRIBUTES: u32 = Self::PUBLIC_ATTRIBUTES + Self::PRIVATE_ATTRIBUTES; - // just keep this value on hand for any possible future changes so that we could preserve backwards compatibility - pub const ENCODING_VERSION: u8 = 1; - pub fn default_parameters() -> Parameters { // safety: the unwrap is fine here as Self::ENCODED_ATTRIBUTES is non-zero Parameters::new(Self::ENCODED_ATTRIBUTES).unwrap() @@ -101,7 +100,7 @@ impl IssuanceBandwidthCredential { let variant_data = variant_data.into(); let type_prehashed = hash_to_scalar(variant_data.info().to_string()); - let params = bandwidth_voucher_params(); + let params = bandwidth_credential_params(); let serial_number = params.random_scalar(); let binding_number = params.random_scalar(); @@ -151,7 +150,7 @@ impl IssuanceBandwidthCredential { } pub fn prepare_for_signing(&self) -> CredentialSigningData { - let params = bandwidth_voucher_params(); + let params = bandwidth_credential_params(); // 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 @@ -166,6 +165,7 @@ impl IssuanceBandwidthCredential { pedersen_commitments_openings, blind_sign_request, public_attributes_plain: self.get_plain_public_attributes(), + typ: self.typ(), } } @@ -192,7 +192,7 @@ impl IssuanceBandwidthCredential { let public_attributes = self.get_public_attributes(); let private_attributes = self.get_private_attributes(); - let params = bandwidth_voucher_params(); + let params = bandwidth_credential_params(); let unblinded_signature = blinded_signature.unblind_and_verify( params, validator_vk, @@ -213,7 +213,7 @@ impl IssuanceBandwidthCredential { let public_attributes = self.get_public_attributes(); let private_attributes = self.get_private_attributes(); - let params = bandwidth_voucher_params(); + let params = bandwidth_credential_params(); let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); attributes.extend_from_slice(&private_attributes); @@ -240,12 +240,19 @@ impl IssuanceBandwidthCredential { pub fn to_recovery_bytes(&self) -> Vec { use bincode::Options; // safety: our data format is stable and thus the serialization should not fail - make_bincode_serializer().serialize(self).unwrap() + make_recovery_bincode_serializer().serialize(self).unwrap() } // TODO: is that actually needed? pub fn try_from_recovered_bytes(bytes: &[u8]) -> Result { use bincode::Options; - Ok(make_bincode_serializer().deserialize(bytes)?) + Ok(make_recovery_bincode_serializer().deserialize(bytes)?) } } + +fn make_recovery_bincode_serializer() -> impl bincode::Options { + use bincode::Options; + bincode::DefaultOptions::new() + .with_big_endian() + .with_varint_encoding() +} diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index e12cacb6c4..d3e8807f05 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -6,7 +6,9 @@ use crate::coconut::bandwidth::issuance::{ BandwidthCredentialIssuanceDataVariant, IssuanceBandwidthCredential, }; use crate::coconut::bandwidth::voucher::BandwidthVoucherIssuedData; -use crate::coconut::bandwidth::{bandwidth_voucher_params, CredentialSpendingData, CredentialType}; +use crate::coconut::bandwidth::{ + bandwidth_credential_params, CredentialSpendingData, CredentialType, +}; use crate::error::Error; use nym_coconut_interface::{ prove_bandwidth_credential, Parameters, PrivateAttribute, PublicAttribute, Signature, @@ -122,7 +124,7 @@ impl IssuedBandwidthCredential { &self, verification_key: &VerificationKey, ) -> Result { - let params = bandwidth_voucher_params(); + let params = bandwidth_credential_params(); let verify_credential_request = prove_bandwidth_credential( params, @@ -133,8 +135,10 @@ impl IssuedBandwidthCredential { )?; Ok(CredentialSpendingData { + embedded_private_attributes: IssuanceBandwidthCredential::PRIVATE_ATTRIBUTES as usize, verify_credential_request, public_attributes_plain: self.get_plain_public_attributes(), + typ: self.typ(), }) } } diff --git a/common/credentials/src/coconut/bandwidth/mod.rs b/common/credentials/src/coconut/bandwidth/mod.rs index 84101bc9cc..8e5a7d5631 100644 --- a/common/credentials/src/coconut/bandwidth/mod.rs +++ b/common/credentials/src/coconut/bandwidth/mod.rs @@ -2,7 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use bls12_381::Scalar; -use nym_coconut_interface::{BlindSignRequest, Parameters, VerifyCredentialRequest}; +use nym_coconut_interface::{ + hash_to_scalar, BlindSignRequest, Parameters, VerificationKey, VerifyCredentialRequest, +}; +use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::sync::OnceLock; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -19,12 +22,12 @@ pub const VOUCHER_INFO_TYPE: &str = "BandwidthVoucher"; pub const FREE_PASS_INFO_TYPE: &str = "FreeBandwidthPass"; // works under the assumption of having 4 attributes in the underlying credential(s) -pub fn bandwidth_voucher_params() -> &'static Parameters { +pub fn bandwidth_credential_params() -> &'static Parameters { static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock = OnceLock::new(); BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(IssuanceBandwidthCredential::default_parameters) } -#[derive(Zeroize, ZeroizeOnDrop, Clone, Debug)] +#[derive(Zeroize, ZeroizeOnDrop, Clone, Debug, Serialize, Deserialize)] pub enum CredentialType { Voucher, FreePass, @@ -52,11 +55,39 @@ pub struct CredentialSigningData { pub(crate) blind_sign_request: BlindSignRequest, pub(crate) public_attributes_plain: Vec, + + pub(crate) typ: CredentialType, } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct CredentialSpendingData { + pub(crate) embedded_private_attributes: usize, + pub(crate) verify_credential_request: VerifyCredentialRequest, pub(crate) public_attributes_plain: Vec, + + pub(crate) typ: CredentialType, +} + +impl CredentialSpendingData { + pub fn verify(&self, verification_key: &VerificationKey) -> bool { + let params = bandwidth_credential_params(); + + let hashed_public_attributes = self + .public_attributes_plain + .iter() + .map(hash_to_scalar) + .collect::>(); + + // get references to the attributes + let public_attributes = hashed_public_attributes.iter().collect::>(); + + nym_coconut_interface::verify_credential( + params, + verification_key, + &self.verify_credential_request, + &public_attributes, + ) + } } diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 08379c9017..a2014d8c1a 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -95,10 +95,3 @@ pub(crate) mod scalar_serde_helper { )) } } - -pub(crate) fn make_bincode_serializer() -> impl bincode::Options { - use bincode::Options; - bincode::DefaultOptions::new() - .with_big_endian() - .with_varint_encoding() -} diff --git a/common/credentials/src/lib.rs b/common/credentials/src/lib.rs index 4f923ae059..caa16ee410 100644 --- a/common/credentials/src/lib.rs +++ b/common/credentials/src/lib.rs @@ -4,4 +4,8 @@ pub mod coconut; pub mod error; +pub use coconut::bandwidth::{ + CredentialSigningData, CredentialSpendingData, IssuanceBandwidthCredential, + IssuedBandwidthCredential, +}; pub use coconut::utils::{obtain_aggregate_signature, obtain_aggregate_verification_key}; diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index a2404d6c4b..9bc55978d9 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -9,6 +9,7 @@ pub use types::*; pub mod authentication; pub mod iv; +mod models; pub mod registration; pub mod types; diff --git a/gateway/gateway-requests/src/models.rs b/gateway/gateway-requests/src/models.rs new file mode 100644 index 0000000000..1f155e7711 --- /dev/null +++ b/gateway/gateway-requests/src/models.rs @@ -0,0 +1,30 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::GatewayRequestsError; +use nym_credentials::coconut::bandwidth::CredentialSpendingData; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CredentialSpendingWithEpoch { + /// The cryptographic material required for spending the underlying credential. + pub data: CredentialSpendingData, + + /// The (DKG) epoch id under which the credential has been issued so that the verifier + /// could use correct verification key for validation. + pub epoch_id: u64, +} + +impl CredentialSpendingWithEpoch { + pub fn new(data: CredentialSpendingData, epoch_id: u64) -> Self { + CredentialSpendingWithEpoch { data, epoch_id } + } + + pub fn to_bytes(&self) -> Vec { + todo!() + } + + pub fn try_from_bytes(raw: &[u8]) -> Result { + todo!() + } +} diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 3683144980..0c5542464e 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -3,10 +3,11 @@ use crate::authentication::encrypted_address::EncryptedAddressBytes; use crate::iv::IV; +use crate::models::CredentialSpendingWithEpoch; use crate::registration::handshake::SharedKeys; use crate::{GatewayMacSize, PROTOCOL_VERSION}; use log::error; -use nym_coconut_interface::Credential; +use nym_credentials::coconut::bandwidth::CredentialSpendingData; use nym_crypto::generic_array::typenum::Unsigned; use nym_crypto::hmac::recompute_keyed_hmac_and_verify_tag; use nym_crypto::symmetric::stream_cipher; @@ -16,10 +17,8 @@ use nym_sphinx::params::packet_sizes::PacketSize; use nym_sphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; use nym_sphinx::DestinationAddressBytes; use serde::{Deserialize, Serialize}; -use std::{ - convert::{TryFrom, TryInto}, - fmt::{self, Error, Formatter}, -}; +use std::convert::{TryFrom, TryInto}; +use thiserror::Error; use tungstenite::protocol::Message; #[derive(Serialize, Deserialize, Debug)] @@ -66,53 +65,43 @@ impl TryInto for RegistrationHandshake { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum GatewayRequestsError { + #[error("the request is too short")] TooShortRequest, + + #[error("provided MAC is invalid")] InvalidMac, - IncorrectlyEncodedAddress, + + #[error("address field was incorrectly encoded: {source}")] + IncorrectlyEncodedAddress { + #[from] + source: NymNodeRoutingAddressError, + }, + + #[error("received request had invalid size. (actual: {0}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))", + PacketSize::AckPacket.size(), + PacketSize::RegularPacket.size(), + PacketSize::ExtendedPacket8.size(), + PacketSize::ExtendedPacket16.size(), + PacketSize::ExtendedPacket32.size()) + ] RequestOfInvalidSize(usize), + + #[error("received sphinx packet was malformed")] MalformedSphinxPacket, + + #[error("the received encrypted data was malformed")] MalformedEncryption, + + #[error("provided packet mode is invalid")] InvalidPacketMode, - InvalidMixPacket(MixPacketFormattingError), -} -impl fmt::Display for GatewayRequestsError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - use GatewayRequestsError::*; - match self { - TooShortRequest => write!(f, "the request is too short"), - InvalidMac => write!(f, "provided MAC is invalid"), - IncorrectlyEncodedAddress => write!(f, "address field was incorrectly encoded"), - RequestOfInvalidSize(actual) => - write!( - f, - "received request had invalid size. (actual: {}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))", - actual, PacketSize::AckPacket.size(), PacketSize::RegularPacket.size(), - PacketSize::ExtendedPacket8.size(), PacketSize::ExtendedPacket16.size(), - PacketSize::ExtendedPacket32.size() - ), - MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"), - MalformedEncryption => write!(f, "the received encrypted data was malformed"), - InvalidPacketMode => write!(f, "provided packet mode is invalid"), - InvalidMixPacket(err) => write!(f, "provided mix packet was malformed - {err}") - } - } -} - -impl std::error::Error for GatewayRequestsError {} - -impl From for GatewayRequestsError { - fn from(_: NymNodeRoutingAddressError) -> Self { - GatewayRequestsError::IncorrectlyEncodedAddress - } -} - -impl From for GatewayRequestsError { - fn from(err: MixPacketFormattingError) -> Self { - GatewayRequestsError::InvalidMixPacket(err) - } + #[error("provided mix packet was malformed: {source}")] + InvalidMixPacket { + #[from] + source: MixPacketFormattingError, + }, } #[derive(Serialize, Deserialize, Debug)] @@ -155,11 +144,13 @@ impl ClientControlRequest { } pub fn new_enc_coconut_bandwidth_credential( - credential: &Credential, + credential: CredentialSpendingData, + epoch_id: u64, shared_key: &SharedKeys, iv: IV, ) -> Self { - let serialized_credential = credential.as_bytes(); + let cred = CredentialSpendingWithEpoch::new(credential, epoch_id); + let serialized_credential = cred.to_bytes(); let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); ClientControlRequest::BandwidthCredential { @@ -172,9 +163,9 @@ impl ClientControlRequest { enc_credential: Vec, shared_key: &SharedKeys, iv: IV, - ) -> Result { + ) -> Result { let credential_bytes = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; - Credential::from_bytes(&credential_bytes) + CredentialSpendingWithEpoch::try_from_bytes(&credential_bytes) .map_err(|_| GatewayRequestsError::MalformedEncryption) } } diff --git a/nym-api/src/coconut/dkg/key_derivation.rs b/nym-api/src/coconut/dkg/key_derivation.rs index ca85716d13..db86ec39a5 100644 --- a/nym-api/src/coconut/dkg/key_derivation.rs +++ b/nym-api/src/coconut/dkg/key_derivation.rs @@ -7,7 +7,7 @@ use crate::coconut::dkg::controller::DkgController; use crate::coconut::dkg::state::key_derivation::{DealerRejectionReason, DerivationFailure}; use crate::coconut::error::CoconutError; use crate::coconut::keys::KeyPairWithEpoch; -use crate::coconut::state::bandwidth_voucher_params; +use crate::coconut::state::bandwidth_credential_params; use cosmwasm_std::Addr; use log::debug; use nym_coconut::{check_vk_pairing, Base58, SecretKey, VerificationKey}; @@ -425,7 +425,7 @@ impl DkgController { // we know we had a non-empty map of dealings and thus, at the very least, we must have derived a single secret // (i.e. the x-element) let sk = SecretKey::create_from_raw(derived_x.unwrap(), derived_secrets); - let derived_vk = sk.verification_key(bandwidth_voucher_params()); + let derived_vk = sk.verification_key(bandwidth_credential_params()); // make the key we derived out of the decrypted shares matches the partial key // (cryptographically there shouldn't be any reason for the mismatch, @@ -436,7 +436,7 @@ impl DkgController { .derived_partials_for(receiver_index) .ok_or(KeyDerivationError::NoSelfPartialKey { receiver_index })?; - if !check_vk_pairing(bandwidth_voucher_params(), &derived_partial, &derived_vk) { + if !check_vk_pairing(bandwidth_credential_params(), &derived_partial, &derived_vk) { // can't do anything, we got all dealings, we derived all keys, but somehow they don't match error!("our derived key does not match the expected partials!"); return Ok(Err(DerivationFailure::MismatchedPartialKey)); diff --git a/nym-api/src/coconut/dkg/key_validation.rs b/nym-api/src/coconut/dkg/key_validation.rs index a957db8f23..68068d30b6 100644 --- a/nym-api/src/coconut/dkg/key_validation.rs +++ b/nym-api/src/coconut/dkg/key_validation.rs @@ -3,7 +3,7 @@ use crate::coconut::dkg::controller::DkgController; use crate::coconut::error::CoconutError; -use crate::coconut::state::bandwidth_voucher_params; +use crate::coconut::state::bandwidth_credential_params; use cosmwasm_std::Addr; use cw3::Vote; use nym_coconut::{check_vk_pairing, Base58, VerificationKey}; @@ -119,7 +119,7 @@ impl DkgController { }); }; - if !check_vk_pairing(bandwidth_voucher_params(), &self_derived, &recovered_key) { + if !check_vk_pairing(bandwidth_credential_params(), &self_derived, &recovered_key) { return reject(ShareRejectionReason::InconsistentKeys { epoch_id, owner, diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs index 150ace1864..e5778674e6 100644 --- a/nym-api/src/coconut/helpers.rs +++ b/nym-api/src/coconut/helpers.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::coconut::error::CoconutError; -use crate::coconut::state::bandwidth_voucher_params; +use crate::coconut::state::bandwidth_credential_params; use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut::{BlindedSignature, SecretKey}; use nym_validator_client::nyxd::error::NyxdError::AbciError; @@ -29,7 +29,7 @@ pub(crate) fn blind_sign( let attributes_ref = public_attributes.iter().collect::>(); Ok(nym_coconut_interface::blind_sign( - bandwidth_voucher_params(), + bandwidth_credential_params(), signing_key, &request.inner_sign_request, &attributes_ref, diff --git a/nym-api/src/coconut/keys/persistence.rs b/nym-api/src/coconut/keys/persistence.rs index b3c9c330ce..4a67876036 100644 --- a/nym-api/src/coconut/keys/persistence.rs +++ b/nym-api/src/coconut/keys/persistence.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::coconut::keys::KeyPairWithEpoch; -use crate::coconut::state::bandwidth_voucher_params; +use crate::coconut::state::bandwidth_credential_params; use nym_coconut::{CoconutError, KeyPair, SecretKey}; use nym_coconut_dkg_common::types::EpochId; use nym_pemstore::traits::PemStorableKey; @@ -33,7 +33,7 @@ impl PemStorableKey for KeyPairWithEpoch { ]); let sk = SecretKey::from_bytes(&bytes[mem::size_of::()..])?; - let vk = sk.verification_key(bandwidth_voucher_params()); + let vk = sk.verification_key(bandwidth_credential_params()); Ok(KeyPairWithEpoch { keys: KeyPair::from_keys(sk, vk), diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs index 72fad9b27d..6bf4159c2e 100644 --- a/nym-api/src/coconut/state.rs +++ b/nym-api/src/coconut/state.rs @@ -16,7 +16,7 @@ use nym_crypto::asymmetric::identity; use nym_validator_client::nyxd::{Hash, TxResponse}; use std::sync::Arc; -pub use nym_credentials::coconut::bandwidth::bandwidth_voucher_params; +pub use nym_credentials::coconut::bandwidth::bandwidth_credential_params; pub struct State { pub(crate) client: Arc, diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 9083541347..a745917381 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3708,6 +3708,7 @@ name = "nym-bandwidth-controller" version = "0.1.0" dependencies = [ "bip39", + "log", "nym-coconut-interface", "nym-credential-storage", "nym-credentials", @@ -3930,6 +3931,7 @@ dependencies = [ name = "nym-credentials" version = "0.1.0" dependencies = [ + "bincode", "bls12_381", "cosmrs", "log", @@ -4040,8 +4042,8 @@ dependencies = [ "gloo-utils", "log", "nym-bandwidth-controller", - "nym-coconut-interface", "nym-credential-storage", + "nym-credentials", "nym-crypto", "nym-gateway-requests", "nym-network-defaults", diff --git a/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs b/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs index 251489b3b5..6a02ca52a5 100644 --- a/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs +++ b/nym-validator-rewarder/src/rewarder/credential_issuance/monitor.rs @@ -14,7 +14,7 @@ use nym_coconut::{ hash_to_scalar, verify_partial_blind_signature, Base58, G1Projective, VerificationKey, }; use nym_coconut_dkg_common::types::EpochId; -use nym_credentials::coconut::bandwidth::bandwidth_voucher_params; +use nym_credentials::coconut::bandwidth::bandwidth_credential_params; use nym_task::TaskClient; use nym_validator_client::nym_api::{IssuedCredential, IssuedCredentialBody, NymApiClientExt}; use nym_validator_client::nyxd::Hash; @@ -154,7 +154,7 @@ impl CredentialIssuanceMonitor { // actually do verify the credential now if !verify_partial_blind_signature( - bandwidth_voucher_params(), + bandwidth_credential_params(), &public_attribute_commitments, &attributes_refs, &credential.blinded_partial_credential, From 9a0cbf507299362c3e610f0ad9ffb3a413e4313b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 7 Feb 2024 16:06:13 +0000 Subject: [PATCH 05/49] wip in removing the Credential type for more strongly typed alternative --- common/bandwidth-controller/src/lib.rs | 2 +- .../client-libs/gateway-client/src/client.rs | 2 +- common/coconut-interface/Cargo.toml | 2 +- common/credential-utils/Cargo.toml | 1 + common/credentials/Cargo.toml | 4 +- .../credentials/src/coconut/bandwidth/mod.rs | 39 ++++++++--- common/credentials/src/coconut/credential.rs | 6 +- gateway/gateway-requests/src/lib.rs | 2 +- gateway/gateway-requests/src/models.rs | 18 +++++ gateway/src/node/client_handling/bandwidth.rs | 38 +++++++++- gateway/src/node/client_handling/mod.rs | 6 +- .../connection_handler/authenticated.rs | 69 ++++++++++++------- .../websocket/connection_handler/coconut.rs | 36 ++++++---- nym-api/nym-api-requests/Cargo.toml | 2 + .../nym-api-requests/src/coconut/models.rs | 27 +++++--- nym-connect/desktop/Cargo.lock | 1 + nym-wallet/Cargo.lock | 1 + 17 files changed, 186 insertions(+), 70 deletions(-) diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index d6f5dfb731..6d78bd7401 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -56,7 +56,7 @@ impl BandwidthController { Ok(obtain_aggregate_verification_key(&coconut_api_clients).await?) } - pub async fn prepare_coconut_credential( + pub async fn prepare_bandwidth_credential( &self, ) -> Result where diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 08d25beed7..b1ebb23fa4 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -574,7 +574,7 @@ impl GatewayClient { .bandwidth_controller .as_ref() .unwrap() - .prepare_coconut_credential() + .prepare_bandwidth_credential() .await?; self.claim_coconut_bandwidth(prepared_credential.data, prepared_credential.epoch_id) diff --git a/common/coconut-interface/Cargo.toml b/common/coconut-interface/Cargo.toml index dc81f4847f..a85115d95f 100644 --- a/common/coconut-interface/Cargo.toml +++ b/common/coconut-interface/Cargo.toml @@ -11,4 +11,4 @@ getset = "0.1.1" serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } -nym-coconut = {path = "../nymcoconut" } +nym-coconut = { path = "../nymcoconut" } diff --git a/common/credential-utils/Cargo.toml b/common/credential-utils/Cargo.toml index 1b5ebfd5bd..232c9cdb1f 100644 --- a/common/credential-utils/Cargo.toml +++ b/common/credential-utils/Cargo.toml @@ -12,6 +12,7 @@ thiserror = { workspace = true } tokio = { workspace = true } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } +nym-coconut = { path = "../nymcoconut" } nym-credentials = { path = "../../common/credentials" } nym-credential-storage = { path = "../../common/credential-storage" } nym-validator-client = { path = "../../common/client-libs/validator-client" } diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index f08a3a5cd5..55894c3754 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -17,10 +17,10 @@ zeroize = { workspace = true } # I guess temporarily until we get serde support in coconut up and running nym-coconut-interface = { path = "../coconut-interface" } -nym-crypto = { path = "../crypto", features = ["rand", "asymmetric"] } +nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "serde"] } nym-api-requests = { path = "../../nym-api/nym-api-requests" } nym-validator-client = { path = "../client-libs/validator-client", default-features = false } -serde = { version = "1.0.189", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } [dev-dependencies] rand = "0.7.3" diff --git a/common/credentials/src/coconut/bandwidth/mod.rs b/common/credentials/src/coconut/bandwidth/mod.rs index 8e5a7d5631..8fe23a444b 100644 --- a/common/credentials/src/coconut/bandwidth/mod.rs +++ b/common/credentials/src/coconut/bandwidth/mod.rs @@ -27,7 +27,7 @@ pub fn bandwidth_credential_params() -> &'static Parameters { BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(IssuanceBandwidthCredential::default_parameters) } -#[derive(Zeroize, ZeroizeOnDrop, Clone, Debug, Serialize, Deserialize)] +#[derive(Zeroize, Copy, Clone, Debug, Serialize, Deserialize)] pub enum CredentialType { Voucher, FreePass, @@ -37,6 +37,10 @@ impl CredentialType { pub fn is_free_pass(&self) -> bool { matches!(self, CredentialType::FreePass) } + + pub fn is_voucher(&self) -> bool { + matches!(self, CredentialType::Voucher) + } } impl Display for CredentialType { @@ -50,24 +54,24 @@ impl Display for CredentialType { #[derive(Debug, Clone)] pub struct CredentialSigningData { - pub(crate) pedersen_commitments_openings: Vec, + pub pedersen_commitments_openings: Vec, - pub(crate) blind_sign_request: BlindSignRequest, + pub blind_sign_request: BlindSignRequest, - pub(crate) public_attributes_plain: Vec, + pub public_attributes_plain: Vec, - pub(crate) typ: CredentialType, + pub typ: CredentialType, } #[derive(Debug, Serialize, Deserialize)] pub struct CredentialSpendingData { - pub(crate) embedded_private_attributes: usize, + pub embedded_private_attributes: usize, - pub(crate) verify_credential_request: VerifyCredentialRequest, + pub verify_credential_request: VerifyCredentialRequest, - pub(crate) public_attributes_plain: Vec, + pub public_attributes_plain: Vec, - pub(crate) typ: CredentialType, + pub typ: CredentialType, } impl CredentialSpendingData { @@ -90,4 +94,21 @@ impl CredentialSpendingData { &public_attributes, ) } + + pub fn validate_type_attribute(&self) -> bool { + // the first attribute is variant specific bandwidth encoding, the second one should be the type + let Some(type_plain) = self.public_attributes_plain.get(1) else { + return false; + }; + + match self.typ { + CredentialType::Voucher => type_plain == VOUCHER_INFO_TYPE, + CredentialType::FreePass => type_plain == FREE_PASS_INFO_TYPE, + } + } + + pub fn get_bandwidth_attribute(&self) -> Option<&String> { + // the first attribute is variant specific bandwidth encoding, the second one should be the type + self.public_attributes_plain.first() + } } diff --git a/common/credentials/src/coconut/credential.rs b/common/credentials/src/coconut/credential.rs index 6863200e17..446682a398 100644 --- a/common/credentials/src/coconut/credential.rs +++ b/common/credentials/src/coconut/credential.rs @@ -2,9 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 pub trait NymCredential { - fn prove_credential(&self); - - // pub attr - // hashed - // private + fn prove_credential(&self) -> Result<(), ()>; } diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index 9bc55978d9..9bb25dc45c 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -9,7 +9,7 @@ pub use types::*; pub mod authentication; pub mod iv; -mod models; +pub mod models; pub mod registration; pub mod types; diff --git a/gateway/gateway-requests/src/models.rs b/gateway/gateway-requests/src/models.rs index 1f155e7711..43881d3ceb 100644 --- a/gateway/gateway-requests/src/models.rs +++ b/gateway/gateway-requests/src/models.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::GatewayRequestsError; +use nym_coconut_interface::CoconutError; use nym_credentials::coconut::bandwidth::CredentialSpendingData; use serde::{Deserialize, Serialize}; @@ -20,6 +21,23 @@ impl CredentialSpendingWithEpoch { CredentialSpendingWithEpoch { data, epoch_id } } + pub fn matches_blinded_serial_number( + &self, + blinded_serial_number_bs58: &str, + ) -> Result { + self.data + .verify_credential_request + .has_blinded_serial_number(blinded_serial_number_bs58) + } + + pub fn unchecked_voucher_value(&self) -> u64 { + self.data + .get_bandwidth_attribute() + .expect("failed to extract bandwidth attribute") + .parse() + .expect("failed to parse voucher value") + } + pub fn to_bytes(&self) -> Vec { todo!() } diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index e1b4d831e0..d5ea02b2d2 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,13 +1,49 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use log::error; use nym_coconut_interface::Credential; +use nym_credentials::coconut::bandwidth::CredentialType; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum BandwidthError {} pub struct Bandwidth { value: u64, } impl Bandwidth { + pub const fn new(value: u64) -> Bandwidth { + Bandwidth { value } + } + + pub fn try_from_raw_value(value: &String, typ: CredentialType) -> Result { + // let bandwidth_value = match credential.data.typ { + // CredentialType::Voucher => { + // todo!() + // } + // CredentialType::FreePass => { + // error!("unimplemented handling of free pass credential"); + // return Err(()); + // } + // }; + + /* + if bandwidth_value > i64::MAX as u64 { + // note that this would have represented more than 1 exabyte, + // which is like 125,000 worth of hard drives so I don't think we have + // to worry about it for now... + warn!("Somehow we received bandwidth value higher than 9223372036854775807. We don't really want to deal with this now"); + return Err(RequestHandlingError::UnsupportedBandwidthValue( + bandwidth_value, + )); + } + */ + + todo!() + } + pub fn value(&self) -> u64 { self.value } diff --git a/gateway/src/node/client_handling/mod.rs b/gateway/src/node/client_handling/mod.rs index ff4691e256..ae26cc558d 100644 --- a/gateway/src/node/client_handling/mod.rs +++ b/gateway/src/node/client_handling/mod.rs @@ -1,9 +1,11 @@ -// Copyright 2020 - Nym Technologies SA +// Copyright 2020-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use crate::node::client_handling::bandwidth::Bandwidth; + pub(crate) mod active_clients; mod bandwidth; pub(crate) mod embedded_network_requester; pub(crate) mod websocket; -pub(crate) const FREE_TESTNET_BANDWIDTH_VALUE: i64 = 64 * 1024 * 1024 * 1024; // 64GB +pub(crate) const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new(64 * 1024 * 1024 * 1024); // 64GB diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index b97bae206f..548037e9d3 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -19,8 +19,11 @@ use thiserror::Error; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; +use nym_coconut_interface::CoconutError; +use nym_credentials::coconut::bandwidth::CredentialType; use std::{convert::TryFrom, process, time::Duration}; +use crate::node::client_handling::bandwidth::BandwidthError; use crate::node::{ client_handling::{ bandwidth::Bandwidth, @@ -76,11 +79,20 @@ pub(crate) enum RequestHandlingError { #[error("Coconut interface error - {0}")] CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), + #[error("coconut failure: {0}")] + CoconutError(#[from] CoconutError), + #[error("coconut api query failure: {0}")] CoconutApiError(#[from] CoconutApiError), #[error("Credential error - {0}")] CredentialError(#[from] nym_credentials::error::Error), + + #[error("failed to recover bandwidth value: {0}")] + BandwidthRecoveryFailure(#[from] BandwidthError), + + #[error("free pass credentials haven't been implemented yet")] + UnimplementedFreePass, } impl RequestHandlingError { @@ -177,10 +189,10 @@ where /// # Arguments /// /// * `amount`: amount to increase the available bandwidth by. - async fn increase_bandwidth(&self, amount: i64) -> Result<(), RequestHandlingError> { + async fn increase_bandwidth(&self, bandwidth: Bandwidth) -> Result<(), RequestHandlingError> { self.inner .storage - .increase_bandwidth(self.client.address, amount) + .increase_bandwidth(self.client.address, bandwidth.value() as i64) .await?; Ok(()) } @@ -232,40 +244,45 @@ where let aggregated_verification_key = self .inner .coconut_verifier - .verification_key(*credential.epoch_id()) + .verification_key(credential.epoch_id) .await?; - if !credential.verify(&aggregated_verification_key) { + if !credential.data.validate_type_attribute() { + unimplemented!() + } + + let Some(bandwidth_attribute) = credential.data.get_bandwidth_attribute() else { + unimplemented!() + }; + + let bandwidth = Bandwidth::try_from_raw_value(bandwidth_attribute, credential.data.typ)?; + + if !credential.data.verify(&aggregated_verification_key) { return Err(RequestHandlingError::InvalidBandwidthCredential( String::from("credential failed to verify on gateway"), )); } - let api_clients = self - .inner - .coconut_verifier - .api_clients(*credential.epoch_id()) - .await?; + match credential.data.typ { + CredentialType::Voucher => { + let api_clients = self + .inner + .coconut_verifier + .api_clients(credential.epoch_id) + .await?; - self.inner - .coconut_verifier - .release_funds(&api_clients, &credential) - .await?; - - let bandwidth = Bandwidth::from(credential); - let bandwidth_value = bandwidth.value(); - - if bandwidth_value > i64::MAX as u64 { - // note that this would have represented more than 1 exabyte, - // which is like 125,000 worth of hard drives so I don't think we have - // to worry about it for now... - warn!("Somehow we received bandwidth value higher than 9223372036854775807. We don't really want to deal with this now"); - return Err(RequestHandlingError::UnsupportedBandwidthValue( - bandwidth_value, - )); + self.inner + .coconut_verifier + .release_bandwidth_voucher_funds(&api_clients, credential) + .await?; + } + CredentialType::FreePass => { + error!("unimplemented handling of free pass credential"); + return Err(RequestHandlingError::UnimplementedFreePass); + } } - self.increase_bandwidth(bandwidth_value as i64).await?; + self.increase_bandwidth(bandwidth).await?; let available_total = self.get_available_bandwidth().await?; Ok(ServerResponse::Bandwidth { available_total }) diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index 89711d31e7..e62d6afa5a 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -4,6 +4,7 @@ use super::authenticated::RequestHandlingError; use log::*; use nym_coconut_interface::{Credential, VerificationKey}; +use nym_gateway_requests::models::CredentialSpendingWithEpoch; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::{MultisigQueryClient, NymContractsProvider}; @@ -176,21 +177,31 @@ impl CoconutVerifier { Ok(all_coconut_api_clients(self.nyxd_client.read().await.deref(), epoch_id).await?) } - pub async fn release_funds( + pub async fn release_bandwidth_voucher_funds( &self, api_clients: &[CoconutApiClient], - credential: &Credential, + credential: CredentialSpendingWithEpoch, ) -> Result<(), RequestHandlingError> { + if !credential.data.typ.is_voucher() { + unimplemented!() + } + + // safety: the voucher funds are released after the credential has already been verified locally + // and the underlying bandwidth value has been extracted, so the below MUST succeed + let voucher_amount = credential.unchecked_voucher_value() as u128; + + let blinded_serial_number = credential + .data + .verify_credential_request + .blinded_serial_number_bs58(); + let res = self .nyxd_client .write() .await .spend_credential( - Coin::new( - credential.voucher_value().into(), - self.mix_denom_base.clone(), - ), - credential.blinded_serial_number(), + Coin::new(voucher_amount, &self.mix_denom_base), + blinded_serial_number, self.address.to_string(), None, ) @@ -211,27 +222,28 @@ impl CoconutVerifier { .await .query_proposal(proposal_id) .await?; - if !credential.has_blinded_serial_number(&proposal.description)? { + if !credential.matches_blinded_serial_number(&proposal.description)? { return Err(RequestHandlingError::ProposalIdError { reason: String::from("proposal has different serial number"), }); } let req = nym_api_requests::coconut::VerifyCredentialBody::new( - credential.clone(), + credential, proposal_id, self.address.clone(), ); for client in api_clients { let ret = client.api_client.verify_bandwidth_credential(&req).await; + let client_url = client.api_client.nym_api.current_url(); match ret { Ok(res) => { if !res.verification_result { - debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.api_client.nym_api.current_url()); + warn!("Validator at {client_url} didn't accept the credential. It will probably vote No on the spending proposal"); } } - Err(e) => { - warn!("Validator {} could not be reached. There might be a problem with the coconut endpoint - {:?}", client.api_client.nym_api.current_url(), e); + Err(err) => { + warn!("Validator at {client_url} could not be reached. There might be a problem with the coconut endpoint: {err}"); } } } diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 1befc394cf..aaf7309fcf 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -16,7 +16,9 @@ serde = { workspace = true, features = ["derive"] } ts-rs = { workspace = true, optional = true } tendermint = { workspace = true } +nym-coconut = { path = "../../common/nymcoconut" } nym-coconut-interface = { path = "../../common/coconut-interface" } +#nym-credentials = { path = "../../common/credentials" } nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"]} nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index aa6e82ef00..15efe1a9f6 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -1,11 +1,11 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::coconut::helpers::issued_credential_plaintext; use cosmrs::AccountId; -use nym_coconut_interface::{ - error::CoconutInterfaceError, hash_to_scalar, Attribute, BlindSignRequest, BlindedSignature, - Bytable, Credential, VerificationKey, +use nym_coconut::{ + hash_to_scalar, Attribute, BlindSignRequest, BlindedSignature, Bytable, CoconutError, + Signature, VerificationKey, }; use nym_crypto::asymmetric::identity; use serde::{Deserialize, Serialize}; @@ -14,21 +14,30 @@ use tendermint::hash::Hash; #[derive(Serialize, Deserialize)] pub struct VerifyCredentialBody { - pub credential: Credential, + /// The cryptographic material required for spending the underlying credential. + pub credential_data: (), + /// The (DKG) epoch id under which the credential has been issued so that the verifier + /// could use correct verification key for validation. + pub epoch_id: u64, + + /// Multisig proposal for releasing funds for the provided bandwidth credential pub proposal_id: u64, + /// Cosmos address of the spender of the credential pub gateway_cosmos_addr: AccountId, } impl VerifyCredentialBody { pub fn new( - credential: Credential, + credential_data: (), + epoch_id: u64, proposal_id: u64, gateway_cosmos_addr: AccountId, ) -> VerifyCredentialBody { VerifyCredentialBody { - credential, + credential_data, + epoch_id, proposal_id, gateway_cosmos_addr, } @@ -115,7 +124,7 @@ impl BlindedSignatureResponse { bs58::encode(&self.to_bytes()).into_string() } - pub fn from_base58_string>(val: I) -> Result { + pub fn from_base58_string>(val: I) -> Result { let bytes = bs58::decode(val).into_vec()?; Self::from_bytes(&bytes) } @@ -124,7 +133,7 @@ impl BlindedSignatureResponse { self.blinded_signature.to_byte_vec() } - pub fn from_bytes(bytes: &[u8]) -> Result { + pub fn from_bytes(bytes: &[u8]) -> Result { Ok(BlindedSignatureResponse { blinded_signature: BlindedSignature::from_bytes(bytes)?, }) diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index a745917381..555390c706 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3694,6 +3694,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "getset", + "nym-coconut", "nym-coconut-interface", "nym-crypto", "nym-mixnet-contract-common", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index a197456fb9..b5dfb64b97 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3098,6 +3098,7 @@ dependencies = [ "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "cosmwasm-std", "getset", + "nym-coconut", "nym-coconut-interface", "nym-crypto", "nym-mixnet-contract-common", From 675cf3d7daa7fbf966974803ae78a293d1abca00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 7 Feb 2024 17:01:30 +0000 Subject: [PATCH 06/49] removed usage of coconut-interface crate --- Cargo.lock | 38 ++-- Cargo.toml | 2 +- clients/native/Cargo.toml | 1 - clients/socks5/Cargo.toml | 1 - common/bandwidth-controller/Cargo.toml | 3 +- .../bandwidth-controller/src/acquire/mod.rs | 8 +- common/bandwidth-controller/src/error.rs | 2 +- common/bandwidth-controller/src/lib.rs | 2 +- .../client-libs/validator-client/Cargo.toml | 2 +- .../validator-client/src/coconut/mod.rs | 2 +- .../validator-client/src/nyxd/error.rs | 3 - common/coconut-interface/Cargo.toml | 14 -- common/coconut-interface/src/error.rs | 17 -- common/coconut-interface/src/lib.rs | 199 ------------------ common/credentials-interface/Cargo.toml | 17 ++ common/credentials-interface/src/lib.rs | 109 ++++++++++ common/credentials/Cargo.toml | 2 +- .../src/coconut/bandwidth/freepass.rs | 2 +- .../src/coconut/bandwidth/issuance.rs | 2 +- .../src/coconut/bandwidth/issued.rs | 2 +- .../credentials/src/coconut/bandwidth/mod.rs | 99 +-------- .../src/coconut/bandwidth/voucher.rs | 2 +- common/credentials/src/coconut/credential.rs | 6 - common/credentials/src/coconut/mod.rs | 1 - common/credentials/src/coconut/utils.rs | 2 +- common/credentials/src/error.rs | 2 +- common/types/Cargo.toml | 1 - gateway/Cargo.toml | 2 +- gateway/gateway-requests/Cargo.toml | 2 +- gateway/gateway-requests/src/models.rs | 2 +- gateway/src/node/client_handling/bandwidth.rs | 19 +- .../connection_handler/authenticated.rs | 48 ++--- .../websocket/connection_handler/coconut.rs | 5 +- nym-api/Cargo.toml | 1 - nym-api/nym-api-requests/Cargo.toml | 4 +- .../nym-api-requests/src/coconut/helpers.rs | 2 +- .../nym-api-requests/src/coconut/models.rs | 10 +- nym-api/src/coconut/api_routes/mod.rs | 35 +-- nym-api/src/coconut/comm.rs | 2 +- nym-api/src/coconut/dkg/key_derivation.rs | 2 +- nym-api/src/coconut/error.rs | 3 - nym-api/src/coconut/helpers.rs | 2 +- nym-api/src/coconut/keys/mod.rs | 4 +- nym-api/src/coconut/state.rs | 2 +- nym-connect/desktop/Cargo.lock | 32 ++- nym-wallet/Cargo.lock | 27 +-- nym-wallet/src-tauri/Cargo.toml | 1 - 47 files changed, 260 insertions(+), 486 deletions(-) delete mode 100644 common/coconut-interface/Cargo.toml delete mode 100644 common/coconut-interface/src/error.rs delete mode 100644 common/coconut-interface/src/lib.rs create mode 100644 common/credentials-interface/Cargo.toml create mode 100644 common/credentials-interface/src/lib.rs delete mode 100644 common/credentials/src/coconut/credential.rs diff --git a/Cargo.lock b/Cargo.lock index 468a7c6790..eb1d3f96cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4945,7 +4945,6 @@ dependencies = [ "nym-coconut", "nym-coconut-bandwidth-contract-common", "nym-coconut-dkg-common", - "nym-coconut-interface", "nym-config", "nym-contracts-common", "nym-credential-storage", @@ -5000,7 +4999,7 @@ dependencies = [ "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "cosmwasm-std", "getset", - "nym-coconut-interface", + "nym-credentials-interface", "nym-crypto", "nym-mixnet-contract-common", "nym-node-requests", @@ -5016,9 +5015,10 @@ version = "0.1.0" dependencies = [ "bip39", "log", - "nym-coconut-interface", + "nym-coconut", "nym-credential-storage", "nym-credentials", + "nym-credentials-interface", "nym-crypto", "nym-network-defaults", "nym-validator-client", @@ -5156,7 +5156,6 @@ dependencies = [ "nym-bin-common", "nym-client-core", "nym-client-websocket-requests", - "nym-coconut-interface", "nym-config", "nym-credential-storage", "nym-credentials", @@ -5309,17 +5308,6 @@ dependencies = [ "nym-multisig-contract-common", ] -[[package]] -name = "nym-coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58 0.4.0", - "getset", - "nym-coconut", - "serde", - "thiserror", -] - [[package]] name = "nym-config" version = "0.1.0" @@ -5364,6 +5352,7 @@ dependencies = [ "log", "nym-bandwidth-controller", "nym-client-core", + "nym-coconut", "nym-config", "nym-credential-storage", "nym-credentials", @@ -5381,7 +5370,7 @@ dependencies = [ "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "log", "nym-api-requests", - "nym-coconut-interface", + "nym-credentials-interface", "nym-crypto", "nym-validator-client", "rand 0.7.3", @@ -5391,6 +5380,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-credentials-interface" +version = "0.1.0" +dependencies = [ + "bls12_381", + "nym-coconut", + "serde", +] + [[package]] name = "nym-crypto" version = "0.4.0" @@ -5518,9 +5516,9 @@ dependencies = [ "log", "nym-api-requests", "nym-bin-common", - "nym-coconut-interface", "nym-config", "nym-credentials", + "nym-credentials-interface", "nym-crypto", "nym-gateway-requests", "nym-ip-packet-router", @@ -5594,8 +5592,8 @@ dependencies = [ "futures", "generic-array 0.14.7", "log", - "nym-coconut-interface", "nym-credentials", + "nym-credentials-interface", "nym-crypto", "nym-pemstore", "nym-sphinx", @@ -6138,7 +6136,6 @@ dependencies = [ "log", "nym-bin-common", "nym-client-core", - "nym-coconut-interface", "nym-config", "nym-credential-storage", "nym-credentials", @@ -6484,7 +6481,6 @@ dependencies = [ "hmac 0.12.1", "itertools 0.11.0", "log", - "nym-coconut-interface", "nym-config", "nym-crypto", "nym-mixnet-contract-common", @@ -6526,9 +6522,9 @@ dependencies = [ "itertools 0.10.5", "log", "nym-api-requests", + "nym-coconut", "nym-coconut-bandwidth-contract-common", "nym-coconut-dkg-common", - "nym-coconut-interface", "nym-config", "nym-contracts-common", "nym-ephemera-common", diff --git a/Cargo.toml b/Cargo.toml index 305be5b37b..c92723a54c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ members = [ "common/client-libs/gateway-client", "common/client-libs/mixnet-client", "common/client-libs/validator-client", - "common/coconut-interface", "common/commands", "common/config", "common/cosmwasm-smart-contracts/coconut-bandwidth-contract", @@ -43,6 +42,7 @@ members = [ "common/credential-storage", "common/credentials", "common/credential-utils", + "common/credentials-interface", "common/crypto", "common/dkg", "common/execute", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 0c5d4b5fd1..076b7128c8 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -38,7 +38,6 @@ tokio-tungstenite = { workspace = true } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] } nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] } -nym-coconut-interface = { path = "../../common/coconut-interface" } nym-config = { path = "../../common/config" } nym-credential-storage = { path = "../../common/credential-storage" } nym-credentials = { path = "../../common/credentials" } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 72a94c4c2d..89edffb4fc 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -23,7 +23,6 @@ url = { workspace = true } # internal nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] } nym-client-core = { path = "../../common/client-core", features = ["fs-surb-storage", "cli"] } -nym-coconut-interface = { path = "../../common/coconut-interface" } nym-config = { path = "../../common/config" } nym-credentials = { path = "../../common/credentials" } nym-crypto = { path = "../../common/crypto" } diff --git a/common/bandwidth-controller/Cargo.toml b/common/bandwidth-controller/Cargo.toml index 116f8d20c4..284ccbfc77 100644 --- a/common/bandwidth-controller/Cargo.toml +++ b/common/bandwidth-controller/Cargo.toml @@ -14,9 +14,10 @@ thiserror = { workspace = true } url = { workspace = true } zeroize = { workspace = true } -nym-coconut-interface = { path = "../coconut-interface" } +nym-coconut = { path = "../nymcoconut" } nym-credential-storage = { path = "../credential-storage" } nym-credentials = { path = "../credentials" } +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 } diff --git a/common/bandwidth-controller/src/acquire/mod.rs b/common/bandwidth-controller/src/acquire/mod.rs index 9bf1f371a3..5b842fc775 100644 --- a/common/bandwidth-controller/src/acquire/mod.rs +++ b/common/bandwidth-controller/src/acquire/mod.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BandwidthControllerError; -use nym_coconut_interface::Base58; +use nym_coconut::Base58; use nym_credential_storage::storage::Storage; -use nym_credentials::coconut::bandwidth::{IssuanceBandwidthCredential, VOUCHER_INFO_TYPE}; +use nym_credentials::coconut::bandwidth::{CredentialType, IssuanceBandwidthCredential}; use nym_credentials::coconut::utils::obtain_aggregate_signature; use nym_crypto::asymmetric::{encryption, identity}; use nym_validator_client::coconut::all_coconut_api_clients; @@ -27,7 +27,7 @@ where let tx_hash = client .deposit( amount.clone(), - VOUCHER_INFO_TYPE.to_string(), + CredentialType::Voucher.to_string(), signing_key.public_key().to_base58_string(), encryption_key.public_key().to_base58_string(), None, @@ -73,7 +73,7 @@ where storage .insert_coconut_credential( voucher_value, - VOUCHER_INFO_TYPE.to_string(), + CredentialType::Voucher.to_string(), state.voucher.get_private_attributes()[0].to_bs58(), state.voucher.get_private_attributes()[1].to_bs58(), signature.to_bs58(), diff --git a/common/bandwidth-controller/src/error.rs b/common/bandwidth-controller/src/error.rs index 5f464c82e5..7b4e787cb5 100644 --- a/common/bandwidth-controller/src/error.rs +++ b/common/bandwidth-controller/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_coconut_interface::CoconutError; +use nym_coconut::CoconutError; use nym_credential_storage::error::StorageError; use nym_credentials::error::Error as CredentialsError; use nym_crypto::asymmetric::encryption::KeyRecoveryError; diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index 6d78bd7401..2902d93451 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -4,11 +4,11 @@ use crate::error::BandwidthControllerError; use crate::utils::stored_credential_to_issued_bandwidth; use log::{error, warn}; -use nym_coconut_interface::VerificationKey; use nym_credential_storage::error::StorageError; use nym_credential_storage::storage::Storage; use nym_credentials::coconut::bandwidth::CredentialSpendingData; use nym_credentials::coconut::utils::obtain_aggregate_verification_key; +use nym_credentials_interface::VerificationKey; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 5325428da2..3a0b2340d9 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -32,7 +32,7 @@ url = { workspace = true, features = ["serde"] } tokio = { workspace = true, features = ["sync", "time"] } futures = { workspace = true } -nym-coconut-interface = { path = "../../coconut-interface" } +nym-coconut = { path = "../../nymcoconut" } nym-network-defaults = { path = "../../network-defaults" } nym-api-requests = { path = "../../../nym-api/nym-api-requests" } diff --git a/common/client-libs/validator-client/src/coconut/mod.rs b/common/client-libs/validator-client/src/coconut/mod.rs index 97ac95cad7..66f5d0e05a 100644 --- a/common/client-libs/validator-client/src/coconut/mod.rs +++ b/common/client-libs/validator-client/src/coconut/mod.rs @@ -4,9 +4,9 @@ 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_coconut_interface::{Base58, CoconutError, VerificationKey}; use thiserror::Error; use url::Url; diff --git a/common/client-libs/validator-client/src/nyxd/error.rs b/common/client-libs/validator-client/src/nyxd/error.rs index e247dff6a1..905d484b72 100644 --- a/common/client-libs/validator-client/src/nyxd/error.rs +++ b/common/client-libs/validator-client/src/nyxd/error.rs @@ -140,9 +140,6 @@ pub enum NyxdError { #[error("Cosmwasm std error: {0}")] CosmwasmStdError(#[from] cosmwasm_std::StdError), - #[error("Coconut interface error: {0}")] - CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), - #[error("Account had an unexpected bech32 prefix. Expected: {expected}, got: {got}")] UnexpectedBech32Prefix { got: String, expected: String }, } diff --git a/common/coconut-interface/Cargo.toml b/common/coconut-interface/Cargo.toml deleted file mode 100644 index a85115d95f..0000000000 --- a/common/coconut-interface/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "nym-coconut-interface" -version = "0.1.0" -edition = "2021" -description = "Crutch library until there is proper SerDe support for coconut structs" -license.workspace = true - -[dependencies] -bs58 = "0.4.0" -getset = "0.1.1" -serde = { workspace = true, features = ["derive"] } -thiserror = { workspace = true } - -nym-coconut = { path = "../nymcoconut" } diff --git a/common/coconut-interface/src/error.rs b/common/coconut-interface/src/error.rs deleted file mode 100644 index e096ee34f8..0000000000 --- a/common/coconut-interface/src/error.rs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use nym_coconut::CoconutError; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum CoconutInterfaceError { - #[error("not enough bytes: {0} received, minimum {1} required")] - InvalidByteLength(usize, usize), - - #[error("Could not decode base 58 string - {0}")] - MalformedString(#[from] bs58::decode::Error), - - #[error("Coconut error - {0}")] - CoconutError(#[from] CoconutError), -} diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs deleted file mode 100644 index 4ac0706463..0000000000 --- a/common/coconut-interface/src/lib.rs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2021-2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use error::CoconutInterfaceError; -use getset::{CopyGetters, Getters}; -use serde::{Deserialize, Serialize}; - -pub mod error; - -// We list these explicity instead of glob export due to shadowing warnings with the pub tests -// module. -pub use nym_coconut::{ - aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, - prepare_blind_sign, prove_bandwidth_credential, verify_credential, Attribute, Base58, - BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, - PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey, - VerifyCredentialRequest, -}; - -// TODO: maybe just remove this sucker? -#[deprecated] -#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)] -pub struct Credential { - #[getset(get = "pub")] - n_params: u32, - - #[getset(get = "pub")] - theta: VerifyCredentialRequest, - - voucher_value: u64, - - voucher_info: String, - - #[getset(get = "pub")] - epoch_id: u64, -} - -impl Credential { - pub fn new( - n_params: u32, - theta: VerifyCredentialRequest, - voucher_value: u64, - voucher_info: String, - epoch_id: u64, - ) -> Credential { - Credential { - n_params, - theta, - voucher_value, - voucher_info, - epoch_id, - } - } - - pub fn blinded_serial_number(&self) -> String { - self.theta.blinded_serial_number_bs58() - } - - pub fn has_blinded_serial_number( - &self, - blinded_serial_number_bs58: &str, - ) -> Result { - Ok(self - .theta - .has_blinded_serial_number(blinded_serial_number_bs58)?) - } - - pub fn voucher_value(&self) -> u64 { - self.voucher_value - } - - pub fn verify(&self, verification_key: &VerificationKey) -> bool { - let params = Parameters::new(self.n_params).unwrap(); - - let hashed_value = hash_to_scalar(self.voucher_value.to_string()); - let hashed_info = hash_to_scalar(&self.voucher_info); - let public_attributes = &[&hashed_value, &hashed_info]; - - nym_coconut::verify_credential(¶ms, verification_key, &self.theta, public_attributes) - } - - pub fn as_bytes(&self) -> Vec { - let n_params_bytes = self.n_params.to_be_bytes(); - let theta_bytes = self.theta.to_bytes(); - let theta_bytes_len = theta_bytes.len(); - let voucher_value_bytes = self.voucher_value.to_be_bytes(); - let epoch_id_bytes = self.epoch_id.to_be_bytes(); - let voucher_info_bytes = self.voucher_info.as_bytes(); - let voucher_info_len = voucher_info_bytes.len(); - - let mut bytes = Vec::with_capacity(28 + theta_bytes_len + voucher_info_len); - bytes.extend_from_slice(&n_params_bytes); - bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes()); - bytes.extend_from_slice(&theta_bytes); - bytes.extend_from_slice(&voucher_value_bytes); - bytes.extend_from_slice(&epoch_id_bytes); - bytes.extend_from_slice(voucher_info_bytes); - - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() < 28 { - return Err(CoconutError::Deserialization(String::from( - "To few bytes in credential", - ))); - } - let mut four_byte = [0u8; 4]; - let mut eight_byte = [0u8; 8]; - - four_byte.copy_from_slice(&bytes[..4]); - let n_params = u32::from_be_bytes(four_byte); - eight_byte.copy_from_slice(&bytes[4..12]); - let theta_len = u64::from_be_bytes(eight_byte); - if bytes.len() < 28 + theta_len as usize { - return Err(CoconutError::Deserialization(String::from( - "To few bytes in credential", - ))); - } - let theta = VerifyCredentialRequest::from_bytes(&bytes[12..12 + theta_len as usize]) - .map_err(|e| CoconutError::Deserialization(e.to_string()))?; - eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]); - let voucher_value = u64::from_be_bytes(eight_byte); - eight_byte.copy_from_slice(&bytes[20 + theta_len as usize..28 + theta_len as usize]); - let epoch_id = u64::from_be_bytes(eight_byte); - let voucher_info = String::from_utf8(bytes[28 + theta_len as usize..].to_vec()) - .map_err(|e| CoconutError::Deserialization(e.to_string()))?; - - Ok(Credential { - n_params, - theta, - voucher_value, - voucher_info, - epoch_id, - }) - } -} - -impl Bytable for Credential { - fn to_byte_vec(&self) -> Vec { - self.as_bytes() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - Credential::from_bytes(slice) - } -} - -impl Base58 for Credential {} - -#[cfg(test)] -mod tests { - use nym_coconut::{prove_bandwidth_credential, Signature}; - - use super::*; - - #[test] - fn serde_coconut_credential() { - let voucher_value = 1000000u64; - let voucher_info = String::from("BandwidthVoucher"); - let serial_number = - Attribute::try_from_bs58("7Rp3imcuNX3w9se9wm5th8gSvc2czsnMrGsdt5HsrycA").unwrap(); - let binding_number = - Attribute::try_from_bs58("Auf8yVEgyEAWNHaXUZmimS4n9g5YiYnNYqp6F9BtBe9E").unwrap(); - let signature = Signature::try_from_bs58( - "ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc\ - 7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZL\ - ZR9j", - ) - .unwrap(); - let params = Parameters::new(4).unwrap(); - let verification_key = VerificationKey::try_from_bs58("8CFtVVXdwLy4WHMQPE4\ - woe89q3DRHoNxBSchftrEjSBPWA4r4xZv4Y9qSvS5x5bMmFtp7BX6ikECAnuXr5EjXWSsgjirZJmpS5XDUynVfht1cD\ - FWGDvy2XFrRCuoCMotNXi3PoF6wYqdTR9Rqcfoj3i2H5Nid422WBaLtVoC9QNobvpvaqq6vX5PbsSyPayvU8HCXFxM6\ - JjScYpbRTxQtdwefWLrk3LmXyJQBWi7c2VAhSxu9msp7VTBycqdwQNgxHETStZuwXsozxaGQ2KssVUCaaoYPR4g2RqK\ - UAvtWwA7pMiAQNcbkXcbsjCgVjWaCpMWC37XA31cLcFf3zbjHD9e5tXjAcqa4M89fbFhuvvSXxowSAZ5NoWrN32kd5d\ - wxJm1JW3Tt2h6yDDBe84oMy71462dZn7N78DVk2mFNGwBCibrZWA7oUzRBMfYxiQrksoFcou7QfLLd58zoNYmPQPt84\ - 1VpQopEBfdQ7Nf9zoXxBt3zMy7g5NsFGvzh7KTbDUyeeXrdkKJPQBs6dqaizr9sS8CPPmR4uk96vDTRh8CJ5FbSsmb8\ - nP71dRvvwRZJHGzwYirMo6SXS3ZYxFuiA3mkxYuqDHCwkTWDuRCcAaztrDYRZg7VCMo4Q446AaEso5eqpeWpHZQt53E\ - ZRpqmNYKASGwMhTeEHPSLgSmtoAAUcaRWpGRzYfd6kzEma8tdGLwyP4rLXgvSvtDLP37dU7YgF3LEXbGAz57U9ATy46\ - 6sroLpHPdaCWB8RF11wvB6Tu196JnJd2KyQBP1iUWP3rtZs3GhAF1QVcxquh8BqDZzAcpQ6wCS1P9c5GxKgww77FVF5\ - Kp83XtoxSrw3GaYVyKTGxNh3vcKPR31txCjTxPaN2fg7TaPLhoQJX4YaAroFSXqrqbbRsisuHhhCeUP2YwDjHedes9y") - .unwrap(); - let theta = prove_bandwidth_credential( - ¶ms, - &verification_key, - &signature, - &serial_number, - &binding_number, - ) - .unwrap(); - let credential = Credential::new(4, theta, voucher_value, voucher_info, 42); - - let serialized_credential = credential.as_bytes(); - let deserialized_credential = Credential::from_bytes(&serialized_credential).unwrap(); - - assert_eq!(credential, deserialized_credential); - } -} diff --git a/common/credentials-interface/Cargo.toml b/common/credentials-interface/Cargo.toml new file mode 100644 index 0000000000..789552ebff --- /dev/null +++ b/common/credentials-interface/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "nym-credentials-interface" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bls12_381 = { workspace = true, default-features = false } +serde = { workspace = true, features = ["derive"] } + +nym-coconut = { path = "../nymcoconut" } diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs new file mode 100644 index 0000000000..f962bf8c9e --- /dev/null +++ b/common/credentials-interface/src/lib.rs @@ -0,0 +1,109 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bls12_381::Scalar; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +pub use nym_coconut::{ + aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, + prepare_blind_sign, prove_bandwidth_credential, verify_credential, Attribute, Base58, + BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, + PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey, + VerifyCredentialRequest, +}; + +pub const VOUCHER_INFO_TYPE: &str = "BandwidthVoucher"; +pub const FREE_PASS_INFO_TYPE: &str = "FreeBandwidthPass"; + +// pub trait NymCredential { +// fn prove_credential(&self) -> Result<(), ()>; +// } + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum CredentialType { + Voucher, + FreePass, +} + +impl CredentialType { + pub fn validate(&self, type_plain: &str) -> bool { + match self { + CredentialType::Voucher => type_plain == VOUCHER_INFO_TYPE, + CredentialType::FreePass => type_plain == FREE_PASS_INFO_TYPE, + } + } + + pub fn is_free_pass(&self) -> bool { + matches!(self, CredentialType::FreePass) + } + + pub fn is_voucher(&self) -> bool { + matches!(self, CredentialType::Voucher) + } +} + +impl Display for CredentialType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CredentialType::Voucher => VOUCHER_INFO_TYPE.fmt(f), + CredentialType::FreePass => FREE_PASS_INFO_TYPE.fmt(f), + } + } +} + +#[derive(Debug, Clone)] +pub struct CredentialSigningData { + pub pedersen_commitments_openings: Vec, + + pub blind_sign_request: BlindSignRequest, + + pub public_attributes_plain: Vec, + + pub typ: CredentialType, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CredentialSpendingData { + pub embedded_private_attributes: usize, + + pub verify_credential_request: VerifyCredentialRequest, + + pub public_attributes_plain: Vec, + + pub typ: CredentialType, +} + +impl CredentialSpendingData { + pub fn verify(&self, params: &Parameters, verification_key: &VerificationKey) -> bool { + let hashed_public_attributes = self + .public_attributes_plain + .iter() + .map(hash_to_scalar) + .collect::>(); + + // get references to the attributes + let public_attributes = hashed_public_attributes.iter().collect::>(); + + verify_credential( + params, + verification_key, + &self.verify_credential_request, + &public_attributes, + ) + } + + pub fn validate_type_attribute(&self) -> bool { + // the first attribute is variant specific bandwidth encoding, the second one should be the type + let Some(type_plain) = self.public_attributes_plain.get(1) else { + return false; + }; + + self.typ.validate(type_plain) + } + + pub fn get_bandwidth_attribute(&self) -> Option<&String> { + // the first attribute is variant specific bandwidth encoding, the second one should be the type + self.public_attributes_plain.first() + } +} diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 55894c3754..bc096fa5a9 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -16,7 +16,7 @@ time = { workspace = true, features = ["serde"] } zeroize = { workspace = true } # I guess temporarily until we get serde support in coconut up and running -nym-coconut-interface = { path = "../coconut-interface" } +nym-credentials-interface = { path = "../credentials-interface" } nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "serde"] } nym-api-requests = { path = "../../nym-api/nym-api-requests" } nym-validator-client = { path = "../client-libs/validator-client", default-features = false } diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs index 5783a8c25d..67fab7197b 100644 --- a/common/credentials/src/coconut/bandwidth/freepass.rs +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::utils::scalar_serde_helper; -use nym_coconut_interface::{hash_to_scalar, Attribute, PublicAttribute}; +use nym_credentials_interface::{hash_to_scalar, Attribute, PublicAttribute}; use serde::{Deserialize, Serialize}; use time::{Duration, OffsetDateTime, Time}; use zeroize::{Zeroize, ZeroizeOnDrop}; diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 736555bfdc..7078d358d5 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -9,7 +9,7 @@ use crate::coconut::bandwidth::{ }; use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; -use nym_coconut_interface::{ +use nym_credentials_interface::{ aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, VerificationKey, }; diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index d3e8807f05..8a66e36365 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -10,7 +10,7 @@ use crate::coconut::bandwidth::{ bandwidth_credential_params, CredentialSpendingData, CredentialType, }; use crate::error::Error; -use nym_coconut_interface::{ +use nym_credentials_interface::{ prove_bandwidth_credential, Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey, }; diff --git a/common/credentials/src/coconut/bandwidth/mod.rs b/common/credentials/src/coconut/bandwidth/mod.rs index 8fe23a444b..195db20598 100644 --- a/common/credentials/src/coconut/bandwidth/mod.rs +++ b/common/credentials/src/coconut/bandwidth/mod.rs @@ -1,114 +1,21 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::Scalar; -use nym_coconut_interface::{ - hash_to_scalar, BlindSignRequest, Parameters, VerificationKey, VerifyCredentialRequest, -}; -use serde::{Deserialize, Serialize}; -use std::fmt::{Display, Formatter}; use std::sync::OnceLock; -use zeroize::{Zeroize, ZeroizeOnDrop}; pub use issuance::IssuanceBandwidthCredential; pub use issued::IssuedBandwidthCredential; +pub use nym_credentials_interface::{ + CredentialSigningData, CredentialSpendingData, CredentialType, Parameters, +}; pub mod freepass; pub mod issuance; pub mod issued; pub mod voucher; -pub const VOUCHER_INFO_TYPE: &str = "BandwidthVoucher"; -pub const FREE_PASS_INFO_TYPE: &str = "FreeBandwidthPass"; - // works under the assumption of having 4 attributes in the underlying credential(s) pub fn bandwidth_credential_params() -> &'static Parameters { static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock = OnceLock::new(); BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(IssuanceBandwidthCredential::default_parameters) } - -#[derive(Zeroize, Copy, Clone, Debug, Serialize, Deserialize)] -pub enum CredentialType { - Voucher, - FreePass, -} - -impl CredentialType { - pub fn is_free_pass(&self) -> bool { - matches!(self, CredentialType::FreePass) - } - - pub fn is_voucher(&self) -> bool { - matches!(self, CredentialType::Voucher) - } -} - -impl Display for CredentialType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CredentialType::Voucher => VOUCHER_INFO_TYPE.fmt(f), - CredentialType::FreePass => FREE_PASS_INFO_TYPE.fmt(f), - } - } -} - -#[derive(Debug, Clone)] -pub struct CredentialSigningData { - pub pedersen_commitments_openings: Vec, - - pub blind_sign_request: BlindSignRequest, - - pub public_attributes_plain: Vec, - - pub typ: CredentialType, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct CredentialSpendingData { - pub embedded_private_attributes: usize, - - pub verify_credential_request: VerifyCredentialRequest, - - pub public_attributes_plain: Vec, - - pub typ: CredentialType, -} - -impl CredentialSpendingData { - pub fn verify(&self, verification_key: &VerificationKey) -> bool { - let params = bandwidth_credential_params(); - - let hashed_public_attributes = self - .public_attributes_plain - .iter() - .map(hash_to_scalar) - .collect::>(); - - // get references to the attributes - let public_attributes = hashed_public_attributes.iter().collect::>(); - - nym_coconut_interface::verify_credential( - params, - verification_key, - &self.verify_credential_request, - &public_attributes, - ) - } - - pub fn validate_type_attribute(&self) -> bool { - // the first attribute is variant specific bandwidth encoding, the second one should be the type - let Some(type_plain) = self.public_attributes_plain.get(1) else { - return false; - }; - - match self.typ { - CredentialType::Voucher => type_plain == VOUCHER_INFO_TYPE, - CredentialType::FreePass => type_plain == FREE_PASS_INFO_TYPE, - } - } - - pub fn get_bandwidth_attribute(&self) -> Option<&String> { - // the first attribute is variant specific bandwidth encoding, the second one should be the type - self.public_attributes_plain.first() - } -} diff --git a/common/credentials/src/coconut/bandwidth/voucher.rs b/common/credentials/src/coconut/bandwidth/voucher.rs index 06f88d2414..6bf4e55dcb 100644 --- a/common/credentials/src/coconut/bandwidth/voucher.rs +++ b/common/credentials/src/coconut/bandwidth/voucher.rs @@ -5,7 +5,7 @@ use crate::coconut::bandwidth::CredentialSigningData; use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; use nym_api_requests::coconut::BlindSignRequestBody; -use nym_coconut_interface::{ +use nym_credentials_interface::{ hash_to_scalar, Attribute, BlindSignRequest, BlindedSignature, PublicAttribute, }; use nym_crypto::asymmetric::{encryption, identity}; diff --git a/common/credentials/src/coconut/credential.rs b/common/credentials/src/coconut/credential.rs deleted file mode 100644 index 446682a398..0000000000 --- a/common/credentials/src/coconut/credential.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -pub trait NymCredential { - fn prove_credential(&self) -> Result<(), ()>; -} diff --git a/common/credentials/src/coconut/mod.rs b/common/credentials/src/coconut/mod.rs index c02b932712..16d9819ab4 100644 --- a/common/credentials/src/coconut/mod.rs +++ b/common/credentials/src/coconut/mod.rs @@ -2,5 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 pub mod bandwidth; -pub mod credential; pub mod utils; diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index a2014d8c1a..cf0025826c 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -4,7 +4,7 @@ use crate::coconut::bandwidth::IssuanceBandwidthCredential; use crate::error::Error; use log::{debug, warn}; -use nym_coconut_interface::{ +use nym_credentials_interface::{ aggregate_verification_keys, Signature, SignatureShare, VerificationKey, }; use nym_validator_client::client::CoconutApiClient; diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index c89667aad6..76c4aa068c 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_coconut_interface::CoconutError; +use nym_credentials_interface::CoconutError; use nym_crypto::asymmetric::encryption::KeyRecoveryError; use nym_validator_client::ValidatorClientError; diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index d08e435486..8b5f0c3340 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -31,7 +31,6 @@ nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } nym-config = { path = "../../common/config" } -nym-coconut-interface = { path = "../../common/coconut-interface" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } [dev-dependencies] diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index a845ba908b..ff1fb32888 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -62,9 +62,9 @@ nym-node = { path = "../nym-node" } nym-api-requests = { path = "../nym-api/nym-api-requests" } nym-bin-common = { path = "../common/bin-common", features = ["output_format"] } -nym-coconut-interface = { path = "../common/coconut-interface" } nym-config = { path = "../common/config" } nym-credentials = { path = "../common/credentials" } +nym-credentials-interface = { path = "../common/credentials-interface" } nym-crypto = { path = "../common/crypto" } nym-gateway-requests = { path = "gateway-requests" } nym-mixnet-client = { path = "../common/client-libs/mixnet-client" } diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index 7916fa3038..d63170628e 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -25,8 +25,8 @@ nym-crypto = { path = "../../common/crypto" } nym-pemstore = { path = "../../common/pemstore" } nym-sphinx = { path = "../../common/nymsphinx" } -nym-coconut-interface = { path = "../../common/coconut-interface" } nym-credentials = { path = "../../common/credentials" } +nym-credentials-interface = { path = "../../common/credentials-interface" } [dependencies.tungstenite] workspace = true diff --git a/gateway/gateway-requests/src/models.rs b/gateway/gateway-requests/src/models.rs index 43881d3ceb..4e1df1d61e 100644 --- a/gateway/gateway-requests/src/models.rs +++ b/gateway/gateway-requests/src/models.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::GatewayRequestsError; -use nym_coconut_interface::CoconutError; use nym_credentials::coconut::bandwidth::CredentialSpendingData; +use nym_credentials_interface::CoconutError; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index d5ea02b2d2..76406360f6 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: GPL-3.0-only use log::error; -use nym_coconut_interface::Credential; use nym_credentials::coconut::bandwidth::CredentialType; use thiserror::Error; @@ -49,12 +48,12 @@ impl Bandwidth { } } -impl From for Bandwidth { - fn from(credential: Credential) -> Self { - let token_value = credential.voucher_value(); - let bandwidth_bytes = token_value * nym_network_defaults::BYTES_PER_UTOKEN; - Bandwidth { - value: bandwidth_bytes, - } - } -} +// impl From for Bandwidth { +// fn from(credential: Credential) -> Self { +// let token_value = credential.voucher_value(); +// let bandwidth_bytes = token_value * nym_network_defaults::BYTES_PER_UTOKEN; +// Bandwidth { +// value: bandwidth_bytes, +// } +// } +// } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index 548037e9d3..d5a662c2ed 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -1,28 +1,6 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use futures::{ - future::{FusedFuture, OptionFuture}, - FutureExt, StreamExt, -}; -use log::*; -use nym_gateway_requests::{ - iv::{IVConversionError, IV}, - types::{BinaryRequest, ServerResponse}, - ClientControlRequest, GatewayRequestsError, -}; -use nym_sphinx::forwarding::packet::MixPacket; -use nym_task::TaskClient; -use nym_validator_client::coconut::CoconutApiError; -use rand::{CryptoRng, Rng}; -use thiserror::Error; -use tokio::io::{AsyncRead, AsyncWrite}; -use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; - -use nym_coconut_interface::CoconutError; -use nym_credentials::coconut::bandwidth::CredentialType; -use std::{convert::TryFrom, process, time::Duration}; - use crate::node::client_handling::bandwidth::BandwidthError; use crate::node::{ client_handling::{ @@ -37,6 +15,26 @@ use crate::node::{ }, storage::{error::StorageError, Storage}, }; +use futures::{ + future::{FusedFuture, OptionFuture}, + FutureExt, StreamExt, +}; +use log::*; +use nym_credentials::coconut::bandwidth::{bandwidth_credential_params, CredentialType}; +use nym_credentials_interface::CoconutError; +use nym_gateway_requests::{ + iv::{IVConversionError, IV}, + types::{BinaryRequest, ServerResponse}, + ClientControlRequest, GatewayRequestsError, +}; +use nym_sphinx::forwarding::packet::MixPacket; +use nym_task::TaskClient; +use nym_validator_client::coconut::CoconutApiError; +use rand::{CryptoRng, Rng}; +use std::{convert::TryFrom, process, time::Duration}; +use thiserror::Error; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; #[derive(Debug, Error)] pub(crate) enum RequestHandlingError { @@ -76,9 +74,6 @@ pub(crate) enum RequestHandlingError { #[error("There was a problem with the proposal id: {reason}")] ProposalIdError { reason: String }, - #[error("Coconut interface error - {0}")] - CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), - #[error("coconut failure: {0}")] CoconutError(#[from] CoconutError), @@ -257,7 +252,8 @@ where let bandwidth = Bandwidth::try_from_raw_value(bandwidth_attribute, credential.data.typ)?; - if !credential.data.verify(&aggregated_verification_key) { + let params = bandwidth_credential_params(); + if !credential.data.verify(params, &aggregated_verification_key) { return Err(RequestHandlingError::InvalidBandwidthCredential( String::from("credential failed to verify on gateway"), )); diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index e62d6afa5a..7515157b55 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -3,7 +3,7 @@ use super::authenticated::RequestHandlingError; use log::*; -use nym_coconut_interface::{Credential, VerificationKey}; +use nym_credentials_interface::VerificationKey; use nym_gateway_requests::models::CredentialSpendingWithEpoch; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nym_api::EpochId; @@ -229,7 +229,8 @@ impl CoconutVerifier { } let req = nym_api_requests::coconut::VerifyCredentialBody::new( - credential, + credential.data, + credential.epoch_id, proposal_id, self.address.clone(), ); diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 579a98d991..db80920086 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -78,7 +78,6 @@ zeroize = { workspace = true } nym-bandwidth-controller = { path = "../common/bandwidth-controller" } nym-coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } -nym-coconut-interface = { path = "../common/coconut-interface" } #nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" } nym-config = { path = "../common/config" } cosmwasm-std = { workspace = true } diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index aaf7309fcf..1baf4d17dd 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -16,9 +16,7 @@ serde = { workspace = true, features = ["derive"] } ts-rs = { workspace = true, optional = true } tendermint = { workspace = true } -nym-coconut = { path = "../../common/nymcoconut" } -nym-coconut-interface = { path = "../../common/coconut-interface" } -#nym-credentials = { path = "../../common/credentials" } +nym-credentials-interface = { path = "../../common/credentials-interface" } nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"]} nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } diff --git a/nym-api/nym-api-requests/src/coconut/helpers.rs b/nym-api/nym-api-requests/src/coconut/helpers.rs index 58aec3eca0..73d1a7f42f 100644 --- a/nym-api/nym-api-requests/src/coconut/helpers.rs +++ b/nym-api/nym-api-requests/src/coconut/helpers.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use nym_coconut_interface::BlindedSignature; +use nym_credentials_interface::BlindedSignature; use tendermint::hash::Hash; // recomputes plaintext on the credential nym-api has used for signing diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index 15efe1a9f6..ce58d8a05e 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -3,9 +3,9 @@ use crate::coconut::helpers::issued_credential_plaintext; use cosmrs::AccountId; -use nym_coconut::{ +use nym_credentials_interface::{ hash_to_scalar, Attribute, BlindSignRequest, BlindedSignature, Bytable, CoconutError, - Signature, VerificationKey, + CredentialSpendingData, Signature, VerificationKey, }; use nym_crypto::asymmetric::identity; use serde::{Deserialize, Serialize}; @@ -15,7 +15,7 @@ use tendermint::hash::Hash; #[derive(Serialize, Deserialize)] pub struct VerifyCredentialBody { /// The cryptographic material required for spending the underlying credential. - pub credential_data: (), + pub credential_data: CredentialSpendingData, /// The (DKG) epoch id under which the credential has been issued so that the verifier /// could use correct verification key for validation. @@ -30,7 +30,7 @@ pub struct VerifyCredentialBody { impl VerifyCredentialBody { pub fn new( - credential_data: (), + credential_data: CredentialSpendingData, epoch_id: u64, proposal_id: u64, gateway_cosmos_addr: AccountId, @@ -100,7 +100,7 @@ impl BlindSignRequestBody { } pub fn encode_commitments(&self) -> Vec { - use nym_coconut_interface::Base58; + use nym_credentials_interface::Base58; self.inner_sign_request .get_private_attributes_pedersen_commitments() diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index c8a739319d..8a276d5e2d 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -17,7 +17,9 @@ use nym_coconut_bandwidth_contract_common::spend_credential::{ funds_from_cosmos_msgs, SpendCredentialStatus, }; use nym_coconut_dkg_common::types::EpochId; -use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential; +use nym_credentials::coconut::bandwidth::{ + bandwidth_credential_params, IssuanceBandwidthCredential, +}; use nym_validator_client::nyxd::Coin; use rocket::serde::json::Json; use rocket::State as RocketState; @@ -93,15 +95,22 @@ pub async fn verify_bandwidth_credential( state: &RocketState, ) -> Result> { let proposal_id = verify_credential_body.proposal_id; - let proposal = state.client.get_proposal(proposal_id).await?; + let epoch_id = verify_credential_body.epoch_id; + let credential_data = &verify_credential_body.credential_data; + let theta = &credential_data.verify_credential_request; + + let voucher_value: u64 = if credential_data.typ.is_voucher() { + todo!() + } else { + todo!("return error here") + }; // TODO: introduce a check to make sure we haven't already voted for this proposal to prevent DDOS + let proposal = state.client.get_proposal(proposal_id).await?; + // Proposal description is the blinded serial number - if !verify_credential_body - .credential - .has_blinded_serial_number(&proposal.description)? - { + if !theta.has_blinded_serial_number(&proposal.description)? { return Err(CoconutError::IncorrectProposal { reason: String::from("incorrect blinded serial number in description"), }); @@ -113,7 +122,7 @@ pub async fn verify_bandwidth_credential( // Credential has not been spent before, and is on its way of being spent let credential_status = state .client - .get_spent_credential(verify_credential_body.credential.blinded_serial_number()) + .get_spent_credential(theta.blinded_serial_number_bs58()) .await? .spend_credential .ok_or(CoconutError::InvalidCredentialStatus { @@ -125,16 +134,12 @@ pub async fn verify_bandwidth_credential( status: format!("{:?}", credential_status), }); } - let verification_key = state - .verification_key(*verify_credential_body.credential.epoch_id()) - .await?; - let mut vote_yes = verify_credential_body.credential.verify(&verification_key); + let verification_key = state.verification_key(epoch_id).await?; + let params = bandwidth_credential_params(); + let mut vote_yes = credential_data.verify(params, &verification_key); vote_yes &= Coin::from(proposed_release_funds) - == Coin::new( - verify_credential_body.credential.voucher_value() as u128, - state.mix_denom.clone(), - ); + == Coin::new(voucher_value as u128, state.mix_denom.clone()); // Vote yes or no on the proposal based on the verification result let ret = state diff --git a/nym-api/src/coconut/comm.rs b/nym-api/src/coconut/comm.rs index 0cb84fc894..2019d99650 100644 --- a/nym-api/src/coconut/comm.rs +++ b/nym-api/src/coconut/comm.rs @@ -4,8 +4,8 @@ use crate::coconut::error::Result; use crate::nyxd; use crate::support::nyxd::ClientInner; +use nym_coconut::VerificationKey; use nym_coconut_dkg_common::types::{Epoch, EpochId}; -use nym_coconut_interface::VerificationKey; use nym_credentials::coconut::utils::obtain_aggregate_verification_key; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; diff --git a/nym-api/src/coconut/dkg/key_derivation.rs b/nym-api/src/coconut/dkg/key_derivation.rs index db86ec39a5..eb6c5b7214 100644 --- a/nym-api/src/coconut/dkg/key_derivation.rs +++ b/nym-api/src/coconut/dkg/key_derivation.rs @@ -10,10 +10,10 @@ use crate::coconut::keys::KeyPairWithEpoch; use crate::coconut::state::bandwidth_credential_params; use cosmwasm_std::Addr; use log::debug; +use nym_coconut::KeyPair as CoconutKeyPair; use nym_coconut::{check_vk_pairing, Base58, SecretKey, VerificationKey}; use nym_coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID; use nym_coconut_dkg_common::types::{DealingIndex, EpochId, NodeIndex}; -use nym_coconut_interface::KeyPair as CoconutKeyPair; use nym_dkg::{ bte::{self, decrypt_share}, combine_shares, try_recover_verification_keys, Dealing, diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 423574db8d..4f464ef175 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -87,9 +87,6 @@ pub enum CoconutError { #[error("public attributes in request differ from the ones in deposit: Expected {0}, got {1}")] DifferentPublicAttributes(String, String), - #[error("error in coconut interface: {0}")] - CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), - #[error("storage error: {0}")] StorageError(#[from] NymApiStorageError), diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs index e5778674e6..ee55f9ff1d 100644 --- a/nym-api/src/coconut/helpers.rs +++ b/nym-api/src/coconut/helpers.rs @@ -28,7 +28,7 @@ pub(crate) fn blind_sign( let public_attributes = request.public_attributes_hashed(); let attributes_ref = public_attributes.iter().collect::>(); - Ok(nym_coconut_interface::blind_sign( + Ok(nym_coconut::blind_sign( bandwidth_credential_params(), signing_key, &request.inner_sign_request, diff --git a/nym-api/src/coconut/keys/mod.rs b/nym-api/src/coconut/keys/mod.rs index 117c512b9e..7c719c7a53 100644 --- a/nym-api/src/coconut/keys/mod.rs +++ b/nym-api/src/coconut/keys/mod.rs @@ -18,12 +18,12 @@ pub struct KeyPair { #[derive(Debug)] pub struct KeyPairWithEpoch { - pub(crate) keys: nym_coconut_interface::KeyPair, + pub(crate) keys: nym_coconut::KeyPair, pub(crate) issued_for_epoch: EpochId, } impl KeyPairWithEpoch { - pub(crate) fn new(keys: nym_coconut_interface::KeyPair, issued_for_epoch: EpochId) -> Self { + pub(crate) fn new(keys: nym_coconut::KeyPair, issued_for_epoch: EpochId) -> Self { KeyPairWithEpoch { keys, issued_for_epoch, diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs index 6bf4159c2e..d92a5d6f81 100644 --- a/nym-api/src/coconut/state.rs +++ b/nym-api/src/coconut/state.rs @@ -10,8 +10,8 @@ use crate::coconut::storage::CoconutStorageExt; use crate::support::storage::NymApiStorage; use nym_api_requests::coconut::helpers::issued_credential_plaintext; use nym_api_requests::coconut::BlindSignRequestBody; +use nym_coconut::{BlindedSignature, VerificationKey}; use nym_coconut_dkg_common::types::EpochId; -use nym_coconut_interface::{BlindedSignature, VerificationKey}; use nym_crypto::asymmetric::identity; use nym_validator_client::nyxd::{Hash, TxResponse}; use std::sync::Arc; diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 555390c706..2cfd6ab068 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3694,8 +3694,7 @@ dependencies = [ "cosmrs", "cosmwasm-std", "getset", - "nym-coconut", - "nym-coconut-interface", + "nym-credentials-interface", "nym-crypto", "nym-mixnet-contract-common", "nym-node-requests", @@ -3710,9 +3709,10 @@ version = "0.1.0" dependencies = [ "bip39", "log", - "nym-coconut-interface", + "nym-coconut", "nym-credential-storage", "nym-credentials", + "nym-credentials-interface", "nym-crypto", "nym-network-defaults", "nym-validator-client", @@ -3830,17 +3830,6 @@ dependencies = [ "nym-multisig-contract-common", ] -[[package]] -name = "nym-coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58 0.4.0", - "getset", - "nym-coconut", - "serde", - "thiserror", -] - [[package]] name = "nym-config" version = "0.1.0" @@ -3937,7 +3926,7 @@ dependencies = [ "cosmrs", "log", "nym-api-requests", - "nym-coconut-interface", + "nym-credentials-interface", "nym-crypto", "nym-validator-client", "serde", @@ -3946,6 +3935,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "nym-credentials-interface" +version = "0.1.0" +dependencies = [ + "bls12_381", + "nym-coconut", + "serde", +] + [[package]] name = "nym-crypto" version = "0.4.0" @@ -4074,8 +4072,8 @@ dependencies = [ "futures", "generic-array 0.14.7", "log", - "nym-coconut-interface", "nym-credentials", + "nym-credentials-interface", "nym-crypto", "nym-pemstore", "nym-sphinx", @@ -4505,9 +4503,9 @@ dependencies = [ "itertools", "log", "nym-api-requests", + "nym-coconut", "nym-coconut-bandwidth-contract-common", "nym-coconut-dkg-common", - "nym-coconut-interface", "nym-config", "nym-contracts-common", "nym-ephemera-common", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index b5dfb64b97..8c3109210f 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3098,8 +3098,7 @@ dependencies = [ "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "cosmwasm-std", "getset", - "nym-coconut", - "nym-coconut-interface", + "nym-credentials-interface", "nym-crypto", "nym-mixnet-contract-common", "nym-node-requests", @@ -3167,17 +3166,6 @@ dependencies = [ "nym-multisig-contract-common", ] -[[package]] -name = "nym-coconut-interface" -version = "0.1.0" -dependencies = [ - "bs58 0.4.0", - "getset", - "nym-coconut", - "serde", - "thiserror", -] - [[package]] name = "nym-config" version = "0.1.0" @@ -3203,6 +3191,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-credentials-interface" +version = "0.1.0" +dependencies = [ + "bls12_381", + "nym-coconut", + "serde", +] + [[package]] name = "nym-crypto" version = "0.4.0" @@ -3401,7 +3398,6 @@ dependencies = [ "hmac 0.12.1", "itertools 0.11.0", "log", - "nym-coconut-interface", "nym-config", "nym-crypto", "nym-mixnet-contract-common", @@ -3442,9 +3438,9 @@ dependencies = [ "itertools 0.10.5", "log", "nym-api-requests", + "nym-coconut", "nym-coconut-bandwidth-contract-common", "nym-coconut-dkg-common", - "nym-coconut-interface", "nym-config", "nym-contracts-common", "nym-ephemera-common", @@ -3546,7 +3542,6 @@ dependencies = [ "itertools 0.10.5", "k256 0.13.1", "log", - "nym-coconut-interface", "nym-config", "nym-contracts-common", "nym-crypto", diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 854aa1d57f..a9b72b234e 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -57,7 +57,6 @@ nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } nym-config = { path = "../../common/config" } -nym-coconut-interface = { path = "../../common/coconut-interface" } nym-types = { path = "../../common/types" } nym-wallet-types = { path = "../nym-wallet-types" } nym-store-cipher = { path = "../../common/store-cipher", features = ["json"] } From 0ee727bac1fe48e5208f95acd75f2ad61835aedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 7 Feb 2024 17:21:29 +0000 Subject: [PATCH 07/49] gateway handling of both credential types --- common/network-defaults/src/lib.rs | 3 + gateway/Cargo.toml | 1 + gateway/src/node/client_handling/bandwidth.rs | 91 +++++++++++++------ .../connection_handler/authenticated.rs | 11 +-- 4 files changed, 69 insertions(+), 37 deletions(-) diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 4404b0e51f..1891cd3cc3 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -457,6 +457,9 @@ pub const ETH_ERC20_APPROVE_FUNCTION_NAME: &str = "approve"; /// How much bandwidth (in bytes) one token can buy pub const BYTES_PER_UTOKEN: u64 = 1024; +/// How much bandwidth (in bytes) one freepass provides +pub const BYTES_PER_FREEPASS: u64 = 1024 * 1024 * 1024; // 1GB + /// Threshold for claiming more bandwidth: 1 MB pub const REMAINING_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024; /// How many ERC20 tokens should be burned to buy bandwidth diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index ff1fb32888..51f0307f76 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -55,6 +55,7 @@ tokio-stream = { version = "0.1.11", features = ["fs"] } tokio-tungstenite = { version = "0.20.1" } tokio-util = { workspace = true, features = ["codec"] } url = { workspace = true, features = ["serde"] } +time = { workspace = true } zeroize = { workspace = true } # internal diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index 76406360f6..ae8469c504 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,12 +1,40 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use log::error; +use log::{error, warn}; use nym_credentials::coconut::bandwidth::CredentialType; +use std::num::ParseIntError; use thiserror::Error; +use time::error::ComponentRange; +use time::OffsetDateTime; #[derive(Debug, Error)] -pub enum BandwidthError {} +pub enum BandwidthError { + #[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)] + UnsupportedBandwidthValue(u64), + + #[error("the provided free pass has already expired (expiry was on {expiry_date})")] + ExpiredFreePass { expiry_date: OffsetDateTime }, + + #[error("failed to pass the bandwidth voucher value: {source}")] + VoucherValueParsingFailure { + #[source] + source: ParseIntError, + }, + + #[error("failed to pass the free pass expiry date: {source}")] + ExpiryDateParsingFailure { + #[source] + source: ParseIntError, + }, + + #[error("failed to parse expiry timestamp into proper datetime: {source}")] + InvalidExpiryDate { + unix_timestamp: i64, + #[source] + source: ComponentRange, + }, +} pub struct Bandwidth { value: u64, @@ -18,42 +46,47 @@ impl Bandwidth { } pub fn try_from_raw_value(value: &String, typ: CredentialType) -> Result { - // let bandwidth_value = match credential.data.typ { - // CredentialType::Voucher => { - // todo!() - // } - // CredentialType::FreePass => { - // error!("unimplemented handling of free pass credential"); - // return Err(()); - // } - // }; + let bandwidth_value = + match typ { + CredentialType::Voucher => { + let token_value: u64 = value + .parse() + .map_err(|source| BandwidthError::VoucherValueParsingFailure { source })?; + token_value * nym_network_defaults::BYTES_PER_UTOKEN + } + CredentialType::FreePass => { + let expiry_timestamp: i64 = value + .parse() + .map_err(|source| BandwidthError::ExpiryDateParsingFailure { source })?; - /* - if bandwidth_value > i64::MAX as u64 { + let expiry_date = OffsetDateTime::from_unix_timestamp(expiry_timestamp) + .map_err(|source| BandwidthError::InvalidExpiryDate { + unix_timestamp: expiry_timestamp, + source, + })?; + let now = OffsetDateTime::now_utc(); + + if expiry_date < now { + return Err(BandwidthError::ExpiredFreePass { expiry_date }); + } + nym_network_defaults::BYTES_PER_FREEPASS + } + }; + + if bandwidth_value > i64::MAX as u64 { // note that this would have represented more than 1 exabyte, - // which is like 125,000 worth of hard drives so I don't think we have + // which is like 125,000 worth of hard drives, so I don't think we have // to worry about it for now... warn!("Somehow we received bandwidth value higher than 9223372036854775807. We don't really want to deal with this now"); - return Err(RequestHandlingError::UnsupportedBandwidthValue( - bandwidth_value, - )); + return Err(BandwidthError::UnsupportedBandwidthValue(bandwidth_value)); } - */ - todo!() + Ok(Bandwidth { + value: bandwidth_value, + }) } pub fn value(&self) -> u64 { self.value } } - -// impl From for Bandwidth { -// fn from(credential: Credential) -> Self { -// let token_value = credential.voucher_value(); -// let bandwidth_bytes = token_value * nym_network_defaults::BYTES_PER_UTOKEN; -// Bandwidth { -// value: bandwidth_bytes, -// } -// } -// } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index d5a662c2ed..3c3ff21fa4 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -53,9 +53,6 @@ pub(crate) enum RequestHandlingError { #[error("The received request is not valid in the current context")] IllegalRequest, - #[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)] - UnsupportedBandwidthValue(u64), - #[error("Provided bandwidth credential did not verify correctly on {0}")] InvalidBandwidthCredential(String), @@ -85,9 +82,6 @@ pub(crate) enum RequestHandlingError { #[error("failed to recover bandwidth value: {0}")] BandwidthRecoveryFailure(#[from] BandwidthError), - - #[error("free pass credentials haven't been implemented yet")] - UnimplementedFreePass, } impl RequestHandlingError { @@ -250,6 +244,7 @@ where unimplemented!() }; + // this will extract token amounts out of bandwidth vouchers and validate expiry of free passes let bandwidth = Bandwidth::try_from_raw_value(bandwidth_attribute, credential.data.typ)?; let params = bandwidth_credential_params(); @@ -273,8 +268,8 @@ where .await?; } CredentialType::FreePass => { - error!("unimplemented handling of free pass credential"); - return Err(RequestHandlingError::UnimplementedFreePass); + // no need to do anything special here, we already extracted the bandwidth amount and checked expiry + info!("received a free pass credential"); } } From 16c942d72ef110546efdd0db97a9292bd9a70edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 7 Feb 2024 17:31:30 +0000 Subject: [PATCH 08/49] removed nym-api placeholders --- nym-api/nym-api-requests/src/coconut/models.rs | 2 +- nym-api/src/coconut/api_routes/mod.rs | 10 ++++++++-- nym-api/src/coconut/error.rs | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index ce58d8a05e..c4de811281 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -5,7 +5,7 @@ use crate::coconut::helpers::issued_credential_plaintext; use cosmrs::AccountId; use nym_credentials_interface::{ hash_to_scalar, Attribute, BlindSignRequest, BlindedSignature, Bytable, CoconutError, - CredentialSpendingData, Signature, VerificationKey, + CredentialSpendingData, VerificationKey, }; use nym_crypto::asymmetric::identity; use serde::{Deserialize, Serialize}; diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 8a276d5e2d..b2b2dfc4bc 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -100,9 +100,15 @@ pub async fn verify_bandwidth_credential( let theta = &credential_data.verify_credential_request; let voucher_value: u64 = if credential_data.typ.is_voucher() { - todo!() + credential_data + .get_bandwidth_attribute() + .ok_or(CoconutError::MissingBandwidthValue)? + .parse() + .map_err(|source| CoconutError::VoucherValueParsingFailure { source })? } else { - todo!("return error here") + return Err(CoconutError::NotABandwidthVoucher { + typ: credential_data.typ, + }); }; // TODO: introduce a check to make sure we haven't already voted for this proposal to prevent DDOS diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 4f464ef175..ec43b0d65d 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -3,6 +3,7 @@ use crate::node_status_api::models::NymApiStorageError; use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId}; +use nym_credentials::coconut::bandwidth::CredentialType; use nym_crypto::asymmetric::{ encryption::KeyRecoveryError, identity::{Ed25519RecoveryError, SignatureError}, @@ -14,6 +15,7 @@ use rocket::http::{ContentType, Status}; use rocket::response::Responder; use rocket::{response, Request, Response}; use std::io::Cursor; +use std::num::ParseIntError; use thiserror::Error; pub type Result = std::result::Result; @@ -23,6 +25,20 @@ pub enum CoconutError { #[error(transparent)] IOError(#[from] std::io::Error), + #[error("the received bandwidth voucher did not contain deposit value")] + MissingBandwidthValue, + + #[error( + "the received bandwidth credential is not a bandwidth voucher. the encoded type is: {typ}" + )] + NotABandwidthVoucher { typ: CredentialType }, + + #[error("failed to parse the bandwidth voucher value: {source}")] + VoucherValueParsingFailure { + #[source] + source: ParseIntError, + }, + #[error("coconut api query failure: {0}")] CoconutApiError(#[from] CoconutApiError), From ddf2770c8ec450078d454447152caf8f3be36552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 8 Feb 2024 10:11:05 +0000 Subject: [PATCH 09/49] reintroduced recovery of vouchers --- .../credential-utils/src/recovery_storage.rs | 29 +++++++---- common/credential-utils/src/utils.rs | 51 +++++++++++-------- .../src/coconut/bandwidth/issuance.rs | 15 ++++++ common/nymcoconut/src/scheme/setup.rs | 6 +-- gateway/src/node/client_handling/bandwidth.rs | 4 +- 5 files changed, 69 insertions(+), 36 deletions(-) diff --git a/common/credential-utils/src/recovery_storage.rs b/common/credential-utils/src/recovery_storage.rs index 3c9b8c17af..f003b562cd 100644 --- a/common/credential-utils/src/recovery_storage.rs +++ b/common/credential-utils/src/recovery_storage.rs @@ -8,6 +8,8 @@ use std::fs::{create_dir_all, read_dir, File}; use std::io::{Read, Write}; use std::path::PathBuf; +pub const DUMPED_VOUCHER_EXTENSION: &str = "credentialrecovery"; + pub struct RecoveryStorage { recovery_dir: PathBuf, } @@ -24,8 +26,10 @@ impl RecoveryStorage { let mut paths = vec![]; for entry in entries.flatten() { let path = entry.path(); - if path.is_file() { - paths.push(path) + if let Some(extension) = path.extension() { + if extension == DUMPED_VOUCHER_EXTENSION { + paths.push(path) + } } } @@ -47,15 +51,20 @@ impl RecoveryStorage { Ok(vouchers) } + pub fn voucher_filename(voucher: &IssuanceBandwidthCredential) -> String { + let prefix = voucher.typ().to_string(); + let suffix = voucher.blinded_g1_serial_number_bs58(); + format!("{prefix}-{suffix}.{DUMPED_VOUCHER_EXTENSION}") + } + pub fn insert_voucher(&self, voucher: &IssuanceBandwidthCredential) -> Result { - todo!() - // let file_name = voucher.tx_hash().to_string(); - // let file_path = self.recovery_dir.join(file_name); - // let mut file = File::create(&file_path)?; - // let buff = voucher.to_bytes(); - // file.write_all(&buff)?; - // - // Ok(file_path) + let file_name = Self::voucher_filename(voucher); + let file_path = self.recovery_dir.join(file_name); + let mut file = File::create(&file_path)?; + let buff = voucher.to_recovery_bytes(); + file.write_all(&buff)?; + + Ok(file_path) } pub fn remove_voucher(&self, file_name: String) -> Result<()> { diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs index 7668b5f1b7..8e773a3225 100644 --- a/common/credential-utils/src/utils.rs +++ b/common/credential-utils/src/utils.rs @@ -5,6 +5,7 @@ use nym_bandwidth_controller::acquire::state::State; use nym_client_core::config::disk_persistence::CommonClientPaths; 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, }; @@ -126,25 +127,33 @@ pub async fn recover_credentials( where C: DkgQueryClient + Send + Sync, { - todo!() - // let mut recovered_amount: u128 = 0; - // for voucher in recovery_storage.unconsumed_vouchers()? { - // let voucher_value = voucher.get_voucher_value(); - // recovered_amount += voucher_value.parse::()?; - // - // let state = State::new(voucher); - // let voucher = state.voucher.tx_hash(); - // if let Err(e) = - // nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await - // { - // error!("Could not recover deposit {voucher} due to {e}, try again later",) - // } else { - // info!("Converted deposit {voucher} to a credential, removing recovery data for it",); - // if let Err(e) = recovery_storage.remove_voucher(voucher.to_string()) { - // warn!("Could not remove recovery data: {e}"); - // } - // } - // } - // - // Ok(recovered_amount) + 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::FreePass => { + error!("unimplemented recovery of free pass credentials"); + continue; + } + }; + recovered_amount += voucher_value.parse::()?; + + let voucher_name = RecoveryStorage::voucher_filename(&voucher); + let state = State::new(voucher); + + if let Err(e) = + nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await + { + error!("Could not recover deposit {voucher_name} due to {e}, try again later",) + } else { + info!( + "Converted deposit {voucher_name} to a credential, removing recovery data for it", + ); + if let Err(err) = recovery_storage.remove_voucher(voucher_name) { + warn!("Could not remove recovery data: {err}"); + } + } + } + + Ok(recovered_amount) } diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 7078d358d5..3ef9352782 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -9,6 +9,7 @@ use crate::coconut::bandwidth::{ }; use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; +use bls12_381::G1Projective; use nym_credentials_interface::{ aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, VerificationKey, @@ -126,6 +127,16 @@ impl IssuanceBandwidthCredential { )) } + pub fn blind_serial_number_in_g1subgroup(&self) -> G1Projective { + bandwidth_credential_params().gen1() * self.serial_number + } + + pub fn blinded_g1_serial_number_bs58(&self) -> String { + use nym_credentials_interface::Base58; + + self.blind_serial_number_in_g1subgroup().to_bs58() + } + pub fn new_freepass(expiry_date: Option) -> Self { Self::new(FreePassIssuanceData::new(expiry_date)) } @@ -149,6 +160,10 @@ impl IssuanceBandwidthCredential { ] } + pub fn get_bandwidth_attribute(&self) -> String { + self.variant_data.public_value_plain() + } + pub fn prepare_for_signing(&self) -> CredentialSigningData { let params = bandwidth_credential_params(); diff --git a/common/nymcoconut/src/scheme/setup.rs b/common/nymcoconut/src/scheme/setup.rs index 926d8f5ed0..63add88404 100644 --- a/common/nymcoconut/src/scheme/setup.rs +++ b/common/nymcoconut/src/scheme/setup.rs @@ -44,11 +44,11 @@ impl Parameters { }) } - pub(crate) fn gen1(&self) -> &G1Affine { + pub fn gen1(&self) -> &G1Affine { &self.g1 } - pub(crate) fn gen2(&self) -> &G2Affine { + pub fn gen2(&self) -> &G2Affine { &self.g2 } @@ -56,7 +56,7 @@ impl Parameters { &self._g2_prepared_miller } - pub(crate) fn gen_hs(&self) -> &[G1Affine] { + pub fn gen_hs(&self) -> &[G1Affine] { &self.hs } diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index ae8469c504..ce1caed6a7 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -16,13 +16,13 @@ pub enum BandwidthError { #[error("the provided free pass has already expired (expiry was on {expiry_date})")] ExpiredFreePass { expiry_date: OffsetDateTime }, - #[error("failed to pass the bandwidth voucher value: {source}")] + #[error("failed to parse the bandwidth voucher value: {source}")] VoucherValueParsingFailure { #[source] source: ParseIntError, }, - #[error("failed to pass the free pass expiry date: {source}")] + #[error("failed to parse the free pass expiry date: {source}")] ExpiryDateParsingFailure { #[source] source: ParseIntError, From f687ebb0f5505ade07cdc6193968d3fa54cd86e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 8 Feb 2024 15:01:58 +0000 Subject: [PATCH 10/49] persisting the issued credentials --- Cargo.lock | 2 + .../bandwidth-controller/src/acquire/mod.rs | 31 +++++---- common/bandwidth-controller/src/lib.rs | 7 +- common/bandwidth-controller/src/utils.rs | 2 +- common/credential-storage/Cargo.toml | 4 +- .../20240206120000_add_credential_types.sql | 12 ++++ .../credential-storage/src/backends/memory.rs | 65 ++++++++++--------- .../credential-storage/src/backends/sqlite.rs | 50 +++++--------- .../src/ephemeral_storage.rs | 42 +++++------- common/credential-storage/src/models.rs | 47 ++++++++------ .../src/persistent_storage.rs | 37 ++++------- common/credential-storage/src/storage.rs | 26 +------- common/credential-utils/src/utils.rs | 5 +- common/credentials/Cargo.toml | 2 +- .../src/coconut/bandwidth/freepass.rs | 2 +- .../src/coconut/bandwidth/issuance.rs | 8 +++ .../src/coconut/bandwidth/issued.rs | 48 ++++++++++++-- nym-connect/desktop/Cargo.lock | 1 + sdk/rust/nym-sdk/src/bandwidth/client.rs | 10 ++- sdk/rust/nym-sdk/src/mixnet.rs | 4 +- 20 files changed, 210 insertions(+), 195 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb1d3f96cc..6a90787a0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5343,6 +5343,7 @@ dependencies = [ "sqlx", "thiserror", "tokio", + "zeroize", ] [[package]] @@ -5543,6 +5544,7 @@ dependencies = [ "sqlx", "subtle-encoding", "thiserror", + "time", "tokio", "tokio-stream", "tokio-tungstenite", diff --git a/common/bandwidth-controller/src/acquire/mod.rs b/common/bandwidth-controller/src/acquire/mod.rs index 5b842fc775..b61bfc2ad4 100644 --- a/common/bandwidth-controller/src/acquire/mod.rs +++ b/common/bandwidth-controller/src/acquire/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BandwidthControllerError; -use nym_coconut::Base58; +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; @@ -13,6 +13,7 @@ use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use nym_validator_client::nyxd::Coin; use rand::rngs::OsRng; use state::State; +use zeroize::Zeroizing; pub mod state; @@ -43,7 +44,7 @@ where Ok(state) } -pub async fn get_credential( +pub async fn get_bandwidth_voucher( state: &State, client: &C, storage: &St, @@ -54,7 +55,7 @@ where ::StorageError: Send + Sync + 'static, { // temporary - assert!(!state.voucher.typ().is_free_pass()); + assert!(state.voucher.typ().is_voucher()); let epoch_id = client.get_current_epoch().await?.epoch_id; let threshold = client @@ -66,19 +67,21 @@ where let signature = obtain_aggregate_signature(&state.voucher, &coconut_api_clients, threshold).await?; + let issued = state.voucher.to_issued_credential(signature); + + // make sure the data gets zeroized after persisting it + let credential_data = Zeroizing::new(issued.pack_v1()); + let storable = StorableIssuedCredential { + serialization_revision: issued.current_serialization_revision(), + credential_data: credential_data.as_ref(), + credential_type: issued.typ().to_string(), + epoch_id: epoch_id + .try_into() + .expect("our epoch is has run over u32::MAX!"), + }; - // we asserted the that the bandwidth credential we obtained is **NOT** the free pass - // so the first public attribute must be the value - let voucher_value = state.voucher.get_plain_public_attributes()[0].clone(); storage - .insert_coconut_credential( - voucher_value, - CredentialType::Voucher.to_string(), - state.voucher.get_private_attributes()[0].to_bs58(), - state.voucher.get_private_attributes()[1].to_bs58(), - signature.to_bs58(), - epoch_id.to_string(), - ) + .insert_issued_credential(storable) .await .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err))) } diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index 2902d93451..3fa91802ee 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -1,10 +1,9 @@ -// Copyright 2021-2023 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::error::BandwidthControllerError; use crate::utils::stored_credential_to_issued_bandwidth; use log::{error, warn}; -use nym_credential_storage::error::StorageError; use nym_credential_storage::storage::Storage; use nym_credentials::coconut::bandwidth::CredentialSpendingData; use nym_credentials::coconut::utils::obtain_aggregate_verification_key; @@ -12,7 +11,6 @@ use nym_credentials_interface::VerificationKey; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; -use std::str::FromStr; pub mod acquire; pub mod error; @@ -69,8 +67,7 @@ impl BandwidthController { .await .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?; - let epoch_id = u64::from_str(&retrieved_credential.epoch_id) - .map_err(|_| StorageError::InconsistentData)?; + let epoch_id = retrieved_credential.epoch_id as EpochId; let credential_id = retrieved_credential.id; let issued_bandwidth = stored_credential_to_issued_bandwidth(retrieved_credential)?; diff --git a/common/bandwidth-controller/src/utils.rs b/common/bandwidth-controller/src/utils.rs index 7f24f8e141..690466daa5 100644 --- a/common/bandwidth-controller/src/utils.rs +++ b/common/bandwidth-controller/src/utils.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BandwidthControllerError; -use nym_credential_storage::models::StoredIssuedCredential; +use nym_credential_storage::models::{StorableIssuedCredential, StoredIssuedCredential}; use nym_credentials::coconut::bandwidth::IssuedBandwidthCredential; use nym_validator_client::nym_api::EpochId; diff --git a/common/credential-storage/Cargo.toml b/common/credential-storage/Cargo.toml index 9beb030e22..d478759ee7 100644 --- a/common/credential-storage/Cargo.toml +++ b/common/credential-storage/Cargo.toml @@ -11,7 +11,9 @@ async-trait = { workspace = true } log = { workspace = true } thiserror = { workspace = true } -tokio = { version = "1.24.1", features = ["sync"]} +tokio = { workspace = true, features = ["sync"]} +zeroize = { workspace = true, features = ["zeroize_derive"] } + [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] workspace = true diff --git a/common/credential-storage/migrations/20240206120000_add_credential_types.sql b/common/credential-storage/migrations/20240206120000_add_credential_types.sql index a72048344f..a75acca881 100644 --- a/common/credential-storage/migrations/20240206120000_add_credential_types.sql +++ b/common/credential-storage/migrations/20240206120000_add_credential_types.sql @@ -3,3 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ +DROP TABLE coconut_credentials; +CREATE TABLE coconut_credentials +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + +-- introduce a way for us to introduce breaking changes in serialization + serialization_revision INTEGER NOT NULL, + credential_type TEXT NOT NULL, + credential_data BLOB NOT NULL, + epoch_id TEXT NOT NULL, + consumed BOOLEAN NOT NULL +); \ No newline at end of file diff --git a/common/credential-storage/src/backends/memory.rs b/common/credential-storage/src/backends/memory.rs index 2dbdff0925..80538337c8 100644 --- a/common/credential-storage/src/backends/memory.rs +++ b/common/credential-storage/src/backends/memory.rs @@ -1,59 +1,60 @@ -// Copyright 2023 - Nym Technologies SA +// Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::models::CoconutCredential; +use crate::models::StoredIssuedCredential; use std::sync::Arc; use tokio::sync::RwLock; #[derive(Clone)] pub struct CoconutCredentialManager { - inner: Arc>>, + inner: Arc>, +} + +#[derive(Default)] +struct CoconutCredentialManagerInner { + data: Vec, + _next_id: i64, +} + +impl CoconutCredentialManagerInner { + fn next_id(&mut self) -> i64 { + let next = self._next_id; + self._next_id += 1; + next + } } impl CoconutCredentialManager { /// Creates new empty instance of the `CoconutCredentialManager`. pub fn new() -> Self { CoconutCredentialManager { - inner: Arc::new(RwLock::new(Vec::new())), + inner: Default::default(), } } - /// Inserts provided signature into the database. - /// - /// # Arguments - /// - /// * `voucher_value`: Plaintext bandwidth value of the credential. - /// * `voucher_info`: Plaintext information of the credential. - /// * `serial_number`: Base58 representation of the serial number attribute. - /// * `binding_number`: Base58 representation of the binding number attribute. - /// * `signature`: Coconut credential in the form of a signature. - pub async fn insert_coconut_credential( + pub async fn insert_issued_credential( &self, - voucher_value: String, - voucher_info: String, - serial_number: String, - binding_number: String, - signature: String, - epoch_id: String, + credential_type: String, + serialization_revision: u8, + credential_data: &[u8], + epoch_id: u32, ) { - let mut creds = self.inner.write().await; - let id = creds.len() as i64; - creds.push(CoconutCredential { + let mut inner = self.inner.write().await; + let id = inner.next_id(); + inner.data.push(StoredIssuedCredential { id, - voucher_value, - voucher_info, - serial_number, - binding_number, - signature, + serialization_revision, + credential_data: credential_data.to_vec(), + credential_type, epoch_id, consumed: false, - }); + }) } /// Tries to retrieve one of the stored, unused credentials. - pub async fn get_next_coconut_credential(&self) -> Option { + pub async fn get_next_unspent_credential(&self) -> Option { let creds = self.inner.read().await; - creds.iter().find(|c| !c.consumed).cloned() + creds.data.iter().find(|c| !c.consumed).cloned() } /// Consumes in the database the specified credential. @@ -63,7 +64,7 @@ impl CoconutCredentialManager { /// * `id`: Database id. pub async fn consume_coconut_credential(&self, id: i64) { let mut creds = self.inner.write().await; - if let Some(cred) = creds.get_mut(id as usize) { + if let Some(cred) = creds.data.get_mut(id as usize) { cred.consumed = true; } } diff --git a/common/credential-storage/src/backends/sqlite.rs b/common/credential-storage/src/backends/sqlite.rs index f64ef811b0..d007c03c16 100644 --- a/common/credential-storage/src/backends/sqlite.rs +++ b/common/credential-storage/src/backends/sqlite.rs @@ -1,7 +1,7 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::models::CoconutCredential; +use crate::models::StoredIssuedCredential; #[derive(Clone)] pub struct CoconutCredentialManager { @@ -18,43 +18,29 @@ impl CoconutCredentialManager { CoconutCredentialManager { connection_pool } } - /// Inserts provided signature into the database. - /// - /// # Arguments - /// - /// * `voucher_value`: Plaintext bandwidth value of the credential. - /// * `voucher_info`: Plaintext information of the credential. - /// * `serial_number`: Base58 representation of the serial number attribute. - /// * `binding_number`: Base58 representation of the binding number attribute. - /// * `signature`: Coconut credential in the form of a signature. - pub async fn insert_coconut_credential( + pub async fn insert_issued_credential( &self, - voucher_value: String, - voucher_info: String, - serial_number: String, - binding_number: String, - signature: String, - epoch_id: String, + credential_type: String, + serialization_revision: u8, + credential_data: &[u8], + epoch_id: u32, ) -> Result<(), sqlx::Error> { sqlx::query!( - "INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature, epoch_id, consumed) VALUES (?, ?, ?, ?, ?, ?, ?)", - voucher_value, voucher_info, serial_number, binding_number, signature, epoch_id, false - ) - .execute(&self.connection_pool) - .await?; + r#" + INSERT INTO coconut_credentials(serialization_revision, credential_type, credential_data, epoch_id, consumed) + VALUES (?, ?, ?, ?, false) + "#, + serialization_revision, credential_type, credential_data, epoch_id + ).execute(&self.connection_pool).await?; Ok(()) } - /// Tries to retrieve one of the stored, unused credentials. - pub async fn get_next_coconut_credential( + pub async fn get_next_unspent_credential( &self, - ) -> Result, sqlx::Error> { - sqlx::query_as!( - CoconutCredential, - "SELECT * FROM coconut_credentials WHERE NOT consumed" - ) - .fetch_optional(&self.connection_pool) - .await + ) -> Result, sqlx::Error> { + sqlx::query_as("SELECT * FROM coconut_credentials WHERE NOT consumed LIMIT 1") + .fetch_optional(&self.connection_pool) + .await } /// Consumes in the database the specified credential. diff --git a/common/credential-storage/src/ephemeral_storage.rs b/common/credential-storage/src/ephemeral_storage.rs index 9a4dd561ba..aa6756ac78 100644 --- a/common/credential-storage/src/ephemeral_storage.rs +++ b/common/credential-storage/src/ephemeral_storage.rs @@ -3,7 +3,7 @@ use crate::backends::memory::CoconutCredentialManager; use crate::error::StorageError; -use crate::models::{CoconutCredential, StoredIssuedCredential}; +use crate::models::{StorableIssuedCredential, StoredIssuedCredential}; use crate::storage::Storage; use async_trait::async_trait; @@ -27,43 +27,31 @@ impl Default for EphemeralStorage { impl Storage for EphemeralStorage { type StorageError = StorageError; - async fn insert_coconut_credential( + async fn insert_issued_credential<'a>( &self, - voucher_value: String, - voucher_info: String, - serial_number: String, - binding_number: String, - signature: String, - epoch_id: String, + bandwidth_credential: StorableIssuedCredential<'a>, ) -> Result<(), StorageError> { self.coconut_credential_manager - .insert_coconut_credential( - voucher_value, - voucher_info, - serial_number, - binding_number, - signature, - epoch_id, + .insert_issued_credential( + bandwidth_credential.credential_type, + bandwidth_credential.serialization_revision, + bandwidth_credential.credential_data, + bandwidth_credential.epoch_id, ) .await; - Ok(()) } - async fn get_next_coconut_credential(&self) -> Result { - let credential = self - .coconut_credential_manager - .get_next_coconut_credential() - .await - .ok_or(StorageError::NoCredential)?; - - Ok(credential) - } - async fn get_next_unspent_credential( &self, ) -> Result { - todo!() + let credential = self + .coconut_credential_manager + .get_next_unspent_credential() + .await + .ok_or(StorageError::NoCredential)?; + + Ok(credential) } async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> { diff --git a/common/credential-storage/src/models.rs b/common/credential-storage/src/models.rs index 47372dd2b6..2695a190ad 100644 --- a/common/credential-storage/src/models.rs +++ b/common/credential-storage/src/models.rs @@ -1,31 +1,38 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -#[derive(Clone)] -pub struct CoconutCredential { - #[allow(dead_code)] - pub id: i64, - pub voucher_value: String, - pub voucher_info: String, - pub serial_number: String, - pub binding_number: String, - pub signature: String, - pub epoch_id: String, - pub consumed: bool, -} +use zeroize::{Zeroize, ZeroizeOnDrop}; + +// #[derive(Clone)] +// pub struct CoconutCredential { +// #[allow(dead_code)] +// pub id: i64, +// pub voucher_value: String, +// pub voucher_info: String, +// pub serial_number: String, +// pub binding_number: String, +// pub signature: String, +// pub epoch_id: String, +// pub consumed: bool, +// } #[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))] +#[derive(Zeroize, ZeroizeOnDrop, Clone)] pub struct StoredIssuedCredential { pub id: i64, - pub serial_number: String, - pub binding_number: String, + pub serialization_revision: u8, + pub credential_data: Vec, + pub credential_type: String, - pub signature: String, - - pub variant_type: String, - pub serialized_variant_data: String, - - pub epoch_id: String, + pub epoch_id: u32, pub consumed: bool, } + +pub struct StorableIssuedCredential<'a> { + pub serialization_revision: u8, + pub credential_data: &'a [u8], + pub credential_type: String, + + pub epoch_id: u32, +} diff --git a/common/credential-storage/src/persistent_storage.rs b/common/credential-storage/src/persistent_storage.rs index 4fffc60777..b6772fdf56 100644 --- a/common/credential-storage/src/persistent_storage.rs +++ b/common/credential-storage/src/persistent_storage.rs @@ -5,7 +5,7 @@ use crate::backends::sqlite::CoconutCredentialManager; use crate::error::StorageError; use crate::storage::Storage; -use crate::models::{CoconutCredential, StoredIssuedCredential}; +use crate::models::{StorableIssuedCredential, StoredIssuedCredential}; use async_trait::async_trait; use log::{debug, error}; use sqlx::ConnectOptions; @@ -58,45 +58,34 @@ impl PersistentStorage { impl Storage for PersistentStorage { type StorageError = StorageError; - async fn insert_coconut_credential( + async fn insert_issued_credential<'a>( &self, - voucher_value: String, - voucher_info: String, - serial_number: String, - binding_number: String, - signature: String, - epoch_id: String, - ) -> Result<(), StorageError> { + bandwidth_credential: StorableIssuedCredential<'a>, + ) -> Result<(), Self::StorageError> { self.coconut_credential_manager - .insert_coconut_credential( - voucher_value, - voucher_info, - serial_number, - binding_number, - signature, - epoch_id, + .insert_issued_credential( + bandwidth_credential.credential_type, + bandwidth_credential.serialization_revision, + bandwidth_credential.credential_data, + bandwidth_credential.epoch_id, ) .await?; Ok(()) } - async fn get_next_coconut_credential(&self) -> Result { + async fn get_next_unspent_credential( + &self, + ) -> Result { let credential = self .coconut_credential_manager - .get_next_coconut_credential() + .get_next_unspent_credential() .await? .ok_or(StorageError::NoCredential)?; Ok(credential) } - async fn get_next_unspent_credential( - &self, - ) -> Result { - todo!() - } - async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> { self.coconut_credential_manager .consume_coconut_credential(id) diff --git a/common/credential-storage/src/storage.rs b/common/credential-storage/src/storage.rs index af16a4a8de..64fe0d6e82 100644 --- a/common/credential-storage/src/storage.rs +++ b/common/credential-storage/src/storage.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::models::{CoconutCredential, StoredIssuedCredential}; +use crate::models::{StorableIssuedCredential, StoredIssuedCredential}; use async_trait::async_trait; use std::error::Error; @@ -9,31 +9,11 @@ use std::error::Error; pub trait Storage: Send + Sync { type StorageError: Error; - /// Inserts provided signature into the database. - /// - /// # Arguments - /// - /// * `voucher_value`: How much bandwidth is in the credential. - /// * `voucher_info`: What type of credential it is. - /// * `serial_number`: Serial number of the credential. - /// * `binding_number`: Binding number of the credential. - /// * `signature`: Coconut credential in the form of a signature. - /// * `epoch_id`: The epoch when it was signed. - #[deprecated] - async fn insert_coconut_credential( + async fn insert_issued_credential<'a>( &self, - voucher_value: String, - voucher_info: String, - serial_number: String, - binding_number: String, - signature: String, - epoch_id: String, + bandwidth_credential: StorableIssuedCredential<'a>, ) -> Result<(), Self::StorageError>; - /// Tries to retrieve one of the stored, unused credentials. - #[deprecated] - async fn get_next_coconut_credential(&self) -> Result; - /// Tries to retrieve one of the stored, unused credentials. async fn get_next_unspent_credential( &self, diff --git a/common/credential-utils/src/utils.rs b/common/credential-utils/src/utils.rs index 8e773a3225..2b2aaba9f7 100644 --- a/common/credential-utils/src/utils.rs +++ b/common/credential-utils/src/utils.rs @@ -44,7 +44,7 @@ where let state = nym_bandwidth_controller::acquire::deposit(client, amount.clone()).await?; - if nym_bandwidth_controller::acquire::get_credential(&state, client, persistent_storage) + if nym_bandwidth_controller::acquire::get_bandwidth_voucher(&state, client, persistent_storage) .await .is_err() { @@ -142,7 +142,8 @@ where let state = State::new(voucher); if let Err(e) = - nym_bandwidth_controller::acquire::get_credential(&state, client, shared_storage).await + nym_bandwidth_controller::acquire::get_bandwidth_voucher(&state, client, shared_storage) + .await { error!("Could not recover deposit {voucher_name} due to {e}, try again later",) } else { diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index bc096fa5a9..053e0f80fc 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -13,6 +13,7 @@ cosmrs = { workspace = true } thiserror = { workspace = true } log = { workspace = true } time = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } zeroize = { workspace = true } # I guess temporarily until we get serde support in coconut up and running @@ -20,7 +21,6 @@ nym-credentials-interface = { path = "../credentials-interface" } nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "serde"] } nym-api-requests = { path = "../../nym-api/nym-api-requests" } nym-validator-client = { path = "../client-libs/validator-client", default-features = false } -serde = { workspace = true, features = ["derive"] } [dev-dependencies] rand = "0.7.3" diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs index 67fab7197b..0a1707ba3b 100644 --- a/common/credentials/src/coconut/bandwidth/freepass.rs +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -30,7 +30,7 @@ impl FreePassIssuedData { } } -#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +#[derive(Zeroize, Serialize, Deserialize)] pub struct FreePassIssuanceData { /// the plain validity value of this credential expressed as unix timestamp #[zeroize(skip)] diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 3ef9352782..02eb1a3c6c 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -238,9 +238,17 @@ impl IssuanceBandwidthCredential { .map_err(Error::SignatureAggregationError) } + // also drops self after the conversion pub fn into_issued_credential( self, aggregate_signature: Signature, + ) -> IssuedBandwidthCredential { + self.to_issued_credential(aggregate_signature) + } + + pub fn to_issued_credential( + &self, + aggregate_signature: Signature, ) -> IssuedBandwidthCredential { IssuedBandwidthCredential::new( self.serial_number, diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index 8a66e36365..62d5a4ecc7 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -1,23 +1,25 @@ // Copyright 2024 - Nym Technologies SA // 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::{ - bandwidth_credential_params, CredentialSpendingData, CredentialType, -}; +use crate::coconut::bandwidth::{CredentialSpendingData, CredentialType}; +use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; +use nym_credentials_interface::prove_bandwidth_credential; use nym_credentials_interface::{ - prove_bandwidth_credential, Parameters, PrivateAttribute, PublicAttribute, Signature, - VerificationKey, + Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey, }; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; -#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +pub const CURRENT_SERIALIZATION_REVISION: u8 = 1; + +#[derive(Zeroize, Serialize, Deserialize)] pub enum BandwidthCredentialIssuedDataVariant { Voucher(BandwidthVoucherIssuedData), FreePass(FreePassIssuedData), @@ -68,13 +70,15 @@ 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)] +#[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 @@ -85,6 +89,7 @@ pub struct IssuedBandwidthCredential { variant_data: BandwidthCredentialIssuedDataVariant, /// type of the bandwdith credential hashed onto a scalar + #[serde(with = "scalar_serde_helper")] type_prehashed: PublicAttribute, } @@ -105,6 +110,28 @@ impl IssuedBandwidthCredential { } } + pub fn current_serialization_revision(&self) -> u8 { + CURRENT_SERIALIZATION_REVISION + } + + /// Pack (serialize) this credential data into a stream of bytes using v1 serializer. + pub fn pack_v1(&self) -> Vec { + use bincode::Options; + // safety: our data format is stable and thus the serialization should not fail + make_storable_bincode_serializer().serialize(self).unwrap() + } + + /// Unpack (deserialize) the credential data from the given bytes using v1 serializer. + pub fn unpack_v1(bytes: &[u8]) -> Result { + use bincode::Options; + Ok(make_storable_bincode_serializer().deserialize(bytes)?) + } + + 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() } @@ -142,3 +169,10 @@ impl IssuedBandwidthCredential { }) } } + +fn make_storable_bincode_serializer() -> impl bincode::Options { + use bincode::Options; + bincode::DefaultOptions::new() + .with_big_endian() + .with_varint_encoding() +} diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 2cfd6ab068..ca362dc721 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3915,6 +3915,7 @@ dependencies = [ "sqlx", "thiserror", "tokio", + "zeroize", ] [[package]] diff --git a/sdk/rust/nym-sdk/src/bandwidth/client.rs b/sdk/rust/nym-sdk/src/bandwidth/client.rs index c5756e0cdc..5051e5a1d4 100644 --- a/sdk/rust/nym-sdk/src/bandwidth/client.rs +++ b/sdk/rust/nym-sdk/src/bandwidth/client.rs @@ -57,7 +57,7 @@ where pub async fn acquire(&self, amount: u128) -> Result<()> { let amount = Coin::new(amount, &self.network_details.chain_details.mix_denom.base); let state = nym_bandwidth_controller::acquire::deposit(&self.client, amount).await?; - nym_bandwidth_controller::acquire::get_credential(&state, &self.client, self.storage) + nym_bandwidth_controller::acquire::get_bandwidth_voucher(&state, &self.client, self.storage) .await .map_err(|reason| Error::UnconvertedDeposit { reason, @@ -71,8 +71,12 @@ where let voucher = IssuanceBandwidthCredential::try_from_recovered_bytes(voucher_blob) .map_err(|_| Error::InvalidVoucherBlob)?; let state = State::new(voucher); - nym_bandwidth_controller::acquire::get_credential(&state, &self.client, self.storage) - .await?; + nym_bandwidth_controller::acquire::get_bandwidth_voucher( + &state, + &self.client, + self.storage, + ) + .await?; Ok(()) } diff --git a/sdk/rust/nym-sdk/src/mixnet.rs b/sdk/rust/nym-sdk/src/mixnet.rs index 9dc4dd9681..b54ef462ee 100644 --- a/sdk/rust/nym-sdk/src/mixnet.rs +++ b/sdk/rust/nym-sdk/src/mixnet.rs @@ -59,8 +59,8 @@ pub use nym_client_core::{ config::{GatewayEndpointConfig, GroupBy}, }; pub use nym_credential_storage::{ - ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage, models::CoconutCredential, - storage::Storage as CredentialStorage, + ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage, + models::StoredIssuedCredential, storage::Storage as CredentialStorage, }; pub use nym_network_defaults::NymNetworkDetails; pub use nym_socks5_client_core::config::Socks5; From ad9aee0ec0e33e843ba4bb12f9c6f0448e051069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 8 Feb 2024 16:10:10 +0000 Subject: [PATCH 11/49] missing serialization --- Cargo.lock | 1 + common/bandwidth-controller/src/error.rs | 3 + common/bandwidth-controller/src/utils.rs | 41 ++--- common/credentials-interface/Cargo.toml | 1 + common/credentials-interface/src/lib.rs | 26 ++- .../src/coconut/bandwidth/issuance.rs | 8 +- common/nymcoconut/src/lib.rs | 1 + common/nymcoconut/src/scheme/keygen.rs | 1 - gateway/gateway-requests/Cargo.toml | 1 + gateway/gateway-requests/src/lib.rs | 5 +- gateway/gateway-requests/src/models.rs | 153 +++++++++++++++++- gateway/gateway-requests/src/types.rs | 16 +- nym-connect/desktop/Cargo.lock | 1 + nym-wallet/Cargo.lock | 1 + 14 files changed, 215 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a90787a0f..a91515295e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5388,6 +5388,7 @@ dependencies = [ "bls12_381", "nym-coconut", "serde", + "thiserror", ] [[package]] diff --git a/common/bandwidth-controller/src/error.rs b/common/bandwidth-controller/src/error.rs index 7b4e787cb5..4adda09b0f 100644 --- a/common/bandwidth-controller/src/error.rs +++ b/common/bandwidth-controller/src/error.rs @@ -45,4 +45,7 @@ pub enum BandwidthControllerError { #[error("Threshold not set yet")] NoThreshold, + + #[error("can't handle recovering storage with revision {stored}. {expected} was expected")] + UnsupportedCredentialStorageRevision { stored: u8, expected: u8 }, } diff --git a/common/bandwidth-controller/src/utils.rs b/common/bandwidth-controller/src/utils.rs index 690466daa5..89891dba48 100644 --- a/common/bandwidth-controller/src/utils.rs +++ b/common/bandwidth-controller/src/utils.rs @@ -2,40 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BandwidthControllerError; -use nym_credential_storage::models::{StorableIssuedCredential, StoredIssuedCredential}; +use nym_credential_storage::models::StoredIssuedCredential; +use nym_credentials::coconut::bandwidth::issued::CURRENT_SERIALIZATION_REVISION; use nym_credentials::coconut::bandwidth::IssuedBandwidthCredential; -use nym_validator_client::nym_api::EpochId; pub fn stored_credential_to_issued_bandwidth( cred: StoredIssuedCredential, ) -> Result { - /* - let bandwidth_credential = self - .storage - .get_next_coconut_credential() - .await - .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?; - let voucher_value = u64::from_str(&bandwidth_credential.voucher_value) - .map_err(|_| StorageError::InconsistentData)?; - let voucher_info = bandwidth_credential.voucher_info.clone(); - let serial_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58( - bandwidth_credential.serial_number, - )?); - let binding_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58( - bandwidth_credential.binding_number, - )?); - let signature = - nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?; - let epoch_id = u64::from_str(&bandwidth_credential.epoch_id) - .map_err(|_| StorageError::InconsistentData)?; + if cred.serialization_revision != CURRENT_SERIALIZATION_REVISION { + return Err( + BandwidthControllerError::UnsupportedCredentialStorageRevision { + stored: cred.serialization_revision, + expected: CURRENT_SERIALIZATION_REVISION, + }, + ); + } - */ - todo!() -} - -pub fn issued_bandwidth_to_stored_credential( - issued: IssuedBandwidthCredential, - epoch_id: EpochId, -) -> StoredIssuedCredential { - todo!() + Ok(IssuedBandwidthCredential::unpack_v1(&cred.credential_data)?) } diff --git a/common/credentials-interface/Cargo.toml b/common/credentials-interface/Cargo.toml index 789552ebff..5393ea37fd 100644 --- a/common/credentials-interface/Cargo.toml +++ b/common/credentials-interface/Cargo.toml @@ -13,5 +13,6 @@ license.workspace = true [dependencies] bls12_381 = { workspace = true, default-features = false } serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } nym-coconut = { path = "../nymcoconut" } diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index f962bf8c9e..36af1ef380 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -4,9 +4,11 @@ use bls12_381::Scalar; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; +use std::str::FromStr; +use thiserror::Error; pub use nym_coconut::{ - aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, + aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, keygen, prepare_blind_sign, prove_bandwidth_credential, verify_credential, Attribute, Base58, BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey, @@ -20,12 +22,30 @@ pub const FREE_PASS_INFO_TYPE: &str = "FreeBandwidthPass"; // fn prove_credential(&self) -> Result<(), ()>; // } -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[derive(Debug, Error)] +#[error("{0} is not a valid credential type")] +pub struct UnknownCredentialType(String); + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum CredentialType { Voucher, FreePass, } +impl FromStr for CredentialType { + type Err = UnknownCredentialType; + + fn from_str(s: &str) -> Result { + if s == VOUCHER_INFO_TYPE { + Ok(CredentialType::Voucher) + } else if s == FREE_PASS_INFO_TYPE { + Ok(CredentialType::FreePass) + } else { + Err(UnknownCredentialType(s.to_string())) + } + } +} + impl CredentialType { pub fn validate(&self, type_plain: &str) -> bool { match self { @@ -63,7 +83,7 @@ pub struct CredentialSigningData { pub typ: CredentialType, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CredentialSpendingData { pub embedded_private_attributes: usize, diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 02eb1a3c6c..1bba208cc5 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -127,6 +127,10 @@ impl IssuanceBandwidthCredential { )) } + pub fn new_freepass(expiry_date: Option) -> Self { + Self::new(FreePassIssuanceData::new(expiry_date)) + } + pub fn blind_serial_number_in_g1subgroup(&self) -> G1Projective { bandwidth_credential_params().gen1() * self.serial_number } @@ -137,10 +141,6 @@ impl IssuanceBandwidthCredential { self.blind_serial_number_in_g1subgroup().to_bs58() } - pub fn new_freepass(expiry_date: Option) -> Self { - Self::new(FreePassIssuanceData::new(expiry_date)) - } - pub fn typ(&self) -> CredentialType { self.variant_data.info() } diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs index ed258cd557..4ac8245c37 100644 --- a/common/nymcoconut/src/lib.rs +++ b/common/nymcoconut/src/lib.rs @@ -14,6 +14,7 @@ pub use scheme::issuance::blind_sign; pub use scheme::issuance::prepare_blind_sign; pub use scheme::issuance::verify_partial_blind_signature; pub use scheme::issuance::BlindSignRequest; +pub use scheme::keygen::keygen; pub use scheme::keygen::ttp_keygen; pub use scheme::keygen::KeyPair; pub use scheme::keygen::SecretKey; diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index 966ebb141c..50d735ef92 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -565,7 +565,6 @@ impl TryFrom<&[u8]> for KeyPair { /// Generate a single Coconut keypair ((x, y0, y1...), (g2^x, g2^y0, ...)). /// It is not suitable for threshold credentials as all subsequent calls to `keygen` generate keys /// that are independent of each other. -#[cfg(test)] pub fn keygen(params: &Parameters) -> KeyPair { let attributes = params.gen_hs().len(); diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index d63170628e..e771a71de1 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -32,3 +32,4 @@ nym-credentials-interface = { path = "../../common/credentials-interface" } workspace = true default-features = false + diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index 9bb25dc45c..e27d272f23 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -15,7 +15,10 @@ pub mod types; /// Defines the current version of the communication protocol between gateway and clients. /// It has to be incremented for any breaking change. -pub const PROTOCOL_VERSION: u8 = 1; +// history: +// 1 - initial release +// 2 - changes to client credentials structure +pub const PROTOCOL_VERSION: u8 = 2; pub type GatewayMac = HmacOutput; diff --git a/gateway/gateway-requests/src/models.rs b/gateway/gateway-requests/src/models.rs index 4e1df1d61e..6e06aabc7e 100644 --- a/gateway/gateway-requests/src/models.rs +++ b/gateway/gateway-requests/src/models.rs @@ -3,10 +3,10 @@ use crate::GatewayRequestsError; use nym_credentials::coconut::bandwidth::CredentialSpendingData; -use nym_credentials_interface::CoconutError; +use nym_credentials_interface::{CoconutError, VerifyCredentialRequest}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CredentialSpendingWithEpoch { /// The cryptographic material required for spending the underlying credential. pub data: CredentialSpendingData, @@ -16,6 +16,23 @@ pub struct CredentialSpendingWithEpoch { pub epoch_id: u64, } +// just a helper macro for checking required length and advancing the buffer +macro_rules! ensure_len_and_advance { + ($b:expr, $n:expr) => {{ + if $b.len() < $n { + return Err(GatewayRequestsError::CredentialDeserializationFailureEOF); + } + // create binding to the desired range + let bytes = &$b[..$n]; + + // update the initial binding + + $b = &$b[$n..]; + + bytes + }}; +} + impl CredentialSpendingWithEpoch { pub fn new(data: CredentialSpendingData, epoch_id: u64) -> Self { CredentialSpendingWithEpoch { data, epoch_id } @@ -39,10 +56,138 @@ impl CredentialSpendingWithEpoch { } pub fn to_bytes(&self) -> Vec { - todo!() + // simple length prefixed serialization + // TODO: change it to a standard format instead + let mut bytes = Vec::new(); + + let embedded_private = (self.data.embedded_private_attributes as u32).to_be_bytes(); + let theta = self.data.verify_credential_request.to_bytes(); + let theta_len = (theta.len() as u32).to_be_bytes(); + + let public = (self.data.public_attributes_plain.len() as u32).to_be_bytes(); + let typ = self.data.typ.to_string(); + let typ_bytes = typ.as_bytes(); + let typ_len = (typ_bytes.len() as u32).to_be_bytes(); + + bytes.extend_from_slice(&embedded_private); + bytes.extend_from_slice(&theta_len); + bytes.extend_from_slice(&theta); + bytes.extend_from_slice(&public); + + for pub_element in &self.data.public_attributes_plain { + let bytes_el = pub_element.as_bytes(); + let len = (bytes_el.len() as u32).to_be_bytes(); + + bytes.extend_from_slice(&len); + bytes.extend_from_slice(bytes_el); + } + + bytes.extend_from_slice(&typ_len); + bytes.extend_from_slice(typ_bytes); + bytes.extend_from_slice(&self.epoch_id.to_be_bytes()); + + bytes } pub fn try_from_bytes(raw: &[u8]) -> Result { - todo!() + // initial binding + let mut b = raw; + let embedded_private_bytes = ensure_len_and_advance!(b, 4); + let embedded_private_attributes = + u32::from_be_bytes(embedded_private_bytes.try_into().unwrap()) as usize; + + let theta_len_bytes = ensure_len_and_advance!(b, 4); + let theta_len = u32::from_be_bytes(theta_len_bytes.try_into().unwrap()) as usize; + + let theta_bytes = ensure_len_and_advance!(b, theta_len); + let theta = VerifyCredentialRequest::from_bytes(theta_bytes) + .map_err(GatewayRequestsError::CredentialDeserializationFailureMalformedTheta)?; + + let public_bytes = ensure_len_and_advance!(b, 4); + let public = u32::from_be_bytes(public_bytes.try_into().unwrap()) as usize; + + let mut public_attributes_plain = Vec::with_capacity(public); + for _ in 0..public { + let element_len_bytes = ensure_len_and_advance!(b, 4); + let element_len = u32::from_be_bytes(element_len_bytes.try_into().unwrap()) as usize; + + let element_bytes = ensure_len_and_advance!(b, element_len); + let element = String::from_utf8(element_bytes.to_vec())?; + public_attributes_plain.push(element); + } + + let typ_len_bytes = ensure_len_and_advance!(b, 4); + let typ_len = u32::from_be_bytes(typ_len_bytes.try_into().unwrap()) as usize; + + let typ_bytes = ensure_len_and_advance!(b, typ_len); + let raw_typ = String::from_utf8(typ_bytes.to_vec())?; + let typ = raw_typ.parse()?; + + // tell the linter to chill out in for this last iteration + #[allow(unused_assignments)] + let epoch_id_bytes = ensure_len_and_advance!(b, 8); + let epoch_id = u64::from_be_bytes(epoch_id_bytes.try_into().unwrap()); + + Ok(CredentialSpendingWithEpoch { + data: CredentialSpendingData { + embedded_private_attributes, + verify_credential_request: theta, + public_attributes_plain, + typ, + }, + epoch_id, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nym_credentials::coconut::bandwidth::bandwidth_credential_params; + use nym_credentials::IssuanceBandwidthCredential; + use nym_credentials_interface::{blind_sign, hash_to_scalar}; + + #[test] + fn credential_roundtrip() { + // make valid request + let params = bandwidth_credential_params(); + let keypair = nym_credentials_interface::keygen(params); + + let issuance = IssuanceBandwidthCredential::new_freepass(None); + let sig_req = issuance.prepare_for_signing(); + let pub_attrs_hashed = sig_req + .public_attributes_plain + .iter() + .map(hash_to_scalar) + .collect::>(); + let pub_attrs = pub_attrs_hashed.iter().collect::>(); + let blind_sig = blind_sign( + params, + keypair.secret_key(), + &sig_req.blind_sign_request, + &pub_attrs, + ) + .unwrap(); + let sig = blind_sig + .unblind( + keypair.verification_key(), + &sig_req.pedersen_commitments_openings, + ) + .unwrap(); + + let issued = issuance.into_issued_credential(sig); + let spending = issued + .prepare_for_spending(keypair.verification_key()) + .unwrap(); + + let with_epoch = CredentialSpendingWithEpoch { + data: spending, + epoch_id: 42, + }; + + let bytes = with_epoch.to_bytes(); + let recovered = CredentialSpendingWithEpoch::try_from_bytes(&bytes).unwrap(); + + assert_eq!(with_epoch, recovered); } } diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 0c5542464e..67ccb148f7 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -1,4 +1,4 @@ -// Copyright 2020 - Nym Technologies SA +// Copyright 2020-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::authentication::encrypted_address::EncryptedAddressBytes; @@ -8,6 +8,7 @@ use crate::registration::handshake::SharedKeys; use crate::{GatewayMacSize, PROTOCOL_VERSION}; use log::error; use nym_credentials::coconut::bandwidth::CredentialSpendingData; +use nym_credentials_interface::{CoconutError, UnknownCredentialType}; use nym_crypto::generic_array::typenum::Unsigned; use nym_crypto::hmac::recompute_keyed_hmac_and_verify_tag; use nym_crypto::symmetric::stream_cipher; @@ -18,6 +19,7 @@ use nym_sphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorit use nym_sphinx::DestinationAddressBytes; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; +use std::string::FromUtf8Error; use thiserror::Error; use tungstenite::protocol::Message; @@ -102,6 +104,18 @@ pub enum GatewayRequestsError { #[from] source: MixPacketFormattingError, }, + + #[error("failed to deserialize provided credential: EOF")] + CredentialDeserializationFailureEOF, + + #[error("failed to deserialize provided credential: malformed string: {0}")] + CredentialDeserializationFailureMalformedString(#[from] FromUtf8Error), + + #[error("failed to deserialize provided credential: {0}")] + CredentialDeserializationFailureUnknownType(#[from] UnknownCredentialType), + + #[error("failed to deserialize provided credential: malformed verify request: {0}")] + CredentialDeserializationFailureMalformedTheta(CoconutError), } #[derive(Serialize, Deserialize, Debug)] diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index ca362dc721..6480b0a255 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3943,6 +3943,7 @@ dependencies = [ "bls12_381", "nym-coconut", "serde", + "thiserror", ] [[package]] diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 8c3109210f..e9cdff7476 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3198,6 +3198,7 @@ dependencies = [ "bls12_381", "nym-coconut", "serde", + "thiserror", ] [[package]] From 9a3bd7a2a964706b5be7a3ff43a3eaf39e3b9909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 8 Feb 2024 17:14:21 +0000 Subject: [PATCH 12/49] clippy and fixing tests --- common/credentials-interface/src/lib.rs | 2 +- .../src/coconut/bandwidth/issuance.rs | 14 +- .../src/coconut/bandwidth/voucher.rs | 414 +----------------- common/network-defaults/src/lib.rs | 3 - gateway/src/node/client_handling/bandwidth.rs | 2 +- nym-api/src/coconut/deposit.rs | 13 +- .../src/coconut/tests/issued_credentials.rs | 47 +- nym-api/src/coconut/tests/mod.rs | 261 ++++++----- 8 files changed, 190 insertions(+), 566 deletions(-) diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index 36af1ef380..f2be1ef70f 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -83,7 +83,7 @@ pub struct CredentialSigningData { pub typ: CredentialType, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct CredentialSpendingData { pub embedded_private_attributes: usize, diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 1bba208cc5..bf166313a6 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -65,6 +65,13 @@ impl BandwidthCredentialIssuanceDataVariant { } } } + + pub fn voucher_data(&self) -> Option<&BandwidthVoucherIssuanceData> { + match self { + BandwidthCredentialIssuanceDataVariant::Voucher(voucher) => Some(voucher), + _ => None, + } + } } // all types of bandwidth credentials contain serial number and binding number @@ -114,7 +121,7 @@ impl IssuanceBandwidthCredential { } pub fn new_voucher( - value: Coin, + value: impl Into, deposit_tx_hash: Hash, signing_key: identity::PrivateKey, unused_ed25519: encryption::PrivateKey, @@ -135,6 +142,7 @@ impl IssuanceBandwidthCredential { bandwidth_credential_params().gen1() * self.serial_number } + // NOT TO BE CONFUSED WITH BLINDED SERIAL NUMBER IN CREDENTIAL ITSELF pub fn blinded_g1_serial_number_bs58(&self) -> String { use nym_credentials_interface::Base58; @@ -160,6 +168,10 @@ impl IssuanceBandwidthCredential { ] } + pub fn get_variant_data(&self) -> &BandwidthCredentialIssuanceDataVariant { + &self.variant_data + } + pub fn get_bandwidth_attribute(&self) -> String { self.variant_data.public_value_plain() } diff --git a/common/credentials/src/coconut/bandwidth/voucher.rs b/common/credentials/src/coconut/bandwidth/voucher.rs index 6bf4e55dcb..06866d9606 100644 --- a/common/credentials/src/coconut/bandwidth/voucher.rs +++ b/common/credentials/src/coconut/bandwidth/voucher.rs @@ -59,11 +59,12 @@ pub struct BandwidthVoucherIssuanceData { impl BandwidthVoucherIssuanceData { pub fn new( - value: Coin, + value: impl Into, deposit_tx_hash: Hash, signing_key: identity::PrivateKey, unused_ed25519: encryption::PrivateKey, ) -> Self { + let value = value.into(); let value_prehashed = hash_to_scalar(value.amount.to_string()); BandwidthVoucherIssuanceData { @@ -130,414 +131,3 @@ impl BandwidthVoucherIssuanceData { &self.unused_ed25519 } } -// -// #[deprecated] -// #[derive(Zeroize, ZeroizeOnDrop)] -// pub struct BandwidthVoucher { -// // private attributes -// /// a random secret value generated by the client used for double-spending detection -// serial_number: PrivateAttribute, -// -// /// a random secret value generated by the client used to bind multiple credentials together -// binding_number: PrivateAttribute, -// -// // public atttributes: -// /// the plain text value (e.g., bandwidth) encoded in this voucher -// // TODO: in another PR change the value from `"1000"` to `"1000unym"` -// voucher_value_plain: String, -// -// /// the plain text information -// voucher_info_plain: String, -// -// /// the precomputed value (e.g., bandwidth) encoded in this voucher -// _voucher_value_prehashed: PublicAttribute, -// -// /// the precomputed field with public information, e.g., type of voucher, interval etc. -// _voucher_info_prehashed: PublicAttribute, -// -// /// the hash of the deposit transaction -// #[zeroize(skip)] -// tx_hash: Hash, -// -// /// base58 encoded private key ensuring the depositer requested these attributes -// signing_key: identity::PrivateKey, -// -// /// base58 encoded private key ensuring only this client receives the signature share -// unused_ed25519: encryption::PrivateKey, -// -// pedersen_commitments_openings: Vec, -// -// #[zeroize(skip)] -// blind_sign_request: BlindSignRequest, -// } -// -// impl BandwidthVoucher { -// 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() -// } -// -// pub fn new( -// params: &Parameters, -// voucher_value: String, -// voucher_info: String, -// tx_hash: Hash, -// signing_key: identity::PrivateKey, -// encryption_key: encryption::PrivateKey, -// ) -> Self { -// let serial_number = params.random_scalar(); -// let binding_number = params.random_scalar(); -// let voucher_value_plain = voucher_value.clone(); -// let voucher_info_plain = voucher_info.clone(); -// -// let _voucher_value_prehashed = hash_to_scalar(voucher_value); -// let _voucher_info_prehashed = hash_to_scalar(voucher_info); -// -// let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign( -// params, -// &[&serial_number, &binding_number], -// &[&_voucher_value_prehashed, &_voucher_info_prehashed], -// ) -// .unwrap(); -// BandwidthVoucher { -// serial_number, -// binding_number, -// _voucher_value_prehashed, -// voucher_value_plain, -// _voucher_info_prehashed, -// voucher_info_plain, -// tx_hash, -// signing_key, -// unused_ed25519: encryption_key, -// pedersen_commitments_openings, -// blind_sign_request, -// } -// } -// -// pub fn to_bytes(&self) -> Vec { -// let serial_number_b = self.serial_number.to_bytes(); -// let binding_number_b = self.binding_number.to_bytes(); -// let voucher_value_plain_b = self.voucher_value_plain.as_bytes(); -// let voucher_info_plain_b = self.voucher_info_plain.as_bytes(); -// let tx_hash_b = self.tx_hash.as_bytes(); -// let signing_key_b = self.signing_key.to_bytes(); -// let encryption_key_b = self.unused_ed25519.to_bytes(); -// let blind_sign_request_b = self.blind_sign_request.to_bytes(); -// -// let mut ret = Vec::new(); -// -// ret.extend_from_slice(&serial_number_b); -// ret.extend_from_slice(&binding_number_b); -// ret.extend_from_slice(tx_hash_b); -// ret.extend_from_slice(&signing_key_b); -// ret.extend_from_slice(&encryption_key_b); -// ret.extend_from_slice(&(voucher_value_plain_b.len() as u64).to_be_bytes()); -// ret.extend_from_slice(&(voucher_info_plain_b.len() as u64).to_be_bytes()); -// ret.extend_from_slice(&(blind_sign_request_b.len() as u64).to_be_bytes()); -// ret.extend_from_slice(&(self.pedersen_commitments_openings.len() as u64).to_be_bytes()); -// ret.extend_from_slice(voucher_value_plain_b); -// ret.extend_from_slice(voucher_info_plain_b); -// ret.extend_from_slice(&blind_sign_request_b); -// for commitment in self.pedersen_commitments_openings.iter() { -// ret.extend_from_slice(&commitment.to_bytes()); -// } -// -// ret -// } -// -// pub fn try_from_bytes(bytes: &[u8]) -> Result { -// if bytes.len() < 32 * 5 + 4 * 8 { -// return Err(Error::BandwidthVoucherDeserializationError(format!( -// "Less then {} bytes needed", -// 32 * 5 + 4 * 8 -// ))); -// } -// let mut buff = [0u8; 32]; -// let mut small_buff = [0u8; 8]; -// let scalar_err = -// || Error::BandwidthVoucherDeserializationError(String::from("Invalid Scalar")); -// buff.copy_from_slice(&bytes[..32]); -// let serial_number = Option::::from(PrivateAttribute::from_bytes(&buff)) -// .ok_or_else(scalar_err)?; -// buff.copy_from_slice(&bytes[32..2 * 32]); -// let binding_number = Option::::from(PrivateAttribute::from_bytes(&buff)) -// .ok_or_else(scalar_err)?; -// buff.copy_from_slice(&bytes[2 * 32..3 * 32]); -// let tx_hash = Hash::from_bytes(Algorithm::Sha256, &buff).map_err(|_| { -// Error::BandwidthVoucherDeserializationError(String::from("Invalid transaction Hash")) -// })?; -// buff.copy_from_slice(&bytes[3 * 32..4 * 32]); -// let signing_key = identity::PrivateKey::from_bytes(&buff).map_err(|_| { -// Error::BandwidthVoucherDeserializationError(String::from("Invalid key")) -// })?; -// buff.copy_from_slice(&bytes[4 * 32..5 * 32]); -// let encryption_key = encryption::PrivateKey::from_bytes(&buff).map_err(|_| { -// Error::BandwidthVoucherDeserializationError(String::from("Invalid key")) -// })?; -// small_buff.copy_from_slice(&bytes[5 * 32..5 * 32 + 8]); -// let voucher_value_plain_no = u64::from_be_bytes(small_buff) as usize; -// small_buff.copy_from_slice(&bytes[5 * 32 + 8..5 * 32 + 2 * 8]); -// let voucher_info_plain_no = u64::from_be_bytes(small_buff) as usize; -// small_buff.copy_from_slice(&bytes[5 * 32 + 2 * 8..5 * 32 + 3 * 8]); -// let blind_sign_request_no = u64::from_be_bytes(small_buff) as usize; -// small_buff.copy_from_slice(&bytes[5 * 32 + 3 * 8..5 * 32 + 4 * 8]); -// let pedersen_commitments_openings_no = u64::from_be_bytes(small_buff) as usize; -// -// let total_length = 32 * 5 -// + 4 * 8 -// + voucher_value_plain_no -// + voucher_info_plain_no -// + blind_sign_request_no -// + pedersen_commitments_openings_no * 32; -// if bytes.len() != total_length { -// return Err(Error::BandwidthVoucherDeserializationError(format!( -// "Expected {total_length} bytes", -// ))); -// } -// -// let utf_err = |_| { -// Err(Error::BandwidthVoucherDeserializationError(String::from( -// "Invalid UTF8 string", -// ))) -// }; -// let mut var_length_pointer = 5 * 32 + 4 * 8; -// let voucher_value_plain = String::from_utf8( -// bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(), -// ) -// .or_else(utf_err)?; -// let _voucher_value_prehashed = hash_to_scalar(&voucher_value_plain); -// var_length_pointer += voucher_value_plain_no; -// let voucher_info_plain = String::from_utf8( -// bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(), -// ) -// .or_else(utf_err)?; -// let _voucher_info_prehashed = hash_to_scalar(&voucher_info_plain); -// var_length_pointer += voucher_info_plain_no; -// let blind_sign_request = BlindSignRequest::from_bytes( -// &bytes[var_length_pointer..var_length_pointer + blind_sign_request_no], -// )?; -// var_length_pointer += blind_sign_request_no; -// -// let mut pedersen_commitments_openings = Vec::new(); -// for _ in 0..pedersen_commitments_openings_no { -// buff.copy_from_slice(&bytes[var_length_pointer..var_length_pointer + 32]); -// let commitment = -// Option::::from(Attribute::from_bytes(&buff)).ok_or_else(scalar_err)?; -// var_length_pointer += 32; -// pedersen_commitments_openings.push(commitment); -// } -// -// Ok(Self { -// serial_number, -// binding_number, -// _voucher_value_prehashed, -// voucher_value_plain, -// _voucher_info_prehashed, -// voucher_info_plain, -// tx_hash, -// signing_key, -// unused_ed25519: encryption_key, -// pedersen_commitments_openings, -// blind_sign_request, -// }) -// } -// -// /// Check if the plain values correspond to the PublicAttributes -// pub fn verify_against_plain(values: &[&PublicAttribute], plain_values: &[String]) -> bool { -// values.len() == 2 -// && plain_values.len() == 2 -// && values[0] == &hash_to_scalar(&plain_values[0]) -// && values[1] == &hash_to_scalar(&plain_values[1]) -// } -// -// pub fn get_public_attributes(&self) -> Vec<&PublicAttribute> { -// vec![ -// &self._voucher_value_prehashed, -// &self._voucher_info_prehashed, -// ] -// } -// -// pub fn tx_hash(&self) -> Hash { -// self.tx_hash -// } -// -// pub fn identity_key(&self) -> &identity::PrivateKey { -// &self.signing_key -// } -// -// pub fn encryption_key(&self) -> &encryption::PrivateKey { -// &self.unused_ed25519 -// } -// -// pub fn pedersen_commitments_openings(&self) -> &Vec { -// &self.pedersen_commitments_openings -// } -// -// pub fn blind_sign_request(&self) -> &BlindSignRequest { -// &self.blind_sign_request -// } -// -// pub fn get_voucher_value(&self) -> String { -// self.voucher_value_plain.clone() -// } -// -// pub fn get_public_attributes_plain(&self) -> Vec { -// vec![ -// self.voucher_value_plain.clone(), -// self.voucher_info_plain.clone(), -// ] -// } -// -// pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> { -// vec![&self.serial_number, &self.binding_number] -// } -// -// pub fn signable_plaintext(request: &BlindSignRequest, tx_hash: Hash) -> Vec { -// let mut message = request.to_bytes(); -// message.extend_from_slice(tx_hash.as_bytes()); -// message -// } -// -// pub fn sign(&self) -> identity::Signature { -// let message = Self::signable_plaintext(&self.blind_sign_request, self.tx_hash); -// self.signing_key.sign(message) -// } -// } - -// pub fn prepare_for_spending( -// voucher_value: u64, -// voucher_info: String, -// serial_number: &PrivateAttribute, -// binding_number: &PrivateAttribute, -// epoch_id: u64, -// signature: &Signature, -// verification_key: &VerificationKey, -// ) -> Result { -// todo!() -// // let params = Parameters::new(BandwidthVoucher::ENCODED_ATTRIBUTES)?; -// // -// // prepare_credential_for_spending( -// // ¶ms, -// // voucher_value, -// // voucher_info, -// // serial_number, -// // binding_number, -// // epoch_id, -// // signature, -// // verification_key, -// // ) -// } - -#[cfg(test)] -mod test { - use super::*; - use cosmrs::tendermint::hash::Algorithm; - use nym_coconut_interface::Base58; - use rand::rngs::OsRng; - - fn voucher_fixture() -> BandwidthVoucher { - let params = Parameters::new(4).unwrap(); - let mut rng = OsRng; - BandwidthVoucher::new( - ¶ms, - "1234".to_string(), - "voucher info".to_string(), - Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap(), - identity::PrivateKey::from_base58_string( - identity::KeyPair::new(&mut rng) - .private_key() - .to_base58_string(), - ) - .unwrap(), - encryption::PrivateKey::from_bytes( - &encryption::KeyPair::new(&mut rng).private_key().to_bytes(), - ) - .unwrap(), - ) - } - - #[test] - fn serde_voucher() { - let voucher = voucher_fixture(); - let bytes = voucher.to_bytes(); - let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap(); - assert_eq!(voucher.serial_number, deserialized_voucher.serial_number); - assert_eq!(voucher.binding_number, deserialized_voucher.binding_number); - assert_eq!( - voucher.voucher_value_plain, - deserialized_voucher.voucher_value_plain - ); - assert_eq!( - voucher.voucher_info_plain, - deserialized_voucher.voucher_info_plain - ); - assert_eq!( - voucher._voucher_value_prehashed, - deserialized_voucher._voucher_value_prehashed - ); - assert_eq!( - voucher._voucher_info_prehashed, - deserialized_voucher._voucher_info_prehashed - ); - assert_eq!(voucher.tx_hash, deserialized_voucher.tx_hash); - assert_eq!( - voucher.signing_key.to_string(), - deserialized_voucher.signing_key.to_string() - ); - assert_eq!( - voucher.unused_ed25519.to_string(), - deserialized_voucher.unused_ed25519.to_string() - ); - assert_eq!( - voucher.pedersen_commitments_openings, - deserialized_voucher.pedersen_commitments_openings - ); - assert_eq!( - voucher.blind_sign_request.to_bs58(), - deserialized_voucher.blind_sign_request.to_bs58() - ); - } - - #[test] - fn voucher_consistency() { - let voucher = voucher_fixture(); - assert!(!BandwidthVoucher::verify_against_plain( - &[], - &voucher.get_public_attributes_plain() - )); - assert!(!BandwidthVoucher::verify_against_plain( - &voucher.get_public_attributes(), - &[], - )); - assert!(!BandwidthVoucher::verify_against_plain( - &voucher.get_public_attributes(), - &[ - voucher.get_public_attributes_plain()[0].clone(), - String::new() - ] - )); - assert!(!BandwidthVoucher::verify_against_plain( - &voucher.get_public_attributes(), - &[ - String::new(), - voucher.get_public_attributes_plain()[1].clone() - ] - )); - assert!(!BandwidthVoucher::verify_against_plain( - &[voucher.get_public_attributes()[0], &Attribute::one()], - &voucher.get_public_attributes_plain() - )); - assert!(!BandwidthVoucher::verify_against_plain( - &[&Attribute::one(), voucher.get_public_attributes()[1]], - &voucher.get_public_attributes_plain() - )); - assert!(BandwidthVoucher::verify_against_plain( - &voucher.get_public_attributes(), - &voucher.get_public_attributes_plain() - )); - } -} diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 1891cd3cc3..ffd5ff6384 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -469,9 +469,6 @@ pub const UTOKENS_TO_BURN: u64 = TOKENS_TO_BURN * 1000000; /// Default bandwidth (in bytes) that we try to buy pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN; -#[deprecated] -pub const VOUCHER_INFO: &str = "BandwidthVoucher"; - /// Defaults Cosmos Hub/ATOM path pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0"; // as set by validators in their configs diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index ce1caed6a7..47c6447a7a 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -45,7 +45,7 @@ impl Bandwidth { Bandwidth { value } } - pub fn try_from_raw_value(value: &String, typ: CredentialType) -> Result { + pub fn try_from_raw_value(value: &str, typ: CredentialType) -> Result { let bandwidth_value = match typ { CredentialType::Voucher => { diff --git a/nym-api/src/coconut/deposit.rs b/nym-api/src/coconut/deposit.rs index ab1c345991..8448342c08 100644 --- a/nym-api/src/coconut/deposit.rs +++ b/nym-api/src/coconut/deposit.rs @@ -73,17 +73,20 @@ pub async fn validate_deposit_tx(request: &BlindSignRequestBody, tx: TxResponse) #[cfg(test)] mod test { use super::*; - use crate::coconut::tests::{tx_entry_fixture, voucher_request_fixture}; + use crate::coconut::tests::{tx_entry_fixture, voucher_fixture}; use cosmwasm_std::coin; use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut::BlindSignRequest; use nym_coconut_bandwidth_contract_common::events::DEPOSITED_FUNDS_EVENT_TYPE; - use nym_config::defaults::VOUCHER_INFO; + use nym_credentials::coconut::bandwidth::CredentialType; use nym_validator_client::nyxd::{Event, EventAttribute}; #[tokio::test] async fn validate_deposit_tx_test() { - let (voucher, correct_request) = voucher_request_fixture(coin(1234, "unym"), None); + let voucher = voucher_fixture(coin(1234, "unym"), None); + let signing_data = voucher.prepare_for_signing(); + let voucher_data = voucher.get_variant_data().voucher_data().unwrap(); + let correct_request = voucher_data.create_blind_sign_request_body(&signing_data); let mut tx_entry = tx_entry_fixture(correct_request.tx_hash); let good_deposit_attribute = EventAttribute { @@ -171,7 +174,7 @@ mod test { err.to_string(), CoconutError::InconsistentDepositInfo { on_chain: "bandwidth deposit info".to_string(), - request: VOUCHER_INFO.to_string(), + request: CredentialType::Voucher.to_string(), } .to_string(), ); @@ -312,7 +315,7 @@ mod test { .parse() .unwrap(), "3vUCc6MCN5AC2LNgDYjRB1QeErZSN1S8f6K14JHjpUcKWXbjGYFExA8DbwQQBki9gyUqrpBF94Drttb4eMcGQXkp".parse().unwrap(), - voucher.get_public_attributes_plain(), + voucher.get_plain_public_attributes(), ); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ good_deposit_attribute.clone(), diff --git a/nym-api/src/coconut/tests/issued_credentials.rs b/nym-api/src/coconut/tests/issued_credentials.rs index d753476235..ef64c38da8 100644 --- a/nym-api/src/coconut/tests/issued_credentials.rs +++ b/nym-api/src/coconut/tests/issued_credentials.rs @@ -1,13 +1,12 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::coconut::tests::{voucher_request_fixture, TestFixture}; +use crate::coconut::tests::{voucher_fixture, TestFixture}; use cosmwasm_std::coin; use nym_api_requests::coconut::models::{ EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse, Pagination, }; use nym_api_requests::coconut::CredentialsRequestBody; -use nym_coconut::Base58; use nym_validator_client::nym_api::routes::{API_VERSION, BANDWIDTH, COCONUT_ROUTES}; use rocket::http::Status; use std::collections::BTreeMap; @@ -102,12 +101,20 @@ async fn issued_credential() { let hash1 = "6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E".to_string(); let hash2 = "9F4DF28B36189B4410BC23D97FD757FC74B919122E80534CC2CA6F3D646F6518".to_string(); - let (voucher1, req1) = voucher_request_fixture(coin(1234, "unym"), Some(hash1.clone())); - let (voucher2, req2) = voucher_request_fixture(coin(1234, "unym"), Some(hash2.clone())); + let voucher1 = voucher_fixture(coin(1234, "unym"), Some(hash1.clone())); + let voucher2 = voucher_fixture(coin(1234, "unym"), Some(hash2.clone())); + + let signing_data1 = voucher1.prepare_for_signing(); + let voucher_data1 = voucher1.get_variant_data().voucher_data().unwrap(); + let request1 = voucher_data1.create_blind_sign_request_body(&signing_data1); + + let signing_data2 = voucher2.prepare_for_signing(); + let voucher_data2 = voucher2.get_variant_data().voucher_data().unwrap(); + let request2 = voucher_data2.create_blind_sign_request_body(&signing_data2); let test_fixture = TestFixture::new().await; - test_fixture.add_deposit_tx(&voucher1); - test_fixture.add_deposit_tx(&voucher2); + test_fixture.add_deposit_tx(voucher_data1); + test_fixture.add_deposit_tx(voucher_data2); // random credential that was never issued let response = test_fixture.rocket.get(route(42)).dispatch().await; @@ -116,10 +123,10 @@ async fn issued_credential() { serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); assert!(parsed_response.credential.is_none()); - let cred1 = test_fixture.issue_credential(req1).await; + let cred1 = test_fixture.issue_credential(request1.clone()).await; test_fixture.set_epoch(3); - let cred2 = test_fixture.issue_credential(req2).await; + let cred2 = test_fixture.issue_credential(request2.clone()).await; let response = test_fixture.rocket.get(route(1)).dispatch().await; assert_eq!(response.status(), Status::Ok); @@ -136,49 +143,37 @@ async fn issued_credential() { // TODO: currently we have no signature checks assert_eq!(1, issued1.credential.id); assert_eq!(1, issued1.credential.epoch_id); - assert_eq!(voucher1.tx_hash(), issued1.credential.tx_hash); + assert_eq!(voucher_data1.tx_hash(), issued1.credential.tx_hash); assert_eq!( cred1.to_bytes(), issued1.credential.blinded_partial_credential.to_bytes() ); - let cms: Vec<_> = voucher1 - .blind_sign_request() - .get_private_attributes_pedersen_commitments() - .iter() - .map(|c| c.to_bs58()) - .collect(); assert_eq!( - cms, + request1.encode_commitments(), issued1 .credential .bs58_encoded_private_attributes_commitments ); assert_eq!( - voucher1.get_public_attributes_plain(), + voucher1.get_plain_public_attributes(), issued1.credential.public_attributes ); assert_eq!(2, issued2.credential.id); assert_eq!(3, issued2.credential.epoch_id); - assert_eq!(voucher2.tx_hash(), issued2.credential.tx_hash); + assert_eq!(voucher_data2.tx_hash(), issued2.credential.tx_hash); assert_eq!( cred2.to_bytes(), issued2.credential.blinded_partial_credential.to_bytes() ); - let cms: Vec<_> = voucher2 - .blind_sign_request() - .get_private_attributes_pedersen_commitments() - .iter() - .map(|c| c.to_bs58()) - .collect(); assert_eq!( - cms, + request2.encode_commitments(), issued2 .credential .bs58_encoded_private_attributes_commitments ); assert_eq!( - voucher2.get_public_attributes_plain(), + voucher2.get_plain_public_attributes(), issued2.credential.public_attributes ); } diff --git a/nym-api/src/coconut/tests/mod.rs b/nym-api/src/coconut/tests/mod.rs index db20f4bc1b..726c42dbd2 100644 --- a/nym-api/src/coconut/tests/mod.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -15,7 +15,7 @@ use cw3::{Proposal, ProposalResponse, Vote, VoteInfo, VoteResponse, Votes}; use cw4::{Cw4Contract, MemberResponse}; use nym_api_requests::coconut::models::{IssuedCredentialBody, IssuedCredentialResponse}; use nym_api_requests::coconut::{BlindSignRequestBody, BlindedSignatureResponse}; -use nym_coconut::{BlindedSignature, Parameters}; +use nym_coconut::{BlindedSignature, Parameters, VerificationKey}; use nym_coconut_bandwidth_contract_common::events::{ DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, DEPOSIT_VALUE, @@ -34,10 +34,10 @@ use nym_coconut_dkg_common::types::{ EpochId, EpochState, PartialContractDealingData, State as ContractState, }; use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; -use nym_coconut_interface::VerificationKey; -use nym_config::defaults::VOUCHER_INFO; use nym_contracts_common::IdentityKey; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_credentials::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData; +use nym_credentials::coconut::bandwidth::CredentialType; +use nym_credentials::IssuanceBandwidthCredential; use nym_crypto::asymmetric::{encryption, identity}; use nym_dkg::{NodeIndex, Threshold}; use nym_validator_client::nym_api::routes::{ @@ -1225,9 +1225,9 @@ pub fn tx_entry_fixture(hash: Hash) -> TxResponse { } } -pub fn deposit_tx_fixture(voucher: &BandwidthVoucher) -> TxResponse { +pub fn deposit_tx_fixture(voucher_data: &BandwidthVoucherIssuanceData) -> TxResponse { TxResponse { - hash: voucher.tx_hash(), + hash: voucher_data.tx_hash(), height: Default::default(), index: 0, tx_result: ExecTxResult { @@ -1242,22 +1242,25 @@ pub fn deposit_tx_fixture(voucher: &BandwidthVoucher) -> TxResponse { attributes: vec![ EventAttribute { key: DEPOSIT_VALUE.to_string(), - value: voucher.get_voucher_value(), + value: voucher_data.value_plain(), index: false, }, EventAttribute { key: DEPOSIT_INFO.to_string(), - value: VOUCHER_INFO.to_string(), + value: CredentialType::Voucher.to_string(), index: false, }, EventAttribute { key: DEPOSIT_IDENTITY_KEY.to_string(), - value: voucher.identity_key().public_key().to_base58_string(), + value: voucher_data.identity_key().public_key().to_base58_string(), index: false, }, EventAttribute { key: DEPOSIT_ENCRYPTION_KEY.parse().unwrap(), - value: voucher.encryption_key().public_key().to_base58_string(), + value: voucher_data + .encryption_key() + .public_key() + .to_base58_string(), index: false, }, ], @@ -1285,11 +1288,10 @@ pub fn blinded_signature_fixture() -> BlindedSignature { BlindedSignature::from_bytes(&dummy_bytes).unwrap() } -pub fn voucher_request_fixture>( +pub fn voucher_fixture>( amount: C, tx_hash: Option, -) -> (BandwidthVoucher, BlindSignRequestBody) { - let params = Parameters::new(4).unwrap(); +) -> IssuanceBandwidthCredential { let mut rng = OsRng; let tx_hash = if let Some(provided) = &tx_hash { provided.parse().unwrap() @@ -1304,23 +1306,7 @@ pub fn voucher_request_fixture>( let enc_priv = encryption::PrivateKey::from_bytes(&encryption_keypair.private_key().to_bytes()).unwrap(); - let voucher = BandwidthVoucher::new( - ¶ms, - amount.into().amount.to_string(), - VOUCHER_INFO.to_string(), - tx_hash, - id_priv, - enc_priv, - ); - - let request = BlindSignRequestBody::new( - voucher.blind_sign_request().clone(), - tx_hash, - voucher.sign(), - voucher.get_public_attributes_plain(), - ); - - (voucher, request) + IssuanceBandwidthCredential::new_voucher(amount.into(), tx_hash, id_priv, enc_priv) } fn dummy_signature() -> identity::Signature { @@ -1398,9 +1384,10 @@ impl TestFixture { self.chain_state.lock().unwrap().txs.insert(hash, tx); } - fn add_deposit_tx(&self, voucher: &BandwidthVoucher) { + fn add_deposit_tx(&self, voucher: &BandwidthVoucherIssuanceData) { let mut guard = self.chain_state.lock().unwrap(); let fixture = deposit_tx_fixture(voucher); + guard.txs.insert(voucher.tx_hash(), fixture); } @@ -1410,9 +1397,13 @@ impl TestFixture { rng.fill_bytes(&mut tx_hash); let tx_hash = Hash::from_bytes(Algorithm::Sha256, &tx_hash).unwrap(); - let (voucher, req) = voucher_request_fixture(coin(1234, "unym"), Some(tx_hash.to_string())); - self.add_deposit_tx(&voucher); + let voucher = voucher_fixture(coin(1234, "unym"), Some(tx_hash.to_string())); + let signing_data = voucher.prepare_for_signing(); + let voucher_data = voucher.get_variant_data().voucher_data().unwrap(); + let req = voucher_data.create_blind_sign_request_body(&signing_data); + + self.add_deposit_tx(voucher_data); self.issue_credential(req).await; } @@ -1456,15 +1447,18 @@ mod credential_tests { use super::*; use crate::coconut::tests::helpers::init_chain; use nym_api_requests::coconut::{VerifyCredentialBody, VerifyCredentialResponse}; - use nym_coconut::tests::helpers::theta_from_keys_and_attributes; - use nym_coconut::{hash_to_scalar, ttp_keygen}; + use nym_coconut::{blind_sign, hash_to_scalar, ttp_keygen}; use nym_coconut_bandwidth_contract_common::spend_credential::SpendCredential; - use nym_coconut_interface::Credential; + use nym_credentials::coconut::bandwidth::bandwidth_credential_params; use nym_validator_client::nym_api::routes::COCONUT_VERIFY_BANDWIDTH_CREDENTIAL; #[tokio::test] async fn already_issued() { - let (_, request_body) = voucher_request_fixture(coin(1234, TEST_COIN_DENOM), None); + let voucher = voucher_fixture(coin(1234, TEST_COIN_DENOM), None); + let signing_data = voucher.prepare_for_signing(); + let voucher_data = voucher.get_variant_data().voucher_data().unwrap(); + let request_body = voucher_data.create_blind_sign_request_body(&signing_data); + let tx_hash = request_body.tx_hash; let tx_entry = tx_entry_fixture(tx_hash); @@ -1547,7 +1541,11 @@ mod credential_tests { .unwrap(); assert!(state.already_issued(tx_hash).await.unwrap().is_none()); - let (_, request_body) = voucher_request_fixture(coin(1234, TEST_COIN_DENOM), None); + let voucher = voucher_fixture(coin(1234, TEST_COIN_DENOM), None); + let signing_data = voucher.prepare_for_signing(); + let voucher_data = voucher.get_variant_data().voucher_data().unwrap(); + let request_body = voucher_data.create_blind_sign_request_body(&signing_data); + let commitments = request_body.encode_commitments(); let public = request_body.public_attributes_plain.clone(); let sig = blinded_signature_fixture(); @@ -1637,10 +1635,8 @@ mod credential_tests { let identity_keypair = identity::KeyPair::new(&mut rng); let encryption_keypair = encryption::KeyPair::new(&mut rng); - let voucher = BandwidthVoucher::new( - ¶ms, - "1234".to_string(), - VOUCHER_INFO.to_string(), + let voucher = IssuanceBandwidthCredential::new_voucher( + coin(1234, "unym"), tx_hash, identity::PrivateKey::from_base58_string( identity_keypair.private_key().to_base58_string(), @@ -1658,7 +1654,9 @@ mod credential_tests { let chain = init_chain(); - let tx_entry = deposit_tx_fixture(&voucher); + let voucher_data = voucher.get_variant_data().voucher_data().unwrap(); + let tx_entry = deposit_tx_fixture(voucher_data); + chain.lock().unwrap().txs.insert(tx_hash, tx_entry.clone()); let nyxd_client = DummyClient::new( @@ -1688,14 +1686,9 @@ mod credential_tests { .await .expect("valid rocket instance"); - let request_signature = voucher.sign(); - - let request_body = BlindSignRequestBody::new( - voucher.blind_sign_request().clone(), - tx_hash, - request_signature, - voucher.get_public_attributes_plain(), - ); + let signing_data = voucher.prepare_for_signing(); + let voucher_data = voucher.get_variant_data().voucher_data().unwrap(); + let request_body = voucher_data.create_blind_sign_request_body(&signing_data); let response = client .post(format!( @@ -1727,24 +1720,39 @@ mod credential_tests { let nyxd_client = DummyClient::new(validator_address.clone(), chain.clone()); let db_dir = tempdir().unwrap(); - let params = Parameters::new(4).unwrap(); - let mut key_pairs = ttp_keygen(¶ms, 1, 1).unwrap(); - let voucher_value = 1234u64; - let voucher_info = "voucher info"; - let public_attributes = [ - hash_to_scalar(voucher_value.to_string()), - hash_to_scalar(voucher_info), - ]; - let public_attributes_ref = vec![&public_attributes[0], &public_attributes[1]]; - let indices: Vec = key_pairs + + // generate all the credential requests + let params = bandwidth_credential_params(); + let key_pair = nym_coconut::keygen(params); + + let voucher_amount = coin(1234, "unym"); + let issuance = voucher_fixture(coin(1234, "unym"), None); + let sig_req = issuance.prepare_for_signing(); + let pub_attrs_hashed = sig_req + .public_attributes_plain .iter() - .enumerate() - .map(|(idx, _)| (idx + 1) as u64) - .collect(); - let theta = - theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes_ref) - .unwrap(); - let key_pair = key_pairs.remove(0); + .map(hash_to_scalar) + .collect::>(); + let pub_attrs = pub_attrs_hashed.iter().collect::>(); + let blind_sig = blind_sign( + params, + key_pair.secret_key(), + &sig_req.blind_sign_request, + &pub_attrs, + ) + .unwrap(); + let sig = blind_sig + .unblind( + key_pair.verification_key(), + &sig_req.pedersen_commitments_openings, + ) + .unwrap(); + + let issued = issuance.into_issued_credential(sig); + let spending = issued + .prepare_for_spending(key_pair.verification_key()) + .unwrap(); + let storage1 = NymApiStorage::init(db_dir.path().join("storage.db")) .await .unwrap(); @@ -1773,13 +1781,16 @@ mod credential_tests { .await .expect("valid rocket instance"); - let credential = - Credential::new(4, theta.clone(), voucher_value, voucher_info.to_string(), 0); + let epoch_id = 69; let proposal_id = 42; // The address is not used, so we can use a duplicate let gateway_cosmos_addr = validator_address.clone(); - let req = - VerifyCredentialBody::new(credential.clone(), proposal_id, gateway_cosmos_addr.clone()); + let req = VerifyCredentialBody::new( + spending.clone(), + epoch_id, + proposal_id, + gateway_cosmos_addr.clone(), + ); // Test endpoint with not proposal for the proposal id let response = client @@ -1841,7 +1852,9 @@ mod credential_tests { ); // Test the endpoint with no msg in the proposal action - proposal.description = credential.blinded_serial_number(); + proposal.description = spending + .verify_credential_request + .blinded_serial_number_bs58(); chain .lock() .unwrap() @@ -1866,9 +1879,9 @@ mod credential_tests { ); // Test the endpoint without any credential recorded in the Coconut Bandwidth Contract - let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); + let funds = voucher_amount.clone(); let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { - funds: funds.clone().into(), + funds: funds.clone(), }; let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: String::new(), @@ -1905,7 +1918,9 @@ mod credential_tests { .bandwidth_contract .spent_credentials .insert( - credential.blinded_serial_number(), + spending + .verify_credential_request + .blinded_serial_number_bs58(), SpendCredentialResponse::new(None), ); @@ -1928,8 +1943,10 @@ mod credential_tests { // Test the endpoint with a credential that doesn't verify correctly let mut spent_credential = SpendCredential::new( - funds.clone().into(), - credential.blinded_serial_number(), + funds.clone(), + spending + .verify_credential_request + .blinded_serial_number_bs58(), Addr::unchecked("unimportant"), ); chain @@ -1938,47 +1955,55 @@ mod credential_tests { .bandwidth_contract .spent_credentials .insert( - credential.blinded_serial_number(), + spending + .verify_credential_request + .blinded_serial_number_bs58(), SpendCredentialResponse::new(Some(spent_credential.clone())), ); - let bad_credential = Credential::new( - 4, - theta.clone(), - voucher_value, - String::from("bad voucher info"), - 0, - ); - let bad_req = - VerifyCredentialBody::new(bad_credential, proposal_id, gateway_cosmos_addr.clone()); - let response = client - .post(format!( - "/{}/{}/{}/{}", - API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL - )) - .json(&bad_req) - .dispatch() - .await; - assert_eq!(response.status(), Status::Ok); - let verify_credential_response = serde_json::from_str::( - &response.into_string().await.unwrap(), - ) - .unwrap(); - assert!(!verify_credential_response.verification_result); - assert_eq!( - cw3::Status::Rejected, - chain - .lock() - .unwrap() - .multisig_contract - .proposals - .get(&proposal_id) - .unwrap() - .status - ); + + // TODO: somehow restore that test + // let bad_credential = Credential::new( + // 4, + // theta.clone(), + // voucher_value, + // String::from("bad voucher info"), + // 0, + // ); + // let bad_req = VerifyCredentialBody::new( + // bad_credential, + // epoch_id, + // proposal_id, + // gateway_cosmos_addr.clone(), + // ); + // let response = client + // .post(format!( + // "/{}/{}/{}/{}", + // API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL + // )) + // .json(&bad_req) + // .dispatch() + // .await; + // assert_eq!(response.status(), Status::Ok); + // let verify_credential_response = serde_json::from_str::( + // &response.into_string().await.unwrap(), + // ) + // .unwrap(); + // assert!(!verify_credential_response.verification_result); + // assert_eq!( + // cw3::Status::Rejected, + // chain + // .lock() + // .unwrap() + // .multisig_contract + // .proposals + // .get(&proposal_id) + // .unwrap() + // .status + // ); // Test the endpoint with a proposal that has a different value for the funds to be released // then what's in the credential - let funds = Coin::new((voucher_value + 10) as u128, TEST_COIN_DENOM); + let funds = Coin::new(voucher_amount.amount.u128() + 10, TEST_COIN_DENOM); let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { funds: funds.clone().into(), }; @@ -2022,9 +2047,9 @@ mod credential_tests { ); // Test the endpoint with every dependency met - let funds = Coin::new(voucher_value as u128, TEST_COIN_DENOM); + let funds = voucher_amount; let msg = nym_coconut_bandwidth_contract_common::msg::ExecuteMsg::ReleaseFunds { - funds: funds.clone().into(), + funds: funds.clone(), }; let cosmos_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: String::new(), @@ -2073,7 +2098,9 @@ mod credential_tests { .bandwidth_contract .spent_credentials .insert( - credential.blinded_serial_number(), + spending + .verify_credential_request + .blinded_serial_number_bs58(), SpendCredentialResponse::new(Some(spent_credential)), ); let response = client From 2638952f5a08245f8a8ce803d3cef5d859ec2de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 09:59:54 +0000 Subject: [PATCH 13/49] reintroduced handling of old v1 credentials --- .../client-libs/gateway-client/src/client.rs | 6 +- gateway/gateway-requests/src/models.rs | 155 +++++++++++++++++- gateway/gateway-requests/src/types.rs | 39 ++++- .../connection_handler/authenticated.rs | 73 ++++++--- .../websocket/connection_handler/fresh.rs | 4 +- 5 files changed, 247 insertions(+), 30 deletions(-) diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index b1ebb23fa4..e0cb06215e 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -384,7 +384,7 @@ impl GatewayClient { // note: in +1.2.0 we will have to return a hard error here Ok(()) } - Some(v) if v != PROTOCOL_VERSION => { + Some(v) if v > PROTOCOL_VERSION => { let err = GatewayClientError::IncompatibleProtocol { gateway: Some(v), current: PROTOCOL_VERSION, @@ -394,7 +394,7 @@ impl GatewayClient { } Some(_) => { - info!("the gateway is using exactly the same protocol version as we are. We're good to continue!"); + info!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!"); Ok(()) } } @@ -522,7 +522,7 @@ impl GatewayClient { let mut rng = OsRng; let iv = IV::new_random(&mut rng); - let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential( + let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential_v2( credential, epoch_id, self.shared_key.as_ref().unwrap(), diff --git a/gateway/gateway-requests/src/models.rs b/gateway/gateway-requests/src/models.rs index 6e06aabc7e..0bef6c4084 100644 --- a/gateway/gateway-requests/src/models.rs +++ b/gateway/gateway-requests/src/models.rs @@ -6,6 +6,106 @@ use nym_credentials::coconut::bandwidth::CredentialSpendingData; use nym_credentials_interface::{CoconutError, VerifyCredentialRequest}; use serde::{Deserialize, Serialize}; +// reimplements old coconut-interface::Credential for backwards compatibility sake +// (so that 'new' gateways could still understand those requests) +#[derive(Debug, PartialEq, Eq)] +pub struct OldV1Credential { + pub n_params: u32, + + pub theta: VerifyCredentialRequest, + + pub voucher_value: u64, + + pub voucher_info: String, + + pub epoch_id: u64, +} + +// attempt to convert the old request type into the new variant +impl TryFrom for CredentialSpendingWithEpoch { + type Error = GatewayRequestsError; + + fn try_from(value: OldV1Credential) -> Result { + if value.n_params <= 2 { + return Err(GatewayRequestsError::InvalidNumberOfEmbededParameters( + value.n_params, + )); + } + let embedded_private_attributes = value.n_params as usize - 2; + let typ = value.voucher_info.parse()?; + let public_attributes_plain = vec![value.voucher_value.to_string(), value.voucher_info]; + + Ok(CredentialSpendingWithEpoch { + data: CredentialSpendingData { + embedded_private_attributes, + verify_credential_request: value.theta, + public_attributes_plain, + typ, + }, + epoch_id: value.epoch_id, + }) + } +} + +impl OldV1Credential { + #[cfg(test)] + pub fn as_bytes(&self) -> Vec { + let n_params_bytes = self.n_params.to_be_bytes(); + let theta_bytes = self.theta.to_bytes(); + let theta_bytes_len = theta_bytes.len(); + let voucher_value_bytes = self.voucher_value.to_be_bytes(); + let epoch_id_bytes = self.epoch_id.to_be_bytes(); + let voucher_info_bytes = self.voucher_info.as_bytes(); + let voucher_info_len = voucher_info_bytes.len(); + + let mut bytes = Vec::with_capacity(28 + theta_bytes_len + voucher_info_len); + bytes.extend_from_slice(&n_params_bytes); + bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes()); + bytes.extend_from_slice(&theta_bytes); + bytes.extend_from_slice(&voucher_value_bytes); + bytes.extend_from_slice(&epoch_id_bytes); + bytes.extend_from_slice(voucher_info_bytes); + + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < 28 { + return Err(CoconutError::Deserialization(String::from( + "To few bytes in credential", + ))); + } + let mut four_byte = [0u8; 4]; + let mut eight_byte = [0u8; 8]; + + four_byte.copy_from_slice(&bytes[..4]); + let n_params = u32::from_be_bytes(four_byte); + eight_byte.copy_from_slice(&bytes[4..12]); + let theta_len = u64::from_be_bytes(eight_byte); + if bytes.len() < 28 + theta_len as usize { + return Err(CoconutError::Deserialization(String::from( + "To few bytes in credential", + ))); + } + let theta = VerifyCredentialRequest::from_bytes(&bytes[12..12 + theta_len as usize]) + .map_err(|e| CoconutError::Deserialization(e.to_string()))?; + eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]); + let voucher_value = u64::from_be_bytes(eight_byte); + eight_byte.copy_from_slice(&bytes[20 + theta_len as usize..28 + theta_len as usize]); + let epoch_id = u64::from_be_bytes(eight_byte); + let voucher_info = String::from_utf8(bytes[28 + theta_len as usize..].to_vec()) + .map_err(|e| CoconutError::Deserialization(e.to_string()))?; + + Ok(OldV1Credential { + n_params, + theta, + voucher_value, + voucher_info, + epoch_id, + }) + } +} + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct CredentialSpendingWithEpoch { /// The cryptographic material required for spending the underlying credential. @@ -145,7 +245,60 @@ mod tests { use super::*; use nym_credentials::coconut::bandwidth::bandwidth_credential_params; use nym_credentials::IssuanceBandwidthCredential; - use nym_credentials_interface::{blind_sign, hash_to_scalar}; + use nym_credentials_interface::{ + blind_sign, hash_to_scalar, prove_bandwidth_credential, Attribute, Base58, Parameters, + Signature, VerificationKey, + }; + + #[test] + fn old_v1_coconut_credential_roundtrip() { + let voucher_value = 1000000u64; + let voucher_info = String::from("BandwidthVoucher"); + let serial_number = + Attribute::try_from_bs58("7Rp3imcuNX3w9se9wm5th8gSvc2czsnMrGsdt5HsrycA").unwrap(); + let binding_number = + Attribute::try_from_bs58("Auf8yVEgyEAWNHaXUZmimS4n9g5YiYnNYqp6F9BtBe9E").unwrap(); + let signature = Signature::try_from_bs58( + "ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc\ + 7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZL\ + ZR9j", + ) + .unwrap(); + let params = Parameters::new(4).unwrap(); + let verification_key = VerificationKey::try_from_bs58("8CFtVVXdwLy4WHMQPE4\ + woe89q3DRHoNxBSchftrEjSBPWA4r4xZv4Y9qSvS5x5bMmFtp7BX6ikECAnuXr5EjXWSsgjirZJmpS5XDUynVfht1cD\ + FWGDvy2XFrRCuoCMotNXi3PoF6wYqdTR9Rqcfoj3i2H5Nid422WBaLtVoC9QNobvpvaqq6vX5PbsSyPayvU8HCXFxM6\ + JjScYpbRTxQtdwefWLrk3LmXyJQBWi7c2VAhSxu9msp7VTBycqdwQNgxHETStZuwXsozxaGQ2KssVUCaaoYPR4g2RqK\ + UAvtWwA7pMiAQNcbkXcbsjCgVjWaCpMWC37XA31cLcFf3zbjHD9e5tXjAcqa4M89fbFhuvvSXxowSAZ5NoWrN32kd5d\ + wxJm1JW3Tt2h6yDDBe84oMy71462dZn7N78DVk2mFNGwBCibrZWA7oUzRBMfYxiQrksoFcou7QfLLd58zoNYmPQPt84\ + 1VpQopEBfdQ7Nf9zoXxBt3zMy7g5NsFGvzh7KTbDUyeeXrdkKJPQBs6dqaizr9sS8CPPmR4uk96vDTRh8CJ5FbSsmb8\ + nP71dRvvwRZJHGzwYirMo6SXS3ZYxFuiA3mkxYuqDHCwkTWDuRCcAaztrDYRZg7VCMo4Q446AaEso5eqpeWpHZQt53E\ + ZRpqmNYKASGwMhTeEHPSLgSmtoAAUcaRWpGRzYfd6kzEma8tdGLwyP4rLXgvSvtDLP37dU7YgF3LEXbGAz57U9ATy46\ + 6sroLpHPdaCWB8RF11wvB6Tu196JnJd2KyQBP1iUWP3rtZs3GhAF1QVcxquh8BqDZzAcpQ6wCS1P9c5GxKgww77FVF5\ + Kp83XtoxSrw3GaYVyKTGxNh3vcKPR31txCjTxPaN2fg7TaPLhoQJX4YaAroFSXqrqbbRsisuHhhCeUP2YwDjHedes9y") + .unwrap(); + let theta = prove_bandwidth_credential( + ¶ms, + &verification_key, + &signature, + &serial_number, + &binding_number, + ) + .unwrap(); + + let credential = OldV1Credential { + n_params: 4, + theta, + voucher_value, + voucher_info, + epoch_id: 42, + }; + + let serialized_credential = credential.as_bytes(); + let deserialized_credential = OldV1Credential::from_bytes(&serialized_credential).unwrap(); + + assert_eq!(credential, deserialized_credential); + } #[test] fn credential_roundtrip() { diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 67ccb148f7..e7e402c37c 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -3,7 +3,7 @@ use crate::authentication::encrypted_address::EncryptedAddressBytes; use crate::iv::IV; -use crate::models::CredentialSpendingWithEpoch; +use crate::models::{CredentialSpendingWithEpoch, OldV1Credential}; use crate::registration::handshake::SharedKeys; use crate::{GatewayMacSize, PROTOCOL_VERSION}; use log::error; @@ -116,6 +116,9 @@ pub enum GatewayRequestsError { #[error("failed to deserialize provided credential: malformed verify request: {0}")] CredentialDeserializationFailureMalformedTheta(CoconutError), + + #[error("the provided [v1] credential has invalid number of parameters - {0}")] + InvalidNumberOfEmbededParameters(u32), } #[derive(Serialize, Deserialize, Debug)] @@ -140,6 +143,10 @@ pub enum ClientControlRequest { enc_credential: Vec, iv: Vec, }, + BandwidthCredentialV2 { + enc_credential: Vec, + iv: Vec, + }, ClaimFreeTestnetBandwidth, } @@ -157,7 +164,31 @@ impl ClientControlRequest { } } - pub fn new_enc_coconut_bandwidth_credential( + pub fn new_enc_coconut_bandwidth_credential_v1( + credential: &OldV1Credential, + shared_key: &SharedKeys, + iv: IV, + ) -> Self { + let serialized_credential = credential.as_bytes(); + let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); + + ClientControlRequest::BandwidthCredential { + enc_credential, + iv: iv.to_bytes(), + } + } + + pub fn try_from_enc_coconut_bandwidth_credential_v1( + enc_credential: Vec, + shared_key: &SharedKeys, + iv: IV, + ) -> Result { + let credential_bytes = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; + OldV1Credential::from_bytes(&credential_bytes) + .map_err(|_| GatewayRequestsError::MalformedEncryption) + } + + pub fn new_enc_coconut_bandwidth_credential_v2( credential: CredentialSpendingData, epoch_id: u64, shared_key: &SharedKeys, @@ -167,13 +198,13 @@ impl ClientControlRequest { let serialized_credential = cred.to_bytes(); let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); - ClientControlRequest::BandwidthCredential { + ClientControlRequest::BandwidthCredentialV2 { enc_credential, iv: iv.to_bytes(), } } - pub fn try_from_enc_coconut_bandwidth_credential( + pub fn try_from_enc_coconut_bandwidth_credential_v2( enc_credential: Vec, shared_key: &SharedKeys, iv: IV, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index 3c3ff21fa4..0f4bd0d7d6 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -22,6 +22,7 @@ use futures::{ use log::*; use nym_credentials::coconut::bandwidth::{bandwidth_credential_params, CredentialType}; use nym_credentials_interface::CoconutError; +use nym_gateway_requests::models::CredentialSpendingWithEpoch; use nym_gateway_requests::{ iv::{IVConversionError, IV}, types::{BinaryRequest, ServerResponse}, @@ -82,6 +83,12 @@ pub(crate) enum RequestHandlingError { #[error("failed to recover bandwidth value: {0}")] BandwidthRecoveryFailure(#[from] BandwidthError), + + #[error("the provided credential did not contain a valid type attribute")] + InvalidTypeAttribute, + + #[error("the provided credential did not have a bandwidth attribute")] + MissingBandwidthAttribute, } impl RequestHandlingError { @@ -211,25 +218,10 @@ where } } - /// Tries to handle the received bandwidth request by checking correctness of the received data - /// and if successful, increases client's bandwidth by an appropriate amount. - /// - /// # Arguments - /// - /// * `enc_credential`: raw encrypted bandwidth credential to verify. - /// * `iv`: fresh iv used for the credential. - async fn handle_bandwidth( + async fn handle_bandwidth_request( &mut self, - enc_credential: Vec, - iv: Vec, + credential: CredentialSpendingWithEpoch, ) -> Result { - let iv = IV::try_from_bytes(&iv)?; - let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential( - enc_credential, - &self.client.shared_keys, - iv, - )?; - let aggregated_verification_key = self .inner .coconut_verifier @@ -237,11 +229,11 @@ where .await?; if !credential.data.validate_type_attribute() { - unimplemented!() + return Err(RequestHandlingError::InvalidTypeAttribute); } let Some(bandwidth_attribute) = credential.data.get_bandwidth_attribute() else { - unimplemented!() + return Err(RequestHandlingError::MissingBandwidthAttribute); }; // this will extract token amounts out of bandwidth vouchers and validate expiry of free passes @@ -279,6 +271,43 @@ where Ok(ServerResponse::Bandwidth { available_total }) } + async fn handle_bandwidth_v1( + &mut self, + enc_credential: Vec, + iv: Vec, + ) -> Result { + let iv = IV::try_from_bytes(&iv)?; + let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential_v1( + enc_credential, + &self.client.shared_keys, + iv, + )?; + + self.handle_bandwidth_request(credential.try_into()?).await + } + + /// Tries to handle the received bandwidth request by checking correctness of the received data + /// and if successful, increases client's bandwidth by an appropriate amount. + /// + /// # Arguments + /// + /// * `enc_credential`: raw encrypted bandwidth credential to verify. + /// * `iv`: fresh iv used for the credential. + async fn handle_bandwidth_v2( + &mut self, + enc_credential: Vec, + iv: Vec, + ) -> Result { + let iv = IV::try_from_bytes(&iv)?; + let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential_v2( + enc_credential, + &self.client.shared_keys, + iv, + )?; + + self.handle_bandwidth_request(credential).await + } + async fn handle_claim_testnet_bandwidth( &mut self, ) -> Result { @@ -357,7 +386,11 @@ where Err(e) => RequestHandlingError::InvalidTextRequest(e).into_error_message(), Ok(request) => match request { ClientControlRequest::BandwidthCredential { enc_credential, iv } => self - .handle_bandwidth(enc_credential, iv) + .handle_bandwidth_v1(enc_credential, iv) + .await + .into_ws_message(), + ClientControlRequest::BandwidthCredentialV2 { enc_credential, iv } => self + .handle_bandwidth_v2(enc_credential, iv) .await .into_ws_message(), ClientControlRequest::ClaimFreeTestnetBandwidth => self diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 2d08c85709..ee1395e8a8 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -366,7 +366,7 @@ where // note: in +1.2.0 we will have to return a hard error here Ok(()) } - Some(v) if v != PROTOCOL_VERSION => { + Some(v) if v > PROTOCOL_VERSION => { let err = InitialAuthenticationError::IncompatibleProtocol { client: Some(v), current: PROTOCOL_VERSION, @@ -376,7 +376,7 @@ where } Some(_) => { - info!("the client is using exactly the same protocol version as we are. We're good to continue!"); + info!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!"); Ok(()) } } From 78e1d84905fce2dd9eac16fc4055890fa945c0fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 10:14:34 +0000 Subject: [PATCH 14/49] restored OldV1Credential::as_bytes to be available to non-test code --- gateway/gateway-requests/src/models.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/gateway/gateway-requests/src/models.rs b/gateway/gateway-requests/src/models.rs index 0bef6c4084..c16bf023cf 100644 --- a/gateway/gateway-requests/src/models.rs +++ b/gateway/gateway-requests/src/models.rs @@ -48,7 +48,6 @@ impl TryFrom for CredentialSpendingWithEpoch { } impl OldV1Credential { - #[cfg(test)] pub fn as_bytes(&self) -> Vec { let n_params_bytes = self.n_params.to_be_bytes(); let theta_bytes = self.theta.to_bytes(); From b02bbdef19ac974a9e330a619433e7eca02df2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 13:57:35 +0000 Subject: [PATCH 15/49] fixed SQL type for epoch_id --- .../migrations/20240206120000_add_credential_types.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/credential-storage/migrations/20240206120000_add_credential_types.sql b/common/credential-storage/migrations/20240206120000_add_credential_types.sql index a75acca881..696d6d46a4 100644 --- a/common/credential-storage/migrations/20240206120000_add_credential_types.sql +++ b/common/credential-storage/migrations/20240206120000_add_credential_types.sql @@ -12,6 +12,6 @@ CREATE TABLE coconut_credentials serialization_revision INTEGER NOT NULL, credential_type TEXT NOT NULL, credential_data BLOB NOT NULL, - epoch_id TEXT NOT NULL, + epoch_id INTEGER NOT NULL, consumed BOOLEAN NOT NULL ); \ No newline at end of file From 00f1ce98bade2a243c7d0f1b2931c5d59d9c01c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 14:17:20 +0000 Subject: [PATCH 16/49] gateway downgrading advertised protocol for incompatible clients --- .../client-libs/gateway-client/src/client.rs | 14 +++- gateway/gateway-requests/src/lib.rs | 3 +- gateway/gateway-requests/src/types.rs | 6 +- .../websocket/connection_handler/fresh.rs | 65 +++++++++++-------- 4 files changed, 54 insertions(+), 34 deletions(-) diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index e0cb06215e..b84e761952 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -19,7 +19,7 @@ use nym_crypto::asymmetric::identity; use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use nym_gateway_requests::iv::IV; use nym_gateway_requests::registration::handshake::{client_handshake, SharedKeys}; -use nym_gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, PROTOCOL_VERSION}; +use nym_gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, CURRENT_PROTOCOL_VERSION}; use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; @@ -80,6 +80,9 @@ pub struct GatewayClient { /// Delay between each subsequent reconnection attempt. reconnection_backoff: Duration, + // currently unused (but populated) + negotiated_protocol: Option, + /// Listen to shutdown messages. shutdown: TaskClient, } @@ -109,6 +112,7 @@ impl GatewayClient { should_reconnect_on_failure: true, reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS, reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF, + negotiated_protocol: None, shutdown, } } @@ -384,10 +388,10 @@ impl GatewayClient { // note: in +1.2.0 we will have to return a hard error here Ok(()) } - Some(v) if v > PROTOCOL_VERSION => { + Some(v) if v > CURRENT_PROTOCOL_VERSION => { let err = GatewayClientError::IncompatibleProtocol { gateway: Some(v), - current: PROTOCOL_VERSION, + current: CURRENT_PROTOCOL_VERSION, }; error!("{err}"); Err(err) @@ -440,6 +444,10 @@ impl GatewayClient { if self.authenticated { self.shared_key = Some(Arc::new(shared_key)); } + + // populate the negotiated protocol for future uses + self.negotiated_protocol = gateway_protocol; + Ok(()) } diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index e27d272f23..e9aae85d7f 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -18,7 +18,8 @@ pub mod types; // history: // 1 - initial release // 2 - changes to client credentials structure -pub const PROTOCOL_VERSION: u8 = 2; +pub const INITIAL_PROTOCOL_VERSION: u8 = 1; +pub const CURRENT_PROTOCOL_VERSION: u8 = 2; pub type GatewayMac = HmacOutput; diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index e7e402c37c..0165a40c6d 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -5,7 +5,7 @@ use crate::authentication::encrypted_address::EncryptedAddressBytes; use crate::iv::IV; use crate::models::{CredentialSpendingWithEpoch, OldV1Credential}; use crate::registration::handshake::SharedKeys; -use crate::{GatewayMacSize, PROTOCOL_VERSION}; +use crate::{GatewayMacSize, CURRENT_PROTOCOL_VERSION}; use log::error; use nym_credentials::coconut::bandwidth::CredentialSpendingData; use nym_credentials_interface::{CoconutError, UnknownCredentialType}; @@ -39,7 +39,7 @@ pub enum RegistrationHandshake { impl RegistrationHandshake { pub fn new_payload(data: Vec) -> Self { RegistrationHandshake::HandshakePayload { - protocol_version: Some(PROTOCOL_VERSION), + protocol_version: Some(CURRENT_PROTOCOL_VERSION), data, } } @@ -157,7 +157,7 @@ impl ClientControlRequest { iv: IV, ) -> Self { ClientControlRequest::Authenticate { - protocol_version: Some(PROTOCOL_VERSION), + protocol_version: Some(CURRENT_PROTOCOL_VERSION), address: address.as_base58_string(), enc_address: enc_address.to_base58_string(), iv: iv.to_base58_string(), diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index ee1395e8a8..8f9dafe112 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -15,7 +15,7 @@ use nym_gateway_requests::{ iv::{IVConversionError, IV}, registration::handshake::{error::HandshakeError, gateway_handshake, SharedKeys}, types::{ClientControlRequest, ServerResponse}, - BinaryResponse, PROTOCOL_VERSION, + BinaryResponse, CURRENT_PROTOCOL_VERSION, INITIAL_PROTOCOL_VERSION, }; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_sphinx::DestinationAddressBytes; @@ -95,6 +95,9 @@ pub(crate) struct FreshHandler { pub(crate) socket_connection: SocketStream, pub(crate) storage: St, pub(crate) coconut_verifier: Arc, + + // currently unused (but populated) + pub(crate) negotiated_protocol: Option, } impl FreshHandler @@ -126,6 +129,7 @@ where local_identity, storage, coconut_verifier, + negotiated_protocol: None, } } @@ -310,7 +314,7 @@ where /// Checks whether the stored shared keys match the received data, i.e. whether the upon decryption /// the provided encrypted address matches the expected unencrypted address. /// - /// Returns the the retrieved shared keys if the check was successful. + /// Returns the retrieved shared keys if the check was successful. /// /// # Arguments /// @@ -355,30 +359,33 @@ where } } - fn check_client_protocol( + fn negotiate_client_protocol( &self, client_protocol: Option, - ) -> Result<(), InitialAuthenticationError> { - // right now there are no failure cases here, but this might change in the future - match client_protocol { - None => { - warn!("the client we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0"); - // note: in +1.2.0 we will have to return a hard error here - Ok(()) - } - Some(v) if v > PROTOCOL_VERSION => { - let err = InitialAuthenticationError::IncompatibleProtocol { - client: Some(v), - current: PROTOCOL_VERSION, - }; - error!("{err}"); - Err(err) - } + ) -> Result { + let Some(client_protocol_version) = client_protocol else { + warn!("the client we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0"); + // note: in +1.2.0 we will have to return a hard error here + Ok(INITIAL_PROTOCOL_VERSION) + }; - Some(_) => { - info!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!"); - Ok(()) - } + // a v2 gateway will understand v1 requests, but v1 client will not understand v2 responses + if client_protocol_version == 1 { + return Ok(1); + } + + // we can't handle clients with higher protocol than ours + // (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement) + if client_protocol_version <= CURRENT_PROTOCOL_VERSION { + info!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!"); + Ok(CURRENT_PROTOCOL_VERSION) + } else { + let err = InitialAuthenticationError::IncompatibleProtocol { + client: client_protocol, + current: CURRENT_PROTOCOL_VERSION, + }; + error!("{err}"); + Err(err) } } @@ -490,7 +497,9 @@ where where S: AsyncRead + AsyncWrite + Unpin, { - self.check_client_protocol(client_protocol_version)?; + let negotiated_protocol = self.negotiate_client_protocol(client_protocol_version)?; + // populate the negotiated protocol for future uses + self.negotiated_protocol = Some(negotiated_protocol); let address = DestinationAddressBytes::try_from_base58_string(address) .map_err(|err| InitialAuthenticationError::MalformedClientAddress(err.to_string()))?; @@ -519,7 +528,7 @@ where Ok(InitialAuthResult::new( client_details, ServerResponse::Authenticate { - protocol_version: Some(PROTOCOL_VERSION), + protocol_version: Some(negotiated_protocol), status, bandwidth_remaining, }, @@ -580,7 +589,9 @@ where where S: AsyncRead + AsyncWrite + Unpin + Send, { - self.check_client_protocol(client_protocol_version)?; + let negotiated_protocol = self.negotiate_client_protocol(client_protocol_version)?; + // populate the negotiated protocol for future uses + self.negotiated_protocol = Some(negotiated_protocol); let remote_identity = Self::extract_remote_identity_from_register_init(&init_data)?; let remote_address = remote_identity.derive_destination_address(); @@ -597,7 +608,7 @@ where Ok(InitialAuthResult::new( Some(client_details), ServerResponse::Register { - protocol_version: Some(PROTOCOL_VERSION), + protocol_version: Some(negotiated_protocol), status, }, )) From ffe55ba072db65c8c50a0296da31c6a1cdc4326f Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Fri, 9 Feb 2024 15:32:35 +0100 Subject: [PATCH 17/49] running cargo fmt --- common/client-libs/gateway-client/src/client.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index b84e761952..a27ec472ba 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -19,7 +19,9 @@ use nym_crypto::asymmetric::identity; use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; use nym_gateway_requests::iv::IV; use nym_gateway_requests::registration::handshake::{client_handshake, SharedKeys}; -use nym_gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse, CURRENT_PROTOCOL_VERSION}; +use nym_gateway_requests::{ + BinaryRequest, ClientControlRequest, ServerResponse, CURRENT_PROTOCOL_VERSION, +}; use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; @@ -447,7 +449,7 @@ impl GatewayClient { // populate the negotiated protocol for future uses self.negotiated_protocol = gateway_protocol; - + Ok(()) } From 400d71bf07ed9403b9cf2ea0747ece86f42486a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 14:57:49 +0000 Subject: [PATCH 18/49] ibid --- common/client-libs/gateway-client/src/client.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index a27ec472ba..87e9fa758a 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -831,6 +831,7 @@ impl GatewayClient { should_reconnect_on_failure: false, reconnection_attempts: DEFAULT_RECONNECTION_ATTEMPTS, reconnection_backoff: DEFAULT_RECONNECTION_BACKOFF, + negotiated_protocol: None, shutdown, } } @@ -862,6 +863,7 @@ impl GatewayClient { should_reconnect_on_failure: self.should_reconnect_on_failure, reconnection_attempts: self.reconnection_attempts, reconnection_backoff: self.reconnection_backoff, + negotiated_protocol: self.negotiated_protocol, shutdown, } } From 691884e20a6d894e8515838be07a13e3d4178aad Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Fri, 9 Feb 2024 16:32:53 +0100 Subject: [PATCH 19/49] add return statement --- .../node/client_handling/websocket/connection_handler/fresh.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 8f9dafe112..c37b0acaae 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -366,7 +366,7 @@ where let Some(client_protocol_version) = client_protocol else { warn!("the client we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0"); // note: in +1.2.0 we will have to return a hard error here - Ok(INITIAL_PROTOCOL_VERSION) + return Ok(INITIAL_PROTOCOL_VERSION) }; // a v2 gateway will understand v1 requests, but v1 client will not understand v2 responses From 6e7bac1e7e27d9287f9b08ccfd5c07437e69564b Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Fri, 9 Feb 2024 16:41:01 +0100 Subject: [PATCH 20/49] cargo fmt --- .../node/client_handling/websocket/connection_handler/fresh.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index c37b0acaae..a2e9e98e7d 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -366,7 +366,7 @@ where let Some(client_protocol_version) = client_protocol else { warn!("the client we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0"); // note: in +1.2.0 we will have to return a hard error here - return Ok(INITIAL_PROTOCOL_VERSION) + return Ok(INITIAL_PROTOCOL_VERSION); }; // a v2 gateway will understand v1 requests, but v1 client will not understand v2 responses From 740cc72ec89068faa485f1ce42a3ff07fde8cc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 13:54:13 +0000 Subject: [PATCH 21/49] request type for obtaining free pass --- .../validator-client/src/nym_api/routes.rs | 1 + nym-api/nym-api-requests/Cargo.toml | 5 ++++- nym-api/nym-api-requests/src/coconut/models.rs | 14 ++++++++++++++ nym-api/src/coconut/api_routes/mod.rs | 5 +++++ nym-connect/desktop/Cargo.lock | 14 ++++++++++++++ nym-wallet/Cargo.lock | 14 ++++++++++++++ 6 files changed, 52 insertions(+), 1 deletion(-) diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index d670033049..bb3125f001 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -15,6 +15,7 @@ pub const REWARDED: &str = "rewarded"; pub const COCONUT_ROUTES: &str = "coconut"; pub const BANDWIDTH: &str = "bandwidth"; +pub const COCONUT_FREE_PASS: &str = "free-pass"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential"; pub const COCONUT_EPOCH_CREDENTIALS: &str = "epoch-credentials"; diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index 1baf4d17dd..d76ce70675 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -16,10 +16,13 @@ serde = { workspace = true, features = ["derive"] } ts-rs = { workspace = true, optional = true } tendermint = { workspace = true } +# for serde on secp256k1 signatures +ecdsa = { version = "0.16", features = ["serde"] } + nym-credentials-interface = { path = "../../common/credentials-interface" } nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"]} -nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } +nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } nym-node-requests = { path = "../../nym-node/nym-node-requests", default-features = false } [features] diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index c4de811281..4092f8fbee 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -140,6 +140,20 @@ impl BlindedSignatureResponse { } } +#[derive(Serialize, Deserialize)] +pub struct FreePassRequest { + // secp256k1 key associated with the admin account + pub cosmos_pubkey: cosmrs::crypto::PublicKey, + + pub inner_sign_request: BlindSignRequest, + + /// Signature on the inner sign request + /// to prove the possession of the cosmos key/address + pub signature: cosmrs::crypto::secp256k1::Signature, + + pub public_attributes_plain: Vec, +} + #[derive(Serialize, Deserialize)] pub struct VerificationKeyResponse { pub key: VerificationKey, diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index b2b2dfc4bc..8d0a6a2caf 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -26,6 +26,11 @@ use rocket::State as RocketState; mod helpers; +pub async fn post_free_pass(state: &RocketState) { + // attach secp256k1 pubkey; derive address; check contract admin verify signature + todo!() +} + #[post("/blind-sign", data = "")] // Until we have serialization and deserialization traits we'll be using a crutch pub async fn post_blind_sign( diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 6480b0a255..3c146f252d 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -1729,6 +1729,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", + "serdect", "signature 2.1.0", "spki 0.7.2", ] @@ -1837,6 +1838,7 @@ dependencies = [ "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", + "serdect", "subtle 2.4.1", "zeroize", ] @@ -3693,6 +3695,7 @@ dependencies = [ "bs58 0.4.0", "cosmrs", "cosmwasm-std", + "ecdsa 0.16.8", "getset", "nym-credentials-interface", "nym-crypto", @@ -5824,6 +5827,7 @@ dependencies = [ "der 0.7.8", "generic-array 0.14.7", "pkcs8 0.10.2", + "serdect", "subtle 2.4.1", "zeroize", ] @@ -6149,6 +6153,16 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct 0.2.0", + "serde", +] + [[package]] name = "serialize-to-javascript" version = "0.1.1" diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index e9cdff7476..885674bddb 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -1480,6 +1480,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", + "serdect", "signature 2.1.0", "spki 0.7.2", ] @@ -1588,6 +1589,7 @@ dependencies = [ "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", + "serdect", "subtle 2.4.1", "zeroize", ] @@ -3097,6 +3099,7 @@ dependencies = [ "bs58 0.4.0", "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "cosmwasm-std", + "ecdsa 0.16.8", "getset", "nym-credentials-interface", "nym-crypto", @@ -4690,6 +4693,7 @@ dependencies = [ "der 0.7.8", "generic-array 0.14.7", "pkcs8 0.10.2", + "serdect", "subtle 2.4.1", "zeroize", ] @@ -4878,6 +4882,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct 0.2.0", + "serde", +] + [[package]] name = "serialize-to-javascript" version = "0.1.1" From c9ff5503118dcfb223410fd3893af8a9e553c918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 16:54:07 +0000 Subject: [PATCH 22/49] nym-api logic for issuing free passes (minus storage impl) --- nym-api/Cargo.toml | 1 + .../20240209120000_free_pass_nonces.sql | 10 ++ .../nym-api-requests/src/coconut/models.rs | 27 ++++- nym-api/src/coconut/api_routes/mod.rs | 103 +++++++++++++++++- nym-api/src/coconut/client.rs | 2 + nym-api/src/coconut/error.rs | 25 +++++ nym-api/src/coconut/helpers.rs | 37 ++++++- nym-api/src/coconut/mod.rs | 1 + nym-api/src/coconut/state.rs | 11 +- nym-api/src/coconut/storage/mod.rs | 12 ++ nym-api/src/coconut/tests/mod.rs | 57 ++++++++++ nym-api/src/support/nyxd/mod.rs | 14 +++ 12 files changed, 286 insertions(+), 14 deletions(-) create mode 100644 nym-api/migrations/20240209120000_free_pass_nonces.sql diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index db80920086..536f7915a8 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -26,6 +26,7 @@ dirs = "4.0" futures = { workspace = true } itertools = "0.12.0" humantime-serde = "1.0" +k256 = { version = "*", features = ["ecdsa-core"] } # needed for the Verifier trait; pull whatever version is used by other dependencies lazy_static = "1.4.0" log = { workspace = true } pin-project = "1.0" diff --git a/nym-api/migrations/20240209120000_free_pass_nonces.sql b/nym-api/migrations/20240209120000_free_pass_nonces.sql new file mode 100644 index 0000000000..da6fd73d37 --- /dev/null +++ b/nym-api/migrations/20240209120000_free_pass_nonces.sql @@ -0,0 +1,10 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE TABLE ISSUED_FREEPASS +( + id INTEGER PRIMARY KEY CHECK (id = 0), + current_nonce INTEGER NOT NULL +); \ No newline at end of file diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index 4092f8fbee..295a8b2720 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -140,20 +140,41 @@ impl BlindedSignatureResponse { } } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct FreePassRequest { // secp256k1 key associated with the admin account pub cosmos_pubkey: cosmrs::crypto::PublicKey, pub inner_sign_request: BlindSignRequest, - /// Signature on the inner sign request + // we need to include a nonce here to prevent replay attacks + // (and not making the nym-api store the serial numbers of all issued credential) + pub used_nonce: u64, + + /// Signature on the nonce /// to prove the possession of the cosmos key/address - pub signature: cosmrs::crypto::secp256k1::Signature, + pub nonce_signature: cosmrs::crypto::secp256k1::Signature, pub public_attributes_plain: Vec, } +impl FreePassRequest { + pub fn tendermint_pubkey(&self) -> tendermint::PublicKey { + self.cosmos_pubkey.into() + } + + pub fn nonce_plaintext(&self) -> [u8; 8] { + self.used_nonce.to_be_bytes() + } + + pub fn public_attributes_hashed(&self) -> Vec { + self.public_attributes_plain + .iter() + .map(hash_to_scalar) + .collect() + } +} + #[derive(Serialize, Deserialize)] pub struct VerificationKeyResponse { pub key: VerificationKey, diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 8d0a6a2caf..76a53c0d8e 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -6,8 +6,9 @@ use crate::coconut::error::{CoconutError, Result}; use crate::coconut::helpers::{accepted_vote_err, blind_sign}; use crate::coconut::state::State; use crate::coconut::storage::CoconutStorageExt; +use k256::ecdsa::signature::Verifier; use nym_api_requests::coconut::models::{ - CredentialsRequestBody, EpochCredentialsResponse, IssuedCredentialResponse, + CredentialsRequestBody, EpochCredentialsResponse, FreePassRequest, IssuedCredentialResponse, IssuedCredentialsResponse, }; use nym_api_requests::coconut::{ @@ -23,12 +24,93 @@ use nym_credentials::coconut::bandwidth::{ use nym_validator_client::nyxd::Coin; use rocket::serde::json::Json; use rocket::State as RocketState; +use std::ops::Deref; mod helpers; -pub async fn post_free_pass(state: &RocketState) { - // attach secp256k1 pubkey; derive address; check contract admin verify signature - todo!() +#[post("/free-pass", data = "")] +pub async fn post_free_pass( + freepass_request_body: Json, + state: &RocketState, +) -> Result> { + debug!("Received free pass sign request"); + trace!("body: {:?}", freepass_request_body); + + // grab the admin of the bandwidth contract + let Some(authorised_admin) = state.get_bandwidth_contract_admin().await? else { + error!("our bandwidth contract does not have an admin set! We won't be able to migrate the contract! We should redeploy it ASAP"); + return Err(CoconutError::MissingBandwidthContractAdmin); + }; + + // derive the address out of the provided pubkey + let requester = match freepass_request_body + .cosmos_pubkey + .account_id(authorised_admin.prefix()) + { + Ok(address) => address, + Err(err) => { + return Err(CoconutError::AdminAccountDerivationFailure { + formatted_source: err.to_string(), + }) + } + }; + debug!("derived the following address out of the provided public key: {requester}. Going to check it against the authorised admin ({authorised_admin})"); + + if &requester != authorised_admin { + return Err(CoconutError::UnauthorisedFreePassAccount { + requester, + authorised_admin: authorised_admin.clone(), + }); + } + + let current_nonce = state.storage.get_current_freepass_nonce().await?; + debug!("the current expected nonce is {current_nonce}"); + + if current_nonce != freepass_request_body.used_nonce { + return Err(CoconutError::InvalidNonce { + current: current_nonce, + received: freepass_request_body.used_nonce, + }); + } + + // check if we have the signing key available + debug!("checking if we actually have coconut keys derived..."); + let maybe_keypair_guard = state.coconut_keypair.get().await; + let Some(keypair_guard) = maybe_keypair_guard.as_ref() else { + return Err(CoconutError::KeyPairNotDerivedYet); + }; + let Some(signing_key) = keypair_guard.as_ref() else { + return Err(CoconutError::KeyPairNotDerivedYet); + }; + + let tm_pubkey = freepass_request_body.tendermint_pubkey(); + + // currently accounts (excluding validators) don't use ed25519 and are secp256k1-based + let Some(secp256k1_pubkey) = tm_pubkey.secp256k1() else { + return Err(CoconutError::UnsupportedNonSecp256k1Key); + }; + + // make sure the signature actually verifies + secp256k1_pubkey + .verify( + &freepass_request_body.nonce_plaintext(), + &freepass_request_body.nonce_signature, + ) + .map_err(|_| CoconutError::FreePassSignatureVerificationFailure)?; + + // produce the partial signature + debug!("producing the partial credential"); + let blinded_signature = + blind_sign(freepass_request_body.deref(), signing_key.keys.secret_key())?; + + // update the nonce in storage (and also check if a parallel request hasn't updated it; if so we return an error. no race conditions allowed) + state + .storage + .update_and_validate_freepass_nonce(current_nonce + 1) + .await?; + + // finally return the credential to the client + Ok(Json(BlindedSignatureResponse { blinded_signature })) } #[post("/blind-sign", data = "")] @@ -82,7 +164,10 @@ pub async fn post_blind_sign( // produce the partial signature debug!("producing the partial credential"); - let blinded_signature = blind_sign(&blind_sign_request_body, signing_key.keys.secret_key())?; + let blinded_signature = blind_sign( + blind_sign_request_body.deref(), + signing_key.keys.secret_key(), + )?; // store the information locally debug!("storing the issued credential in the database"); @@ -223,3 +308,11 @@ pub async fn issued_credentials( build_credentials_response(credentials).map(Json) } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn foo() {} +} diff --git a/nym-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs index f018c1f72d..bf0b7ac60a 100644 --- a/nym-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -27,6 +27,8 @@ pub trait Client { async fn address(&self) -> AccountId; async fn dkg_contract_address(&self) -> Result; + + async fn bandwidth_contract_admin(&self) -> Result>; async fn get_tx(&self, tx_hash: Hash) -> Result; diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index ec43b0d65d..9efa07b2b7 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -11,6 +11,7 @@ use nym_crypto::asymmetric::{ use nym_dkg::error::DkgError; use nym_validator_client::coconut::CoconutApiError; use nym_validator_client::nyxd::error::{NyxdError, TendermintError}; +use nym_validator_client::nyxd::AccountId; use rocket::http::{ContentType, Status}; use rocket::response::Responder; use rocket::{response, Request, Response}; @@ -25,6 +26,30 @@ pub enum CoconutError { #[error(transparent)] IOError(#[from] std::io::Error), + #[error("the address of the bandwidth contract hasn't been set")] + MissingBandwidthContractAddress, + + #[error("the current bandwidth contract does not have any admin address set")] + MissingBandwidthContractAdmin, + + #[error("failed to derive the admin account from the provided public key: {formatted_source}")] + AdminAccountDerivationFailure { formatted_source: String }, + + #[error("the requester of the free pass ({requester}) is not authorised. the only allowed account is {authorised_admin}.")] + UnauthorisedFreePassAccount { + requester: AccountId, + authorised_admin: AccountId, + }, + + #[error("failed to verify signature on the provided free pass request")] + FreePassSignatureVerificationFailure, + + #[error("the provided signing nonce is invalid. the current value is: {current}. got {received} instead")] + InvalidNonce { current: u64, received: u64 }, + + #[error("only secp256k1 keys are supported for free pass issuance")] + UnsupportedNonSecp256k1Key, + #[error("the received bandwidth voucher did not contain deposit value")] MissingBandwidthValue, diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs index ee55f9ff1d..15b9a15477 100644 --- a/nym-api/src/coconut/helpers.rs +++ b/nym-api/src/coconut/helpers.rs @@ -3,8 +3,9 @@ use crate::coconut::error::CoconutError; use crate::coconut::state::bandwidth_credential_params; +use nym_api_requests::coconut::models::FreePassRequest; use nym_api_requests::coconut::BlindSignRequestBody; -use nym_coconut::{BlindedSignature, SecretKey}; +use nym_coconut::{Attribute, BlindSignRequest, BlindedSignature, SecretKey}; use nym_validator_client::nyxd::error::NyxdError::AbciError; // If the result is already established, the vote might be redundant and @@ -21,17 +22,43 @@ pub(crate) fn accepted_vote_err(ret: Result<(), CoconutError>) -> Result<(), Coc Ok(()) } -pub(crate) fn blind_sign( - request: &BlindSignRequestBody, +pub(crate) trait CredentialRequest { + fn blind_sign_request(&self) -> &BlindSignRequest; + + fn public_attributes(&self) -> Vec; +} + +impl CredentialRequest for BlindSignRequestBody { + fn blind_sign_request(&self) -> &BlindSignRequest { + &self.inner_sign_request + } + + fn public_attributes(&self) -> Vec { + self.public_attributes_hashed() + } +} + +impl CredentialRequest for FreePassRequest { + fn blind_sign_request(&self) -> &BlindSignRequest { + &self.inner_sign_request + } + + fn public_attributes(&self) -> Vec { + self.public_attributes_hashed() + } +} + +pub(crate) fn blind_sign( + request: &C, signing_key: &SecretKey, ) -> Result { - let public_attributes = request.public_attributes_hashed(); + let public_attributes = request.public_attributes(); let attributes_ref = public_attributes.iter().collect::>(); Ok(nym_coconut::blind_sign( bandwidth_credential_params(), signing_key, - &request.inner_sign_request, + &request.blind_sign_request(), &attributes_ref, )?) } diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index 2e1af74968..82ca3c59b2 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -52,6 +52,7 @@ where // this format! is so ugly... format!("/{NYM_API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}"), routes![ + api_routes::post_free_pass, api_routes::post_blind_sign, api_routes::verify_bandwidth_credential, api_routes::epoch_credentials, diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs index d92a5d6f81..c85c77575a 100644 --- a/nym-api/src/coconut/state.rs +++ b/nym-api/src/coconut/state.rs @@ -13,13 +13,15 @@ use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut::{BlindedSignature, VerificationKey}; use nym_coconut_dkg_common::types::EpochId; use nym_crypto::asymmetric::identity; -use nym_validator_client::nyxd::{Hash, TxResponse}; +use nym_validator_client::nyxd::{AccountId, Hash, TxResponse}; use std::sync::Arc; +use tokio::sync::OnceCell; pub use nym_credentials::coconut::bandwidth::bandwidth_credential_params; pub struct State { pub(crate) client: Arc, + pub(crate) bandwidth_contract_admin: OnceCell>, pub(crate) mix_denom: String, pub(crate) coconut_keypair: KeyPair, pub(crate) identity_keypair: identity::KeyPair, @@ -45,6 +47,7 @@ impl State { Self { client, + bandwidth_contract_admin: OnceCell::new(), mix_denom, coconut_keypair: key_pair, identity_keypair, @@ -67,6 +70,12 @@ impl State { self.client.get_tx(tx_hash).await } + pub async fn get_bandwidth_contract_admin(&self) -> Result<&Option> { + self.bandwidth_contract_admin + .get_or_try_init(|| async { self.client.bandwidth_contract_admin().await }) + .await + } + pub async fn validate_request( &self, request: &BlindSignRequestBody, diff --git a/nym-api/src/coconut/storage/mod.rs b/nym-api/src/coconut/storage/mod.rs index 5633ba468d..ad7a98ff05 100644 --- a/nym-api/src/coconut/storage/mod.rs +++ b/nym-api/src/coconut/storage/mod.rs @@ -63,6 +63,10 @@ pub trait CoconutStorageExt { &self, pagination: Pagination, ) -> Result, NymApiStorageError>; + + async fn get_current_freepass_nonce(&self) -> Result; + + async fn update_and_validate_freepass_nonce(&self, new: u64) -> Result<(), NymApiStorageError>; } #[async_trait] @@ -163,4 +167,12 @@ impl CoconutStorageExt for NymApiStorage { .get_issued_credentials_paged(start_after, limit) .await?) } + + async fn get_current_freepass_nonce(&self) -> Result { + todo!() + } + + async fn update_and_validate_freepass_nonce(&self, new: u64) -> Result<(), NymApiStorageError> { + todo!() + } } diff --git a/nym-api/src/coconut/tests/mod.rs b/nym-api/src/coconut/tests/mod.rs index 726c42dbd2..fe0ad3b165 100644 --- a/nym-api/src/coconut/tests/mod.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -275,6 +275,7 @@ impl FakeMultisigContractState { #[derive(Debug)] pub(crate) struct FakeBandwidthContractState { pub(crate) address: Addr, + pub(crate) admin: Option, pub(crate) spent_credentials: HashMap, } @@ -313,6 +314,11 @@ impl Default for FakeChainState { let bandwidth_contract = Addr::unchecked("n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq746x3uffylqkjar4l"); + let bandwidth_contract_admin = + "n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a" + .parse() + .unwrap(); + FakeChainState { _counters: Default::default(), @@ -346,6 +352,7 @@ impl Default for FakeChainState { }, bandwidth_contract: FakeBandwidthContractState { address: bandwidth_contract, + admin: Some(bandwidth_contract_admin), spent_credentials: Default::default(), }, } @@ -612,6 +619,10 @@ impl super::client::Client for DummyClient { Ok(self.state.lock().unwrap().dkg_contract.address.clone()) } + async fn bandwidth_contract_admin(&self) -> Result> { + Ok(self.state.lock().unwrap().bandwidth_contract.admin.clone()) + } + async fn get_tx(&self, tx_hash: Hash) -> Result { Ok(self .state @@ -786,6 +797,52 @@ impl super::client::Client for DummyClient { }) } + async fn get_dealer_dealings_status( + &self, + epoch_id: EpochId, + dealer: String, + ) -> Result { + let guard = self.state.lock().unwrap(); + let key_size = guard.dkg_contract.contract_state.key_size; + + let dealer_addr = Addr::unchecked(&dealer); + + let Some(epoch_dealings) = guard.dkg_contract.dealings.get(&epoch_id) else { + return Ok(DealerDealingsStatusResponse { + epoch_id, + dealer: dealer_addr, + all_dealings_fully_submitted: false, + dealing_submission_status: Default::default(), + }); + }; + + let Some(dealer_dealings) = epoch_dealings.get(&dealer) else { + return Ok(DealerDealingsStatusResponse { + epoch_id, + dealer: dealer_addr, + all_dealings_fully_submitted: false, + dealing_submission_status: Default::default(), + }); + }; + + let mut dealing_submission_status: BTreeMap = BTreeMap::new(); + for dealing_index in 0..key_size { + let metadata = dealer_dealings + .get(&dealing_index) + .map(|d| d.metadata.clone()); + dealing_submission_status.insert(dealing_index, metadata.into()); + } + + Ok(DealerDealingsStatusResponse { + epoch_id, + dealer: Addr::unchecked(&dealer), + all_dealings_fully_submitted: dealing_submission_status + .values() + .all(|d| d.fully_submitted), + dealing_submission_status, + }) + } + async fn get_dealing_status( &self, epoch_id: EpochId, diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 1ae492206e..79fb39b663 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -355,6 +355,20 @@ impl crate::coconut::client::Client for Client { ) } + async fn bandwidth_contract_admin(&self) -> crate::coconut::error::Result> { + let guard = self.inner.read().await; + + let bandwidth_contract = query_guard!( + guard, + coconut_bandwidth_contract_address() + .ok_or(CoconutError::MissingBandwidthContractAddress) + )?; + + let contract = query_guard!(guard, get_contract(bandwidth_contract)).await?; + + Ok(contract.contract_info.admin) + } + async fn get_tx(&self, tx_hash: Hash) -> crate::coconut::error::Result { nyxd_query!(self, get_tx(tx_hash).await).map_err(|source| { CoconutError::TxRetrievalFailure { From f61b898c4f0b625f480ef8204f4269d794c2f005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 17:23:58 +0000 Subject: [PATCH 23/49] storage implementation --- .../20240209120000_free_pass_nonces.sql | 6 +- .../nym-api-requests/src/coconut/models.rs | 4 +- nym-api/src/coconut/error.rs | 2 +- nym-api/src/coconut/storage/manager.rs | 57 +++++++++++++++++++ nym-api/src/coconut/storage/mod.rs | 8 +-- 5 files changed, 68 insertions(+), 9 deletions(-) diff --git a/nym-api/migrations/20240209120000_free_pass_nonces.sql b/nym-api/migrations/20240209120000_free_pass_nonces.sql index da6fd73d37..441910036b 100644 --- a/nym-api/migrations/20240209120000_free_pass_nonces.sql +++ b/nym-api/migrations/20240209120000_free_pass_nonces.sql @@ -3,8 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -CREATE TABLE ISSUED_FREEPASS +CREATE TABLE issued_freepass ( id INTEGER PRIMARY KEY CHECK (id = 0), current_nonce INTEGER NOT NULL -); \ No newline at end of file +); + +INSERT INTO issued_freepass(id, current_nonce) VALUES (0,0); \ No newline at end of file diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index 295a8b2720..0a3c3c0138 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -149,7 +149,7 @@ pub struct FreePassRequest { // we need to include a nonce here to prevent replay attacks // (and not making the nym-api store the serial numbers of all issued credential) - pub used_nonce: u64, + pub used_nonce: u32, /// Signature on the nonce /// to prove the possession of the cosmos key/address @@ -163,7 +163,7 @@ impl FreePassRequest { self.cosmos_pubkey.into() } - pub fn nonce_plaintext(&self) -> [u8; 8] { + pub fn nonce_plaintext(&self) -> [u8; 4] { self.used_nonce.to_be_bytes() } diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 9efa07b2b7..969b7dfeb2 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -45,7 +45,7 @@ pub enum CoconutError { FreePassSignatureVerificationFailure, #[error("the provided signing nonce is invalid. the current value is: {current}. got {received} instead")] - InvalidNonce { current: u64, received: u64 }, + InvalidNonce { current: u32, received: u32 }, #[error("only secp256k1 keys are supported for free pass issuance")] UnsupportedNonSecp256k1Key, diff --git a/nym-api/src/coconut/storage/manager.rs b/nym-api/src/coconut/storage/manager.rs index e40b2a4a96..17816bebd8 100644 --- a/nym-api/src/coconut/storage/manager.rs +++ b/nym-api/src/coconut/storage/manager.rs @@ -4,6 +4,7 @@ use crate::coconut::storage::models::{EpochCredentials, IssuedCredential}; use crate::support::storage::manager::StorageManager; use nym_coconut_dkg_common::types::EpochId; +use thiserror::Error; #[async_trait] pub trait CoconutStorageManagerExt { @@ -120,6 +121,17 @@ pub trait CoconutStorageManagerExt { start_after: i64, limit: u32, ) -> Result, sqlx::Error>; + + /// Attempts to retrieve the current value of the freepass nonce. + async fn get_current_freepass_nonce(&self) -> Result; + + /// Attempt to update the currently stored nonce to the provided value whilst ensuring + /// it's strictly equal the current value plus 1 + /// + /// # Arguments + /// + /// * `new`: the new value of the free pass nonce + async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), sqlx::Error>; } #[async_trait] @@ -378,4 +390,49 @@ impl CoconutStorageManagerExt for StorageManager { .fetch_all(&self.connection_pool) .await } + + /// Attempts to retrieve the current value of the freepass nonce. + async fn get_current_freepass_nonce(&self) -> Result { + sqlx::query!("SELECT current_nonce as 'current_nonce: u32' FROM issued_freepass") + .fetch_one(&self.connection_pool) + .await + .map(|row| row.current_nonce) + } + + /// Attempt to update the currently stored nonce to the provided value whilst ensuring + /// it's strictly equal the current value plus 1 + /// + /// # Arguments + /// + /// * `new`: the new value of the free pass nonce + async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), sqlx::Error> { + let mut tx = self.connection_pool.begin().await?; + + let currently_stored = + sqlx::query!("SELECT current_nonce as 'current_nonce: u32' FROM issued_freepass") + .fetch_one(&mut tx) + .await? + .current_nonce; + + if currently_stored + 1 != new { + // this is not the best error but I really don't want to be creating a new enum + return Err(sqlx::Error::Decode(Box::new(UnexpectedNonce { + current: currently_stored, + got: new, + }))); + } + + sqlx::query!("UPDATE issued_freepass SET current_nonce = ?", new) + .execute(&mut tx) + .await?; + + tx.commit().await + } +} + +#[derive(Debug, Error)] +#[error("tried to store an invalid nonce. the received value is {got} while current is {current}. expected {current} + 1")] +pub struct UnexpectedNonce { + current: u32, + got: u32, } diff --git a/nym-api/src/coconut/storage/mod.rs b/nym-api/src/coconut/storage/mod.rs index ad7a98ff05..89156ee7da 100644 --- a/nym-api/src/coconut/storage/mod.rs +++ b/nym-api/src/coconut/storage/mod.rs @@ -64,9 +64,9 @@ pub trait CoconutStorageExt { pagination: Pagination, ) -> Result, NymApiStorageError>; - async fn get_current_freepass_nonce(&self) -> Result; + async fn get_current_freepass_nonce(&self) -> Result; - async fn update_and_validate_freepass_nonce(&self, new: u64) -> Result<(), NymApiStorageError>; + async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), NymApiStorageError>; } #[async_trait] @@ -168,11 +168,11 @@ impl CoconutStorageExt for NymApiStorage { .await?) } - async fn get_current_freepass_nonce(&self) -> Result { + async fn get_current_freepass_nonce(&self) -> Result { todo!() } - async fn update_and_validate_freepass_nonce(&self, new: u64) -> Result<(), NymApiStorageError> { + async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), NymApiStorageError> { todo!() } } From 96f3192694ecf40cb0d57def6bb6ad26022591d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 9 Feb 2024 17:35:07 +0000 Subject: [PATCH 24/49] validating request attributes --- .../credentials/src/coconut/bandwidth/mod.rs | 2 +- nym-api/src/coconut/api_routes/mod.rs | 44 ++++++++++++++++++- nym-api/src/coconut/error.rs | 31 ++++++++++++- nym-api/src/coconut/storage/mod.rs | 4 +- 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/common/credentials/src/coconut/bandwidth/mod.rs b/common/credentials/src/coconut/bandwidth/mod.rs index 195db20598..8a0e8f4136 100644 --- a/common/credentials/src/coconut/bandwidth/mod.rs +++ b/common/credentials/src/coconut/bandwidth/mod.rs @@ -6,7 +6,7 @@ use std::sync::OnceLock; pub use issuance::IssuanceBandwidthCredential; pub use issued::IssuedBandwidthCredential; pub use nym_credentials_interface::{ - CredentialSigningData, CredentialSpendingData, CredentialType, Parameters, + CredentialSigningData, CredentialSpendingData, CredentialType, Parameters, UnknownCredentialType }; pub mod freepass; diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 76a53c0d8e..45fef58525 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -18,16 +18,56 @@ use nym_coconut_bandwidth_contract_common::spend_credential::{ funds_from_cosmos_msgs, SpendCredentialStatus, }; use nym_coconut_dkg_common::types::EpochId; +use nym_credentials::coconut::bandwidth::freepass::MAX_FREE_PASS_VALIDITY; use nym_credentials::coconut::bandwidth::{ - bandwidth_credential_params, IssuanceBandwidthCredential, + bandwidth_credential_params, CredentialType, IssuanceBandwidthCredential, }; use nym_validator_client::nyxd::Coin; use rocket::serde::json::Json; use rocket::State as RocketState; use std::ops::Deref; +use time::OffsetDateTime; mod helpers; +fn validate_freepass_public_attributes(res: &FreePassRequest) -> Result<()> { + let public_attributes = &res.public_attributes_plain; + + if public_attributes.len() != IssuanceBandwidthCredential::PUBLIC_ATTRIBUTES as usize { + return Err(CoconutError::InvalidFreePassAttributes { + got: public_attributes.len(), + expected: IssuanceBandwidthCredential::PUBLIC_ATTRIBUTES as usize, + }); + } + + // SAFETY: we just ensured correct number of attributes + let expiry_raw = public_attributes.first().unwrap(); + let type_raw = public_attributes.get(1).unwrap(); + + let parsed_type = type_raw.parse::()?; + if parsed_type != CredentialType::FreePass { + return Err(CoconutError::InvalidFreePassTypeAttribute { got: parsed_type }); + } + + let expiry_timestamp: i64 = expiry_raw + .parse() + .map_err(|source| CoconutError::ExpiryDateParsingFailure { source })?; + + let expiry_date = OffsetDateTime::from_unix_timestamp(expiry_timestamp).map_err(|source| { + CoconutError::InvalidExpiryDate { + unix_timestamp: expiry_timestamp, + source, + } + })?; + let now = OffsetDateTime::now_utc(); + + if expiry_date > now + MAX_FREE_PASS_VALIDITY { + return Err(CoconutError::TooLongFreePass { expiry_date }); + } + + Ok(()) +} + #[post("/free-pass", data = "")] pub async fn post_free_pass( freepass_request_body: Json, @@ -36,6 +76,8 @@ pub async fn post_free_pass( debug!("Received free pass sign request"); trace!("body: {:?}", freepass_request_body); + validate_freepass_public_attributes(&freepass_request_body)?; + // grab the admin of the bandwidth contract let Some(authorised_admin) = state.get_bandwidth_contract_admin().await? else { error!("our bandwidth contract does not have an admin set! We won't be able to migrate the contract! We should redeploy it ASAP"); diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 969b7dfeb2..d5f6ac81d6 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -3,7 +3,7 @@ use crate::node_status_api::models::NymApiStorageError; use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId}; -use nym_credentials::coconut::bandwidth::CredentialType; +use nym_credentials::coconut::bandwidth::{CredentialType, UnknownCredentialType}; use nym_crypto::asymmetric::{ encryption::KeyRecoveryError, identity::{Ed25519RecoveryError, SignatureError}, @@ -18,6 +18,8 @@ use rocket::{response, Request, Response}; use std::io::Cursor; use std::num::ParseIntError; use thiserror::Error; +use time::error::ComponentRange; +use time::OffsetDateTime; pub type Result = std::result::Result; @@ -50,6 +52,33 @@ pub enum CoconutError { #[error("only secp256k1 keys are supported for free pass issuance")] UnsupportedNonSecp256k1Key, + #[error("received credential request for an unknown type: {0}")] + UnknownCredentialType(#[from] UnknownCredentialType), + + #[error("the provided free pass request had an unexpected number of public attributes. got {got} but expected {expected}")] + InvalidFreePassAttributes { got: usize, expected: usize }, + + #[error("the provided free pass request had an invalid type attribute (got: '{got}')")] + InvalidFreePassTypeAttribute { got: CredentialType }, + + #[error("failed to parse the free pass expiry date: {source}")] + ExpiryDateParsingFailure { + #[source] + source: ParseIntError, + }, + + #[error("failed to parse expiry timestamp into proper datetime: {source}")] + InvalidExpiryDate { + unix_timestamp: i64, + #[source] + source: ComponentRange, + }, + + #[error( + "the provided free pass request has too long expiry (expiry is set to on {expiry_date})" + )] + TooLongFreePass { expiry_date: OffsetDateTime }, + #[error("the received bandwidth voucher did not contain deposit value")] MissingBandwidthValue, diff --git a/nym-api/src/coconut/storage/mod.rs b/nym-api/src/coconut/storage/mod.rs index 89156ee7da..f77703cea8 100644 --- a/nym-api/src/coconut/storage/mod.rs +++ b/nym-api/src/coconut/storage/mod.rs @@ -169,10 +169,10 @@ impl CoconutStorageExt for NymApiStorage { } async fn get_current_freepass_nonce(&self) -> Result { - todo!() + Ok(self.manager.get_current_freepass_nonce().await?) } async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), NymApiStorageError> { - todo!() + Ok(self.manager.update_and_validate_freepass_nonce(new).await?) } } From 3fa74c90ff3ee518cab794a7a0797d05735aa955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 12 Feb 2024 10:01:50 +0000 Subject: [PATCH 25/49] cargo fmt --- common/credentials/src/coconut/bandwidth/mod.rs | 3 ++- nym-api/src/coconut/client.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/credentials/src/coconut/bandwidth/mod.rs b/common/credentials/src/coconut/bandwidth/mod.rs index 8a0e8f4136..de49c9d4b4 100644 --- a/common/credentials/src/coconut/bandwidth/mod.rs +++ b/common/credentials/src/coconut/bandwidth/mod.rs @@ -6,7 +6,8 @@ use std::sync::OnceLock; pub use issuance::IssuanceBandwidthCredential; pub use issued::IssuedBandwidthCredential; pub use nym_credentials_interface::{ - CredentialSigningData, CredentialSpendingData, CredentialType, Parameters, UnknownCredentialType + CredentialSigningData, CredentialSpendingData, CredentialType, Parameters, + UnknownCredentialType, }; pub mod freepass; diff --git a/nym-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs index bf0b7ac60a..45ebe7758e 100644 --- a/nym-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -27,7 +27,7 @@ pub trait Client { async fn address(&self) -> AccountId; async fn dkg_contract_address(&self) -> Result; - + async fn bandwidth_contract_admin(&self) -> Result>; async fn get_tx(&self, tx_hash: Hash) -> Result; From c2517ac63bef0730d2333fffb75a9e5fd2645f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 12 Feb 2024 17:29:44 +0000 Subject: [PATCH 26/49] clippy --- nym-api/src/coconut/api_routes/mod.rs | 8 -------- nym-api/src/coconut/helpers.rs | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 45fef58525..4a5bfe8f2a 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -350,11 +350,3 @@ pub async fn issued_credentials( build_credentials_response(credentials).map(Json) } - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn foo() {} -} diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs index 15b9a15477..699c9b8823 100644 --- a/nym-api/src/coconut/helpers.rs +++ b/nym-api/src/coconut/helpers.rs @@ -58,7 +58,7 @@ pub(crate) fn blind_sign( Ok(nym_coconut::blind_sign( bandwidth_credential_params(), signing_key, - &request.blind_sign_request(), + request.blind_sign_request(), &attributes_ref, )?) } From 3f0194a9aa6697ae52a0de99238c64928fbe8d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 12 Feb 2024 16:26:19 +0000 Subject: [PATCH 27/49] nym-cli commands for issuing free passes --- Cargo.lock | 4 + .../bandwidth-controller/src/acquire/mod.rs | 2 +- common/bandwidth-controller/src/lib.rs | 2 +- .../validator-client/src/client.rs | 15 +- .../validator-client/src/nym_api/mod.rs | 32 +++ .../validator-client/src/nym_api/routes.rs | 1 + .../validator-client/src/nyxd/mod.rs | 4 + common/commands/Cargo.toml | 4 + .../commands/src/coconut/generate_freepass.rs | 188 ++++++++++++++++++ common/commands/src/coconut/mod.rs | 2 + .../src/coconut/bandwidth/freepass.rs | 57 +++++- .../src/coconut/bandwidth/issuance.rs | 77 +++++-- .../src/coconut/bandwidth/issued.rs | 6 + common/credentials/src/coconut/utils.rs | 6 +- common/credentials/src/error.rs | 9 + common/nymcoconut/src/scheme/mod.rs | 9 + gateway/gateway-requests/src/models.rs | 2 +- .../websocket/connection_handler/coconut.rs | 4 +- nym-api/nym-api-requests/src/coconut/mod.rs | 2 +- .../nym-api-requests/src/coconut/models.rs | 21 ++ nym-api/src/coconut/api_routes/mod.rs | 16 +- nym-api/src/coconut/comm.rs | 2 +- nym-api/src/coconut/mod.rs | 1 + tools/nym-cli/src/coconut/mod.rs | 7 + 24 files changed, 439 insertions(+), 34 deletions(-) create mode 100644 common/commands/src/coconut/generate_freepass.rs diff --git a/Cargo.lock b/Cargo.lock index a91515295e..5c93874c55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5107,6 +5107,7 @@ dependencies = [ "cosmwasm-std", "csv", "cw-utils", + "futures", "handlebars", "humantime-serde", "inquire", @@ -5122,6 +5123,7 @@ dependencies = [ "nym-credential-storage", "nym-credential-utils", "nym-credentials", + "nym-credentials-interface", "nym-crypto", "nym-mixnet-contract-common", "nym-multisig-contract-common", @@ -5139,8 +5141,10 @@ dependencies = [ "tap", "thiserror", "time", + "tokio", "toml 0.5.11", "url", + "zeroize", ] [[package]] diff --git a/common/bandwidth-controller/src/acquire/mod.rs b/common/bandwidth-controller/src/acquire/mod.rs index b61bfc2ad4..4f2431b45b 100644 --- a/common/bandwidth-controller/src/acquire/mod.rs +++ b/common/bandwidth-controller/src/acquire/mod.rs @@ -67,7 +67,7 @@ where let signature = obtain_aggregate_signature(&state.voucher, &coconut_api_clients, threshold).await?; - let issued = state.voucher.to_issued_credential(signature); + let issued = state.voucher.to_issued_credential(signature, epoch_id); // make sure the data gets zeroized after persisting it let credential_data = Zeroizing::new(issued.pack_v1()); diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index 3fa91802ee..fbfcf41865 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -51,7 +51,7 @@ impl BandwidthController { ::StorageError: Send + Sync + 'static, { let coconut_api_clients = all_coconut_api_clients(&self.client, epoch_id).await?; - Ok(obtain_aggregate_verification_key(&coconut_api_clients).await?) + Ok(obtain_aggregate_verification_key(&coconut_api_clients)?) } pub async fn prepare_bandwidth_credential( diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index f28e45bd37..968a9ddbd3 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -8,8 +8,10 @@ use crate::{ nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient, ValidatorClientError, }; +use nym_api_requests::coconut::models::FreePassNonceResponse; use nym_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, FreePassRequest, VerifyCredentialBody, + VerifyCredentialResponse, }; use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated}; use nym_api_requests::models::{ @@ -348,4 +350,15 @@ impl NymApiClient { .verify_bandwidth_credential(request_body) .await?) } + + pub async fn free_pass_nonce(&self) -> Result { + Ok(self.nym_api.free_pass_nonce().await?) + } + + pub async fn issue_free_pass_credential( + &self, + request: &FreePassRequest, + ) -> Result { + Ok(self.nym_api.free_pass(request).await?) + } } diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index ced2e4caff..a2ad37e5d8 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -32,6 +32,8 @@ pub mod error; pub mod routes; pub use http_api_client::Client; +use nym_api_requests::coconut::models::FreePassNonceResponse; +use nym_api_requests::coconut::FreePassRequest; #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -373,6 +375,36 @@ pub trait NymApiClientExt: ApiClient { .await } + async fn free_pass_nonce(&self) -> Result { + self.get_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_FREE_PASS_NONCE, + ], + NO_PARAMS, + ) + .await + } + + async fn free_pass( + &self, + request: &FreePassRequest, + ) -> Result { + self.post_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_FREE_PASS_NONCE, + ], + NO_PARAMS, + request, + ) + .await + } + async fn blind_sign( &self, request_body: &BlindSignRequestBody, diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index bb3125f001..212ae0a8c0 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -16,6 +16,7 @@ pub const COCONUT_ROUTES: &str = "coconut"; pub const BANDWIDTH: &str = "bandwidth"; 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 COCONUT_EPOCH_CREDENTIALS: &str = "epoch-credentials"; diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 2757f66967..416bf7bb2f 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -358,6 +358,10 @@ where S: OfflineSigner + Send + Sync, NyxdError: From<::Error>, { + pub fn signing_account(&self) -> Result { + Ok(self.find_account(&self.address())?) + } + pub fn address(&self) -> AccountId { match self.client.signer_addresses() { Ok(addresses) => addresses[0].clone(), diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index a8e665acb4..3a552ecc77 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -15,6 +15,7 @@ cfg-if = "1.0.0" clap = { workspace = true, features = ["derive"] } csv = "1.3.0" cw-utils = { workspace = true } +futures = { workspace = true } handlebars = "3.0.1" humantime-serde = "1.0" inquire = "0.6.2" @@ -25,9 +26,11 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } time = { workspace = true, features = ["parsing", "formatting"] } +tokio = { workspace = true, features = ["sync"]} toml = "0.5.6" url = { workspace = true } tap = "1" +zeroize = { workspace = true } cosmrs = { workspace = true } cosmwasm-std = { workspace = true } @@ -49,6 +52,7 @@ nym-sphinx = { path = "../../common/nymsphinx" } nym-client-core = { path = "../../common/client-core" } nym-config = { path = "../../common/config" } nym-credentials = { path = "../../common/credentials" } +nym-credentials-interface = { path = "../../common/credentials-interface" } nym-credential-storage = { path = "../../common/credential-storage" } nym-credential-utils = { path = "../../common/credential-utils" } diff --git a/common/commands/src/coconut/generate_freepass.rs b/common/commands/src/coconut/generate_freepass.rs new file mode 100644 index 0000000000..7bcd42e858 --- /dev/null +++ b/common/commands/src/coconut/generate_freepass.rs @@ -0,0 +1,188 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::context::SigningClient; +use anyhow::{anyhow, bail}; +use clap::ArgGroup; +use clap::Parser; +use futures::StreamExt; +use log::{error, info}; +use nym_coconut_dkg_common::types::EpochId; +use nym_credential_utils::utils::block_until_coconut_is_available; +use nym_credentials::coconut::bandwidth::freepass::MAX_FREE_PASS_VALIDITY; +use nym_credentials::{ + obtain_aggregate_verification_key, IssuanceBandwidthCredential, IssuedBandwidthCredential, +}; +use nym_credentials_interface::VerificationKey; +use nym_validator_client::coconut::all_coconut_api_clients; +use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, NymContractsProvider}; +use nym_validator_client::nyxd::CosmWasmClient; +use nym_validator_client::signing::AccountData; +use nym_validator_client::CoconutApiClient; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; +use std::sync::Arc; +use time::format_description::well_known::Rfc3339; +use time::OffsetDateTime; +use zeroize::Zeroizing; + +fn parse_rfc3339_expiration_date(raw: &str) -> Result { + OffsetDateTime::parse(raw, &Rfc3339) +} + +#[derive(Debug, Parser)] +#[clap(group(ArgGroup::new("expiration").required(true)))] +pub struct Args { + /// Specifies the expiration date of the free pass(es) + /// Requires + #[clap(long, group = "expiration", value_parser = parse_rfc3339_expiration_date)] + pub(crate) expiration_date: Option, + + #[clap(long, group = "expiration")] + pub(crate) expiration_timestamp: Option, + + /// The number of free passes to issue + #[clap(long, default_value = "1")] + pub(crate) amount: u64, + + /// Path to the output directory for generated free passes. + #[clap(long)] + pub(crate) output_dir: PathBuf, +} + +async fn get_freepass( + api_clients: Vec, + aggregate_vk: &VerificationKey, + threshold: u64, + epoch_id: EpochId, + signing_account: &AccountData, + expiration_date: OffsetDateTime, +) -> anyhow::Result { + let issuance_pass = IssuanceBandwidthCredential::new_freepass(Some(expiration_date)); + let signing_data = issuance_pass.prepare_for_signing(); + + let credential_shares = Arc::new(tokio::sync::Mutex::new(Vec::new())); + + futures::stream::iter(api_clients) + .for_each_concurrent(None, |client| async { + // move the client into the block + let client = client; + let api_url = client.api_client.api_url(); + + info!("contacting {api_url} for blinded free pass"); + + match issuance_pass + .obtain_partial_freepass_credential( + &client.api_client, + &signing_account, + &client.verification_key, + signing_data.clone(), + ) + .await + { + Ok(partial_credential) => { + credential_shares + .lock() + .await + .push((partial_credential, client.node_id).into()); + } + Err(err) => { + error!("failed to obtain partial free pass from {api_url}: {err}") + } + } + }) + .await; + + // SAFETY: the futures have completed, so we MUST have the only arc reference + #[allow(clippy::unwrap_used)] + let credential_shares = Arc::into_inner(credential_shares).unwrap().into_inner(); + + if credential_shares.len() < threshold as usize { + bail!("we managed to obtain only {} partial credentials while the minimum threshold is {threshold}", credential_shares.len()); + } + + let signature = issuance_pass.aggregate_signature_shares(aggregate_vk, &credential_shares)?; + Ok(issuance_pass.into_issued_credential(signature, epoch_id)) +} + +pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> { + let address = client.address(); + + if !args.output_dir.is_dir() { + bail!("the provided output directory is not a directory!"); + } + + if args.output_dir.read_dir()?.next().is_some() { + bail!("the provided output directory is not empty!"); + } + + let Some(bandwidth_contract) = client.coconut_bandwidth_contract_address() else { + bail!("the bandwidth contract address is not set") + }; + + let Some(bandwidth_admin) = client + .get_contract(bandwidth_contract) + .await + .map(|c| c.contract_info.admin)? + else { + bail!("the bandwidth contract doesn't have any admin set") + }; + + // sanity checks since nym-apis will reject invalid requests anyway + if address != bandwidth_admin { + bail!("the provided mnemonic does not correspond to the current admin of the bandwidth contract") + } + + let expiration_date = match args.expiration_date { + Some(date) => date, + // SAFETY: one of those arguments must have been set + None => OffsetDateTime::from_unix_timestamp(args.expiration_timestamp.unwrap())?, + }; + + let now = OffsetDateTime::now_utc(); + + if expiration_date > now + MAX_FREE_PASS_VALIDITY { + bail!("the provided free pass request has too long expiry (expiry is set to on {expiration_date})") + } + + // issuance start + block_until_coconut_is_available(&client).await?; + + let signing_account = client.signing_account()?; + + let epoch_id = client.get_current_epoch().await?.epoch_id; + let threshold = client + .get_current_epoch_threshold() + .await? + .ok_or(anyhow!("no threshold available"))?; + let api_clients = all_coconut_api_clients(&client, epoch_id).await?; + + if api_clients.len() < threshold as usize { + bail!( + "we have only {} api clients available while the minimum threshold is {threshold}", + api_clients.len() + ) + } + let aggregate_vk = obtain_aggregate_verification_key(&api_clients)?; + + for i in 0..args.amount { + let human_index = i + 1; + info!("trying to obtain free pass {human_index}/{}", args.amount); + let free_pass = get_freepass( + api_clients.clone(), + &aggregate_vk, + threshold, + epoch_id, + &signing_account, + expiration_date, + ) + .await?; + let credential_data = Zeroizing::new(free_pass.pack_v1()); + let output = args.output_dir.join(format!("freepass_{i}.nym")); + info!("saving the freepass to '{}'", output.display()); + File::create(output)?.write_all(&credential_data)?; + } + + Ok(()) +} diff --git a/common/commands/src/coconut/mod.rs b/common/commands/src/coconut/mod.rs index 531a316125..c6499deed4 100644 --- a/common/commands/src/coconut/mod.rs +++ b/common/commands/src/coconut/mod.rs @@ -3,6 +3,7 @@ use clap::{Args, Subcommand}; +pub mod generate_freepass; pub mod issue_credentials; pub mod recover_credentials; @@ -15,6 +16,7 @@ pub struct Coconut { #[derive(Debug, Subcommand)] pub enum CoconutCommands { + GenerateFreepass(generate_freepass::Args), IssueCredentials(issue_credentials::Args), RecoverCredentials(recover_credentials::Args), } diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs index 0a1707ba3b..ed2b57000c 100644 --- a/common/credentials/src/coconut/bandwidth/freepass.rs +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -2,7 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::utils::scalar_serde_helper; -use nym_credentials_interface::{hash_to_scalar, Attribute, PublicAttribute}; +use crate::error::Error; +use nym_api_requests::coconut::FreePassRequest; +use nym_credentials_interface::{ + hash_to_scalar, Attribute, BlindedSignature, CredentialSigningData, PublicAttribute, +}; +use nym_validator_client::signing::AccountData; use serde::{Deserialize, Serialize}; use time::{Duration, OffsetDateTime, Time}; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -76,4 +81,54 @@ impl FreePassIssuanceData { pub fn expiry_date_plain(&self) -> String { self.expiry_date.unix_timestamp().to_string() } + + pub async fn obtain_free_pass_nonce( + &self, + client: &nym_validator_client::client::NymApiClient, + ) -> Result { + let server_response = client.free_pass_nonce().await?; + Ok(server_response.current_nonce) + } + + pub fn create_free_pass_request( + &self, + signing_request: &CredentialSigningData, + account_data: &AccountData, + issuer_nonce: u32, + ) -> Result { + let plaintext = issuer_nonce.to_be_bytes(); + let nonce_signature = account_data + .private_key() + .sign(&plaintext) + .map_err(|_| Error::Secp256k1SignFailure)?; + + Ok(FreePassRequest { + cosmos_pubkey: account_data.public_key(), + inner_sign_request: signing_request.blind_sign_request.clone(), + used_nonce: issuer_nonce, + nonce_signature, + public_attributes_plain: signing_request.public_attributes_plain.clone(), + }) + } + + pub async fn obtain_blinded_credential( + &self, + client: &nym_validator_client::client::NymApiClient, + request: &FreePassRequest, + ) -> Result { + let server_response = client.issue_free_pass_credential(request).await?; + Ok(server_response.blinded_signature) + } + + pub async fn request_blinded_credential( + &self, + signing_request: &CredentialSigningData, + account_data: &AccountData, + client: &nym_validator_client::client::NymApiClient, + ) -> Result { + let signing_nonce = self.obtain_free_pass_nonce(client).await?; + let request = + self.create_free_pass_request(signing_request, account_data, signing_nonce)?; + self.obtain_blinded_credential(client, &request).await + } } diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index bf166313a6..5441a899fc 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -11,11 +11,13 @@ use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; use bls12_381::G1Projective; use nym_credentials_interface::{ - aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, Parameters, - PrivateAttribute, PublicAttribute, Signature, SignatureShare, VerificationKey, + aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSignature, + Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, VerificationKey, }; use nym_crypto::asymmetric::{encryption, identity}; +use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::{Coin, Hash}; +use nym_validator_client::signing::AccountData; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -196,26 +198,12 @@ impl IssuanceBandwidthCredential { } } - pub async fn obtain_partial_credential( + pub fn unblind_signature( &self, - client: &nym_validator_client::client::NymApiClient, validator_vk: &VerificationKey, - signing_data: impl Into>, + signing_data: &CredentialSigningData, + blinded_signature: BlindedSignature, ) -> Result { - // if we provided signing data, do use them, otherwise generate fresh data - let signing_data = signing_data - .into() - .unwrap_or_else(|| self.prepare_for_signing()); - - let blinded_signature = match &self.variant_data { - BandwidthCredentialIssuanceDataVariant::FreePass(_freepass) => unimplemented!(), - BandwidthCredentialIssuanceDataVariant::Voucher(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? - } - }; - let public_attributes = self.get_public_attributes(); let private_attributes = self.get_private_attributes(); @@ -232,6 +220,52 @@ impl IssuanceBandwidthCredential { Ok(unblinded_signature) } + pub async fn obtain_partial_freepass_credential( + &self, + client: &nym_validator_client::client::NymApiClient, + account_data: &AccountData, + validator_vk: &VerificationKey, + signing_data: impl Into>, + ) -> Result { + // if we provided signing data, do use them, otherwise generate fresh data + let signing_data = signing_data + .into() + .unwrap_or_else(|| self.prepare_for_signing()); + + let blinded_signature = match &self.variant_data { + BandwidthCredentialIssuanceDataVariant::FreePass(freepass) => { + freepass + .request_blinded_credential(&signing_data, account_data, client) + .await? + } + _ => return Err(Error::NotAFreePass), + }; + self.unblind_signature(validator_vk, &signing_data, blinded_signature) + } + + // ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers + pub async fn obtain_partial_bandwidth_voucher_credential( + &self, + client: &nym_validator_client::client::NymApiClient, + validator_vk: &VerificationKey, + signing_data: impl Into>, + ) -> Result { + // if we provided signing data, do use them, otherwise generate fresh data + let signing_data = signing_data + .into() + .unwrap_or_else(|| self.prepare_for_signing()); + + let blinded_signature = match &self.variant_data { + BandwidthCredentialIssuanceDataVariant::Voucher(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) + } + pub fn aggregate_signature_shares( &self, verification_key: &VerificationKey, @@ -254,13 +288,15 @@ impl IssuanceBandwidthCredential { pub fn into_issued_credential( self, aggregate_signature: Signature, + epoch_id: EpochId, ) -> IssuedBandwidthCredential { - self.to_issued_credential(aggregate_signature) + self.to_issued_credential(aggregate_signature, epoch_id) } pub fn to_issued_credential( &self, aggregate_signature: Signature, + epoch_id: EpochId, ) -> IssuedBandwidthCredential { IssuedBandwidthCredential::new( self.serial_number, @@ -268,6 +304,7 @@ impl IssuanceBandwidthCredential { aggregate_signature, (&self.variant_data).into(), self.type_prehashed, + epoch_id, ) } diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index 62d5a4ecc7..753f5434d2 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -16,6 +16,7 @@ use nym_credentials_interface::{ }; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; +use nym_validator_client::nym_api::EpochId; pub const CURRENT_SERIALIZATION_REVISION: u8 = 1; @@ -91,6 +92,9 @@ pub struct IssuedBandwidthCredential { /// type of the bandwdith credential hashed onto a scalar #[serde(with = "scalar_serde_helper")] type_prehashed: PublicAttribute, + + /// Specifies the (DKG) epoch id when this credential has been issued + epoch_id: EpochId } impl IssuedBandwidthCredential { @@ -100,6 +104,7 @@ impl IssuedBandwidthCredential { signature: Signature, variant_data: BandwidthCredentialIssuedDataVariant, type_prehashed: PublicAttribute, + epoch_id: EpochId ) -> Self { IssuedBandwidthCredential { serial_number, @@ -107,6 +112,7 @@ impl IssuedBandwidthCredential { signature, variant_data, type_prehashed, + epoch_id, } } diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index cf0025826c..3192804530 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -9,7 +9,7 @@ use nym_credentials_interface::{ }; use nym_validator_client::client::CoconutApiClient; -pub async fn obtain_aggregate_verification_key( +pub fn obtain_aggregate_verification_key( api_clients: &[CoconutApiClient], ) -> Result { if api_clients.is_empty() { @@ -37,7 +37,7 @@ pub async fn obtain_aggregate_signature( return Err(Error::NoValidatorsAvailable); } let mut shares = Vec::with_capacity(coconut_api_clients.len()); - let verification_key = obtain_aggregate_verification_key(coconut_api_clients).await?; + let verification_key = obtain_aggregate_verification_key(coconut_api_clients)?; let request = voucher.prepare_for_signing(); @@ -48,7 +48,7 @@ pub async fn obtain_aggregate_signature( ); match voucher - .obtain_partial_credential( + .obtain_partial_bandwidth_voucher_credential( &coconut_api_client.api_client, &coconut_api_client.verification_key, Some(request.clone()), diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 76c4aa068c..620196b627 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -44,4 +44,13 @@ pub enum Error { #[error("Could not deserialize bandwidth voucher - {0}")] BandwidthVoucherDeserializationError(String), + + #[error("the provided issuance data wasn't prepared for a bandwidth voucher")] + NotABandwdithVoucher, + + #[error("the provided issuance data wasn't prepared for a free pass")] + NotAFreePass, + + #[error("failed to create a secp256k1 signature")] + Secp256k1SignFailure } diff --git a/common/nymcoconut/src/scheme/mod.rs b/common/nymcoconut/src/scheme/mod.rs index 68dd0d2b9a..58c1ca0f5e 100644 --- a/common/nymcoconut/src/scheme/mod.rs +++ b/common/nymcoconut/src/scheme/mod.rs @@ -248,6 +248,15 @@ pub struct SignatureShare { index: SignerIndex, } +impl From<(Signature, SignerIndex)> for SignatureShare { + fn from(value: (Signature, SignerIndex)) -> Self { + SignatureShare { + signature: value.0, + index: value.1, + } + } +} + impl SignatureShare { pub fn new(signature: Signature, index: SignerIndex) -> Self { SignatureShare { signature, index } diff --git a/gateway/gateway-requests/src/models.rs b/gateway/gateway-requests/src/models.rs index c16bf023cf..93407363bd 100644 --- a/gateway/gateway-requests/src/models.rs +++ b/gateway/gateway-requests/src/models.rs @@ -327,7 +327,7 @@ mod tests { ) .unwrap(); - let issued = issuance.into_issued_credential(sig); + let issued = issuance.into_issued_credential(sig, 42); let spending = issued .prepare_for_spending(keypair.verification_key()) .unwrap(); diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index 7515157b55..db4ec9ee6c 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -89,7 +89,7 @@ impl CoconutVerifier { }); } let aggregated_verification_key = - nym_credentials::obtain_aggregate_verification_key(&epoch_api_clients).await?; + nym_credentials::obtain_aggregate_verification_key(&epoch_api_clients)?; api_clients.insert(current_epoch.epoch_id, epoch_api_clients); master_keys.insert(current_epoch.epoch_id, aggregated_verification_key); @@ -155,7 +155,7 @@ impl CoconutVerifier { let api_clients = self.api_clients(epoch_id).await?; let aggregated_verification_key = - nym_credentials::obtain_aggregate_verification_key(&api_clients).await?; + nym_credentials::obtain_aggregate_verification_key(&api_clients)?; let mut guard = self.master_keys.write().await; guard.insert(epoch_id, aggregated_verification_key); diff --git a/nym-api/nym-api-requests/src/coconut/mod.rs b/nym-api/nym-api-requests/src/coconut/mod.rs index fb5740928c..1971f0b8f8 100644 --- a/nym-api/nym-api-requests/src/coconut/mod.rs +++ b/nym-api/nym-api-requests/src/coconut/mod.rs @@ -5,6 +5,6 @@ pub mod helpers; pub mod models; pub use models::{ - BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, + BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, FreePassRequest, VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse, }; diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index 0a3c3c0138..aae0bc9ea0 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -110,6 +110,11 @@ impl BlindSignRequestBody { } } +#[derive(Debug, Serialize, Deserialize)] +pub struct FreePassNonceResponse { + pub current_nonce: u32, +} + #[derive(Debug, Serialize, Deserialize)] pub struct BlindedSignatureResponse { pub blinded_signature: BlindedSignature, @@ -159,6 +164,22 @@ pub struct FreePassRequest { } impl FreePassRequest { + pub fn new( + cosmos_pubkey: cosmrs::crypto::PublicKey, + inner_sign_request: BlindSignRequest, + used_nonce: u32, + nonce_signature: cosmrs::crypto::secp256k1::Signature, + public_attributes_plain: Vec, + ) -> Self { + FreePassRequest { + cosmos_pubkey, + inner_sign_request, + used_nonce, + nonce_signature, + public_attributes_plain, + } + } + pub fn tendermint_pubkey(&self) -> tendermint::PublicKey { self.cosmos_pubkey.into() } diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 4a5bfe8f2a..df8300746e 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -8,8 +8,8 @@ use crate::coconut::state::State; use crate::coconut::storage::CoconutStorageExt; use k256::ecdsa::signature::Verifier; use nym_api_requests::coconut::models::{ - CredentialsRequestBody, EpochCredentialsResponse, FreePassRequest, IssuedCredentialResponse, - IssuedCredentialsResponse, + CredentialsRequestBody, EpochCredentialsResponse, FreePassNonceResponse, FreePassRequest, + IssuedCredentialResponse, IssuedCredentialsResponse, }; use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, @@ -68,6 +68,18 @@ fn validate_freepass_public_attributes(res: &FreePassRequest) -> Result<()> { Ok(()) } +#[get("/free-pass-nonce")] +pub async fn get_current_free_pass_nonce( + state: &RocketState, +) -> Result> { + debug!("Received free pass nonce request"); + + let current_nonce = state.storage.get_current_freepass_nonce().await?; + debug!("the current expected nonce is {current_nonce}"); + + Ok(Json(FreePassNonceResponse { current_nonce })) +} + #[post("/free-pass", data = "")] pub async fn post_free_pass( freepass_request_body: Json, diff --git a/nym-api/src/coconut/comm.rs b/nym-api/src/coconut/comm.rs index 2019d99650..48e7823fce 100644 --- a/nym-api/src/coconut/comm.rs +++ b/nym-api/src/coconut/comm.rs @@ -110,7 +110,7 @@ impl APICommunicationChannel for QueryCommunicationChannel { ClientInner::Signing(client) => all_coconut_api_clients(client, epoch_id).await?, }; - let vk = obtain_aggregate_verification_key(&coconut_api_clients).await?; + let vk = obtain_aggregate_verification_key(&coconut_api_clients)?; guard.insert(epoch_id, vk.clone()); diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index 82ca3c59b2..5047982304 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -52,6 +52,7 @@ where // this format! is so ugly... format!("/{NYM_API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}"), routes![ + api_routes::get_current_free_pass_nonce, api_routes::post_free_pass, api_routes::post_blind_sign, api_routes::verify_bandwidth_credential, diff --git a/tools/nym-cli/src/coconut/mod.rs b/tools/nym-cli/src/coconut/mod.rs index ea4fd45381..4df05bb5fd 100644 --- a/tools/nym-cli/src/coconut/mod.rs +++ b/tools/nym-cli/src/coconut/mod.rs @@ -7,6 +7,13 @@ pub(crate) async fn execute( network_details: &NymNetworkDetails, ) -> anyhow::Result<()> { match coconut.command { + nym_cli_commands::coconut::CoconutCommands::GenerateFreepass(args) => { + nym_cli_commands::coconut::generate_freepass::execute( + args, + create_signing_client(global_args, network_details)?, + ) + .await? + } nym_cli_commands::coconut::CoconutCommands::IssueCredentials(args) => { nym_cli_commands::coconut::issue_credentials::execute( args, From edbcade5f5c23ccd61f35c73d53aa3275ec31bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 12 Feb 2024 16:39:03 +0000 Subject: [PATCH 28/49] clippy --- common/commands/src/coconut/generate_freepass.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/commands/src/coconut/generate_freepass.rs b/common/commands/src/coconut/generate_freepass.rs index 7bcd42e858..98325ea710 100644 --- a/common/commands/src/coconut/generate_freepass.rs +++ b/common/commands/src/coconut/generate_freepass.rs @@ -75,7 +75,7 @@ async fn get_freepass( match issuance_pass .obtain_partial_freepass_credential( &client.api_client, - &signing_account, + signing_account, &client.verification_key, signing_data.clone(), ) From fa93c4598fb142da58af7a4ac415610cd434f070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 12 Feb 2024 16:56:55 +0000 Subject: [PATCH 29/49] removing redundant epoch_id field --- .../client-libs/gateway-client/src/client.rs | 5 +-- common/credentials-interface/src/lib.rs | 3 ++ .../src/coconut/bandwidth/issued.rs | 9 +++--- gateway/gateway-requests/src/models.rs | 31 +++++++------------ gateway/gateway-requests/src/types.rs | 9 +++--- .../connection_handler/authenticated.rs | 8 ++--- .../websocket/connection_handler/coconut.rs | 5 ++- .../nym-api-requests/src/coconut/models.rs | 7 ----- nym-api/src/coconut/api_routes/mod.rs | 2 +- 9 files changed, 32 insertions(+), 47 deletions(-) diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 87e9fa758a..2a9750e7a1 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -25,7 +25,6 @@ use nym_gateway_requests::{ use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; use nym_sphinx::forwarding::packet::MixPacket; use nym_task::TaskClient; -use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use rand::rngs::OsRng; use std::convert::TryFrom; @@ -527,14 +526,12 @@ impl GatewayClient { async fn claim_coconut_bandwidth( &mut self, credential: CredentialSpendingData, - epoch_id: EpochId, ) -> Result<(), GatewayClientError> { let mut rng = OsRng; let iv = IV::new_random(&mut rng); let msg = ClientControlRequest::new_enc_coconut_bandwidth_credential_v2( credential, - epoch_id, self.shared_key.as_ref().unwrap(), iv, ) @@ -587,7 +584,7 @@ impl GatewayClient { .prepare_bandwidth_credential() .await?; - self.claim_coconut_bandwidth(prepared_credential.data, prepared_credential.epoch_id) + self.claim_coconut_bandwidth(prepared_credential.data) .await?; self.bandwidth_controller .as_ref() diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index f2be1ef70f..7acc98499c 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -92,6 +92,9 @@ pub struct CredentialSpendingData { pub public_attributes_plain: Vec, pub typ: CredentialType, + + /// The (DKG) epoch id under which the credential has been issued so that the verifier could use correct verification key for validation. + pub epoch_id: u64, } impl CredentialSpendingData { diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index 753f5434d2..8354408533 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -14,9 +14,9 @@ use nym_credentials_interface::prove_bandwidth_credential; use nym_credentials_interface::{ Parameters, PrivateAttribute, PublicAttribute, Signature, VerificationKey, }; +use nym_validator_client::nym_api::EpochId; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; -use nym_validator_client::nym_api::EpochId; pub const CURRENT_SERIALIZATION_REVISION: u8 = 1; @@ -92,9 +92,9 @@ pub struct IssuedBandwidthCredential { /// type of the bandwdith credential hashed onto a scalar #[serde(with = "scalar_serde_helper")] type_prehashed: PublicAttribute, - + /// Specifies the (DKG) epoch id when this credential has been issued - epoch_id: EpochId + epoch_id: EpochId, } impl IssuedBandwidthCredential { @@ -104,7 +104,7 @@ impl IssuedBandwidthCredential { signature: Signature, variant_data: BandwidthCredentialIssuedDataVariant, type_prehashed: PublicAttribute, - epoch_id: EpochId + epoch_id: EpochId, ) -> Self { IssuedBandwidthCredential { serial_number, @@ -172,6 +172,7 @@ impl IssuedBandwidthCredential { verify_credential_request, public_attributes_plain: self.get_plain_public_attributes(), typ: self.typ(), + epoch_id: self.epoch_id, }) } } diff --git a/gateway/gateway-requests/src/models.rs b/gateway/gateway-requests/src/models.rs index 93407363bd..a250612508 100644 --- a/gateway/gateway-requests/src/models.rs +++ b/gateway/gateway-requests/src/models.rs @@ -22,7 +22,7 @@ pub struct OldV1Credential { } // attempt to convert the old request type into the new variant -impl TryFrom for CredentialSpendingWithEpoch { +impl TryFrom for CredentialSpendingRequest { type Error = GatewayRequestsError; fn try_from(value: OldV1Credential) -> Result { @@ -35,14 +35,14 @@ impl TryFrom for CredentialSpendingWithEpoch { let typ = value.voucher_info.parse()?; let public_attributes_plain = vec![value.voucher_value.to_string(), value.voucher_info]; - Ok(CredentialSpendingWithEpoch { + Ok(CredentialSpendingRequest { data: CredentialSpendingData { embedded_private_attributes, verify_credential_request: value.theta, public_attributes_plain, typ, + epoch_id: value.epoch_id, }, - epoch_id: value.epoch_id, }) } } @@ -106,13 +106,9 @@ impl OldV1Credential { } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct CredentialSpendingWithEpoch { +pub struct CredentialSpendingRequest { /// The cryptographic material required for spending the underlying credential. pub data: CredentialSpendingData, - - /// The (DKG) epoch id under which the credential has been issued so that the verifier - /// could use correct verification key for validation. - pub epoch_id: u64, } // just a helper macro for checking required length and advancing the buffer @@ -132,9 +128,9 @@ macro_rules! ensure_len_and_advance { }}; } -impl CredentialSpendingWithEpoch { - pub fn new(data: CredentialSpendingData, epoch_id: u64) -> Self { - CredentialSpendingWithEpoch { data, epoch_id } +impl CredentialSpendingRequest { + pub fn new(data: CredentialSpendingData) -> Self { + CredentialSpendingRequest { data } } pub fn matches_blinded_serial_number( @@ -183,7 +179,7 @@ impl CredentialSpendingWithEpoch { bytes.extend_from_slice(&typ_len); bytes.extend_from_slice(typ_bytes); - bytes.extend_from_slice(&self.epoch_id.to_be_bytes()); + bytes.extend_from_slice(&self.data.epoch_id.to_be_bytes()); bytes } @@ -227,14 +223,14 @@ impl CredentialSpendingWithEpoch { let epoch_id_bytes = ensure_len_and_advance!(b, 8); let epoch_id = u64::from_be_bytes(epoch_id_bytes.try_into().unwrap()); - Ok(CredentialSpendingWithEpoch { + Ok(CredentialSpendingRequest { data: CredentialSpendingData { embedded_private_attributes, verify_credential_request: theta, public_attributes_plain, typ, + epoch_id, }, - epoch_id, }) } } @@ -332,13 +328,10 @@ mod tests { .prepare_for_spending(keypair.verification_key()) .unwrap(); - let with_epoch = CredentialSpendingWithEpoch { - data: spending, - epoch_id: 42, - }; + let with_epoch = CredentialSpendingRequest { data: spending }; let bytes = with_epoch.to_bytes(); - let recovered = CredentialSpendingWithEpoch::try_from_bytes(&bytes).unwrap(); + let recovered = CredentialSpendingRequest::try_from_bytes(&bytes).unwrap(); assert_eq!(with_epoch, recovered); } diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 0165a40c6d..cc85d408c6 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -3,7 +3,7 @@ use crate::authentication::encrypted_address::EncryptedAddressBytes; use crate::iv::IV; -use crate::models::{CredentialSpendingWithEpoch, OldV1Credential}; +use crate::models::{CredentialSpendingRequest, OldV1Credential}; use crate::registration::handshake::SharedKeys; use crate::{GatewayMacSize, CURRENT_PROTOCOL_VERSION}; use log::error; @@ -190,11 +190,10 @@ impl ClientControlRequest { pub fn new_enc_coconut_bandwidth_credential_v2( credential: CredentialSpendingData, - epoch_id: u64, shared_key: &SharedKeys, iv: IV, ) -> Self { - let cred = CredentialSpendingWithEpoch::new(credential, epoch_id); + let cred = CredentialSpendingRequest::new(credential); let serialized_credential = cred.to_bytes(); let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); @@ -208,9 +207,9 @@ impl ClientControlRequest { enc_credential: Vec, shared_key: &SharedKeys, iv: IV, - ) -> Result { + ) -> Result { let credential_bytes = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; - CredentialSpendingWithEpoch::try_from_bytes(&credential_bytes) + CredentialSpendingRequest::try_from_bytes(&credential_bytes) .map_err(|_| GatewayRequestsError::MalformedEncryption) } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index 0f4bd0d7d6..de56f78413 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -22,7 +22,7 @@ use futures::{ use log::*; use nym_credentials::coconut::bandwidth::{bandwidth_credential_params, CredentialType}; use nym_credentials_interface::CoconutError; -use nym_gateway_requests::models::CredentialSpendingWithEpoch; +use nym_gateway_requests::models::CredentialSpendingRequest; use nym_gateway_requests::{ iv::{IVConversionError, IV}, types::{BinaryRequest, ServerResponse}, @@ -220,12 +220,12 @@ where async fn handle_bandwidth_request( &mut self, - credential: CredentialSpendingWithEpoch, + credential: CredentialSpendingRequest, ) -> Result { let aggregated_verification_key = self .inner .coconut_verifier - .verification_key(credential.epoch_id) + .verification_key(credential.data.epoch_id) .await?; if !credential.data.validate_type_attribute() { @@ -251,7 +251,7 @@ where let api_clients = self .inner .coconut_verifier - .api_clients(credential.epoch_id) + .api_clients(credential.data.epoch_id) .await?; self.inner diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index db4ec9ee6c..d013f8fda7 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -4,7 +4,7 @@ use super::authenticated::RequestHandlingError; use log::*; use nym_credentials_interface::VerificationKey; -use nym_gateway_requests::models::CredentialSpendingWithEpoch; +use nym_gateway_requests::models::CredentialSpendingRequest; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::{MultisigQueryClient, NymContractsProvider}; @@ -180,7 +180,7 @@ impl CoconutVerifier { pub async fn release_bandwidth_voucher_funds( &self, api_clients: &[CoconutApiClient], - credential: CredentialSpendingWithEpoch, + credential: CredentialSpendingRequest, ) -> Result<(), RequestHandlingError> { if !credential.data.typ.is_voucher() { unimplemented!() @@ -230,7 +230,6 @@ impl CoconutVerifier { let req = nym_api_requests::coconut::VerifyCredentialBody::new( credential.data, - credential.epoch_id, proposal_id, self.address.clone(), ); diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index aae0bc9ea0..aaa980cc5a 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -17,10 +17,6 @@ pub struct VerifyCredentialBody { /// The cryptographic material required for spending the underlying credential. pub credential_data: CredentialSpendingData, - /// The (DKG) epoch id under which the credential has been issued so that the verifier - /// could use correct verification key for validation. - pub epoch_id: u64, - /// Multisig proposal for releasing funds for the provided bandwidth credential pub proposal_id: u64, @@ -31,13 +27,11 @@ pub struct VerifyCredentialBody { impl VerifyCredentialBody { pub fn new( credential_data: CredentialSpendingData, - epoch_id: u64, proposal_id: u64, gateway_cosmos_addr: AccountId, ) -> VerifyCredentialBody { VerifyCredentialBody { credential_data, - epoch_id, proposal_id, gateway_cosmos_addr, } @@ -68,7 +62,6 @@ pub struct BlindSignRequestBody { /// Signature on the inner sign request and the tx hash pub signature: identity::Signature, - // public_attributes: Vec, pub public_attributes_plain: Vec, } diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index df8300746e..013f600897 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -239,8 +239,8 @@ pub async fn verify_bandwidth_credential( state: &RocketState, ) -> Result> { let proposal_id = verify_credential_body.proposal_id; - let epoch_id = verify_credential_body.epoch_id; let credential_data = &verify_credential_body.credential_data; + let epoch_id = credential_data.epoch_id; let theta = &credential_data.verify_credential_request; let voucher_value: u64 = if credential_data.typ.is_voucher() { From 5a4dfafe9fe57092a06879dcaa8c4c5110042838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 12 Feb 2024 17:00:11 +0000 Subject: [PATCH 30/49] cargo fmt --- common/credentials/src/error.rs | 4 ++-- nym-api/src/coconut/tests/mod.rs | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 620196b627..96d8bf4202 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -50,7 +50,7 @@ pub enum Error { #[error("the provided issuance data wasn't prepared for a free pass")] NotAFreePass, - + #[error("failed to create a secp256k1 signature")] - Secp256k1SignFailure + Secp256k1SignFailure, } diff --git a/nym-api/src/coconut/tests/mod.rs b/nym-api/src/coconut/tests/mod.rs index fe0ad3b165..9912916a0d 100644 --- a/nym-api/src/coconut/tests/mod.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -1781,6 +1781,7 @@ mod credential_tests { // generate all the credential requests let params = bandwidth_credential_params(); let key_pair = nym_coconut::keygen(params); + let epoch = 1; let voucher_amount = coin(1234, "unym"); let issuance = voucher_fixture(coin(1234, "unym"), None); @@ -1805,7 +1806,7 @@ mod credential_tests { ) .unwrap(); - let issued = issuance.into_issued_credential(sig); + let issued = issuance.into_issued_credential(sig, epoch); let spending = issued .prepare_for_spending(key_pair.verification_key()) .unwrap(); @@ -1818,7 +1819,7 @@ mod credential_tests { staged_key_pair .set(KeyPairWithEpoch { keys: key_pair, - issued_for_epoch: 1, + issued_for_epoch: epoch, }) .await; staged_key_pair.validate(); @@ -1838,16 +1839,11 @@ mod credential_tests { .await .expect("valid rocket instance"); - let epoch_id = 69; let proposal_id = 42; // The address is not used, so we can use a duplicate let gateway_cosmos_addr = validator_address.clone(); - let req = VerifyCredentialBody::new( - spending.clone(), - epoch_id, - proposal_id, - gateway_cosmos_addr.clone(), - ); + let req = + VerifyCredentialBody::new(spending.clone(), proposal_id, gateway_cosmos_addr.clone()); // Test endpoint with not proposal for the proposal id let response = client From 92d9cb7dab484c37cc302c89c41311ed9d68c8b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 26 Jan 2024 15:13:25 +0000 Subject: [PATCH 31/49] added database code for the serial number storage --- .../credential-utils/src/recovery_storage.rs | 2 +- common/credentials-interface/src/lib.rs | 6 +- .../src/coconut/bandwidth/issuance.rs | 15 ++--- common/nymcoconut/src/lib.rs | 1 + common/nymcoconut/src/scheme/double_use.rs | 49 +++++++++++--- common/nymcoconut/src/scheme/verification.rs | 41 +++++------- .../20240126120000_store_serial_numbers.sql | 11 +++ gateway/src/node/storage/bandwidth.rs | 52 +++++++++++++- gateway/src/node/storage/mod.rs | 67 +++++++++++++++++++ gateway/src/node/storage/models.rs | 14 +++- 10 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 gateway/migrations/20240126120000_store_serial_numbers.sql diff --git a/common/credential-utils/src/recovery_storage.rs b/common/credential-utils/src/recovery_storage.rs index f003b562cd..0344a3ad86 100644 --- a/common/credential-utils/src/recovery_storage.rs +++ b/common/credential-utils/src/recovery_storage.rs @@ -53,7 +53,7 @@ impl RecoveryStorage { pub fn voucher_filename(voucher: &IssuanceBandwidthCredential) -> String { let prefix = voucher.typ().to_string(); - let suffix = voucher.blinded_g1_serial_number_bs58(); + let suffix = voucher.blinded_serial_number_bs58(); format!("{prefix}-{suffix}.{DUMPED_VOUCHER_EXTENSION}") } diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index 7acc98499c..ed3a2ca03c 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -10,9 +10,9 @@ use thiserror::Error; pub use nym_coconut::{ aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, keygen, prepare_blind_sign, prove_bandwidth_credential, verify_credential, Attribute, Base58, - BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, - PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey, - VerifyCredentialRequest, + BlindSignRequest, BlindedSerialNumber, BlindedSignature, Bytable, CoconutError, KeyPair, + Parameters, PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, + VerificationKey, VerifyCredentialRequest, }; pub const VOUCHER_INFO_TYPE: &str = "BandwidthVoucher"; diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 5441a899fc..bc56bda0fd 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -9,10 +9,10 @@ use crate::coconut::bandwidth::{ }; use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; -use bls12_381::G1Projective; use nym_credentials_interface::{ - aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSignature, - Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, VerificationKey, + aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSerialNumber, + BlindedSignature, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, + VerificationKey, }; use nym_crypto::asymmetric::{encryption, identity}; use nym_validator_client::nym_api::EpochId; @@ -140,15 +140,14 @@ impl IssuanceBandwidthCredential { Self::new(FreePassIssuanceData::new(expiry_date)) } - pub fn blind_serial_number_in_g1subgroup(&self) -> G1Projective { - bandwidth_credential_params().gen1() * self.serial_number + pub fn blind_serial_number(&self) -> BlindedSerialNumber { + (bandwidth_credential_params().gen2() * self.serial_number).into() } - // NOT TO BE CONFUSED WITH BLINDED SERIAL NUMBER IN CREDENTIAL ITSELF - pub fn blinded_g1_serial_number_bs58(&self) -> String { + pub fn blinded_serial_number_bs58(&self) -> String { use nym_credentials_interface::Base58; - self.blind_serial_number_in_g1subgroup().to_bs58() + self.blind_serial_number().to_bs58() } pub fn typ(&self) -> CredentialType { diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs index 4ac8245c37..e2a3138b3a 100644 --- a/common/nymcoconut/src/lib.rs +++ b/common/nymcoconut/src/lib.rs @@ -24,6 +24,7 @@ pub use scheme::setup::Parameters; pub use scheme::verification::check_vk_pairing; pub use scheme::verification::prove_bandwidth_credential; pub use scheme::verification::verify_credential; +pub use scheme::verification::BlindedSerialNumber; pub use scheme::verification::VerifyCredentialRequest; pub use scheme::BlindedSignature; pub use scheme::Signature; diff --git a/common/nymcoconut/src/scheme/double_use.rs b/common/nymcoconut/src/scheme/double_use.rs index ce3cf58d67..c8f89820b4 100644 --- a/common/nymcoconut/src/scheme/double_use.rs +++ b/common/nymcoconut/src/scheme/double_use.rs @@ -1,17 +1,46 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::G2Projective; -use group::Curve; -use std::convert::TryFrom; -use std::convert::TryInto; - use crate::error::{CoconutError, Result}; use crate::traits::{Base58, Bytable}; use crate::utils::try_deserialize_g2_projective; +use bls12_381::{G2Affine, G2Projective}; +use group::Curve; +use std::convert::TryFrom; +use std::convert::TryInto; +use std::fmt::{Debug, Formatter}; +use std::ops::Deref; -pub struct BlindedSerialNumber { - pub(crate) inner: G2Projective, +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct BlindedSerialNumber(G2Projective); + +// use custom Debug implementation to show base58 encoding (rather than raw curve elements) +impl Debug for BlindedSerialNumber { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("BlindedSerialNumber") + .field(&self.to_bs58()) + .finish() + } +} + +impl From for BlindedSerialNumber { + fn from(value: G2Projective) -> Self { + BlindedSerialNumber(value) + } +} + +impl From for BlindedSerialNumber { + fn from(value: G2Affine) -> Self { + BlindedSerialNumber(value.into()) + } +} + +impl Deref for BlindedSerialNumber { + type Target = G2Projective; + + fn deref(&self) -> &Self::Target { + &self.0 + } } impl TryFrom<&[u8]> for BlindedSerialNumber { @@ -34,13 +63,13 @@ impl TryFrom<&[u8]> for BlindedSerialNumber { ), )?; - Ok(BlindedSerialNumber { inner }) + Ok(BlindedSerialNumber(inner)) } } impl Bytable for BlindedSerialNumber { fn to_byte_vec(&self) -> Vec { - self.inner.to_affine().to_compressed().to_vec() + self.0.to_affine().to_compressed().to_vec() } fn try_from_byte_slice(slice: &[u8]) -> Result { diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index cbf36fde2f..e9957a7c73 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -1,22 +1,21 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use core::ops::Neg; -use std::convert::TryFrom; -use std::convert::TryInto; - -use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar}; -use group::{Curve, Group}; - use crate::error::{CoconutError, Result}; use crate::proofs::ProofKappaZeta; -use crate::scheme::double_use::BlindedSerialNumber; use crate::scheme::setup::Parameters; use crate::scheme::Signature; use crate::scheme::VerificationKey; use crate::traits::{Base58, Bytable}; use crate::utils::try_deserialize_g2_projective; use crate::Attribute; +use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar}; +use core::ops::Neg; +use group::{Curve, Group}; +use std::convert::TryFrom; +use std::convert::TryInto; + +pub use crate::scheme::double_use::BlindedSerialNumber; // TODO NAMING: this whole thing // Theta @@ -25,7 +24,7 @@ pub struct VerifyCredentialRequest { // blinded_message (kappa) pub blinded_message: G2Projective, // blinded serial number (zeta) - pub blinded_serial_number: G2Projective, + pub blinded_serial_number: BlindedSerialNumber, // sigma pub credential: Signature, // pi_v @@ -53,15 +52,10 @@ impl TryFrom<&[u8]> for VerifyCredentialRequest { ), )?; - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::unwrap_used)] - let blinded_serial_number_bytes = bytes[96..192].try_into().unwrap(); - let blinded_serial_number = try_deserialize_g2_projective( - &blinded_serial_number_bytes, - CoconutError::Deserialization( - "failed to deserialize the blinded serial number (zeta)".to_string(), - ), - )?; + let blinded_serial_number_bytes = &bytes[96..192]; + let blinded_serial_number = + BlindedSerialNumber::try_from_byte_slice(blinded_serial_number_bytes)?; + let credential = Signature::try_from(&bytes[192..288])?; let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?; @@ -87,7 +81,7 @@ impl VerifyCredentialRequest { pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result { let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?; - let ret = self.blinded_serial_number.eq(&blinded_serial_number.inner); + let ret = self.blinded_serial_number.eq(&blinded_serial_number); Ok(ret) } @@ -112,10 +106,7 @@ impl VerifyCredentialRequest { } pub fn blinded_serial_number_bs58(&self) -> String { - let blinded_serial_nuumber = BlindedSerialNumber { - inner: self.blinded_serial_number, - }; - blinded_serial_nuumber.to_bs58() + self.blinded_serial_number.to_bs58() } } @@ -198,7 +189,7 @@ pub fn prove_bandwidth_credential( Ok(VerifyCredentialRequest { blinded_message, - blinded_serial_number, + blinded_serial_number: blinded_serial_number.into(), credential: signature_prime, pi_v, }) diff --git a/gateway/migrations/20240126120000_store_serial_numbers.sql b/gateway/migrations/20240126120000_store_serial_numbers.sql new file mode 100644 index 0000000000..b7e1384dad --- /dev/null +++ b/gateway/migrations/20240126120000_store_serial_numbers.sql @@ -0,0 +1,11 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE TABLE spent_credential +( + blinded_serial_number_bs58 TEXT NOT NULL PRIMARY KEY UNIQUE, + was_freepass BOOLEAN NOT NULL, + client_address_bs58 TEXT NOT NULL REFERENCES shared_keys (client_address_bs58) +); \ No newline at end of file diff --git a/gateway/src/node/storage/bandwidth.rs b/gateway/src/node/storage/bandwidth.rs index 536f9b69f0..3eb4dab057 100644 --- a/gateway/src/node/storage/bandwidth.rs +++ b/gateway/src/node/storage/bandwidth.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::storage::models::PersistedBandwidth; +use crate::node::storage::models::{PersistedBandwidth, SpentCredential}; #[derive(Clone)] pub(crate) struct BandwidthManager { @@ -103,4 +103,54 @@ impl BandwidthManager { .await?; Ok(()) } + + /// Mark received credential as spent and insert it into the storage. + /// + /// # Arguments + /// + /// * `blinded_serial_number_bs58`: the unique blinded serial number embedded in the credential + /// * `was_freepass`: indicates whether the spent credential was a freepass + /// * `client_address_bs58`: address of the client that spent the credential + pub(crate) async fn insert_spent_credential( + &self, + blinded_serial_number_bs58: &str, + was_freepass: bool, + client_address_bs58: &str, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO spent_credential + (blinded_serial_number_bs58, was_freepass, client_address_bs58) + VALUES (?, ?, ?) + "#, + blinded_serial_number_bs58, + was_freepass, + client_address_bs58 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + /// Retrieve the spent credential with the provided blinded serial number from the storage. + /// + /// # Arguments + /// + /// * `blinded_serial_number_bs58`: the unique blinded serial number embedded in the credential + pub(crate) async fn retrieve_spent_credential( + &self, + blinded_serial_number_bs58: &str, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + SpentCredential, + r#" + SELECT * FROM spent_credential + WHERE blinded_serial_number_bs58 = ? + LIMIT 1 + "#, + blinded_serial_number_bs58, + ) + .fetch_optional(&self.connection_pool) + .await + } } diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs index 77f87437ea..bc6bbb76f9 100644 --- a/gateway/src/node/storage/mod.rs +++ b/gateway/src/node/storage/mod.rs @@ -8,6 +8,7 @@ use crate::node::storage::models::{PersistedSharedKeys, StoredMessage}; use crate::node::storage::shared_keys::SharedKeysManager; use async_trait::async_trait; use log::{debug, error}; +use nym_credentials_interface::{Base58, BlindedSerialNumber}; use nym_gateway_requests::registration::handshake::SharedKeys; use nym_sphinx::DestinationAddressBytes; use sqlx::ConnectOptions; @@ -134,6 +135,29 @@ pub(crate) trait Storage: Send + Sync { client_address: DestinationAddressBytes, amount: i64, ) -> Result<(), StorageError>; + + /// Mark received credential as spent and insert it into the storage. + /// + /// # Arguments + /// + /// * `blinded_serial_number`: the unique blinded serial number embedded in the credential + /// * `client_address`: address of the client that spent the credential + async fn insert_spent_credential( + &self, + blinded_serial_number: BlindedSerialNumber, + was_freepass: bool, + client_address: DestinationAddressBytes, + ) -> Result<(), StorageError>; + + /// Check if the credential with the provided blinded serial number if already present in the storage. + /// + /// # Arguments + /// + /// * `blinded_serial_number`: the unique blinded serial number embedded in the credential + async fn contains_credential( + &self, + blinded_serial_number: &BlindedSerialNumber, + ) -> Result; } // note that clone here is fine as upon cloning the same underlying pool will be used @@ -304,6 +328,34 @@ impl Storage for PersistentStorage { .await?; Ok(()) } + + async fn insert_spent_credential( + &self, + blinded_serial_number: BlindedSerialNumber, + was_freepass: bool, + client_address: DestinationAddressBytes, + ) -> Result<(), StorageError> { + self.bandwidth_manager + .insert_spent_credential( + &blinded_serial_number.to_bs58(), + was_freepass, + &client_address.as_base58_string(), + ) + .await?; + Ok(()) + } + + async fn contains_credential( + &self, + blinded_serial_number: &BlindedSerialNumber, + ) -> Result { + let cred = self + .bandwidth_manager + .retrieve_spent_credential(&blinded_serial_number.to_bs58()) + .await?; + + Ok(cred.is_some()) + } } /// In-memory implementation of `Storage`. The intention is primarily in testing environments. @@ -393,4 +445,19 @@ impl Storage for InMemStorage { ) -> Result<(), StorageError> { todo!() } + + async fn insert_spent_credential( + &self, + blinded_serial_number: BlindedSerialNumber, + client_address: DestinationAddressBytes, + ) -> Result<(), StorageError> { + todo!() + } + + async fn contains_credential( + &self, + blinded_serial_number: &BlindedSerialNumber, + ) -> Result { + todo!() + } } diff --git a/gateway/src/node/storage/models.rs b/gateway/src/node/storage/models.rs index c98fb4c26b..2bef3b7265 100644 --- a/gateway/src/node/storage/models.rs +++ b/gateway/src/node/storage/models.rs @@ -1,6 +1,8 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use sqlx::FromRow; + pub(crate) struct PersistedSharedKeys { pub(crate) client_address_bs58: String, pub(crate) derived_aes128_ctr_blake3_hmac_keys_bs58: String, @@ -18,3 +20,13 @@ pub(crate) struct PersistedBandwidth { pub(crate) client_address_bs58: String, pub(crate) available: i64, } + +#[derive(Debug, Clone, FromRow)] +pub(crate) struct SpentCredential { + #[allow(dead_code)] + pub(crate) blinded_serial_number_bs58: String, + #[allow(dead_code)] + pub(crate) was_freepass: bool, + #[allow(dead_code)] + pub(crate) client_address_bs58: String, +} From dd97eb13a81920f10f45a61c20439c03c00eaf1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 13 Feb 2024 09:53:13 +0000 Subject: [PATCH 32/49] locally marking credentials as spent --- common/credentials-interface/src/lib.rs | 4 +++ common/nymcoconut/src/scheme/verification.rs | 4 +++ .../connection_handler/authenticated.rs | 35 +++++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index ed3a2ca03c..277dcbe5a5 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -129,4 +129,8 @@ impl CredentialSpendingData { // the first attribute is variant specific bandwidth encoding, the second one should be the type self.public_attributes_plain.first() } + + pub fn blinded_serial_number(&self) -> BlindedSerialNumber { + self.verify_credential_request.blinded_serial_number() + } } diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index e9957a7c73..652a02a04e 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -105,6 +105,10 @@ impl VerifyCredentialRequest { VerifyCredentialRequest::try_from(bytes) } + pub fn blinded_serial_number(&self) -> BlindedSerialNumber { + self.blinded_serial_number + } + pub fn blinded_serial_number_bs58(&self) -> String { self.blinded_serial_number.to_bs58() } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index de56f78413..1c22df93b7 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -57,6 +57,9 @@ pub(crate) enum RequestHandlingError { #[error("Provided bandwidth credential did not verify correctly on {0}")] InvalidBandwidthCredential(String), + #[error("the provided bandwidth credential has already been spent before at this gateway")] + BandwidthCredentialAlreadySpent, + #[error("This gateway is only accepting coconut credentials for bandwidth")] OnlyCoconutCredentials, @@ -222,6 +225,17 @@ where &mut self, credential: CredentialSpendingRequest, ) -> Result { + // check if the credential hasn't been spent before + let serial_number = credential.data.blinded_serial_number(); + let already_spent = self + .inner + .storage + .contains_credential(&serial_number) + .await?; + if already_spent { + return Err(RequestHandlingError::BandwidthCredentialAlreadySpent); + } + let aggregated_verification_key = self .inner .coconut_verifier @@ -239,6 +253,7 @@ where // this will extract token amounts out of bandwidth vouchers and validate expiry of free passes let bandwidth = Bandwidth::try_from_raw_value(bandwidth_attribute, credential.data.typ)?; + // locally verify the credential let params = bandwidth_credential_params(); if !credential.data.verify(params, &aggregated_verification_key) { return Err(RequestHandlingError::InvalidBandwidthCredential( @@ -246,7 +261,7 @@ where )); } - match credential.data.typ { + let was_freepass = match credential.data.typ { CredentialType::Voucher => { let api_clients = self .inner @@ -258,12 +273,28 @@ where .coconut_verifier .release_bandwidth_voucher_funds(&api_clients, credential) .await?; + false } CredentialType::FreePass => { // no need to do anything special here, we already extracted the bandwidth amount and checked expiry info!("received a free pass credential"); + + true } - } + }; + + // technically this is not atomic, i.e. checking for the spending and then marking as spent, + // but because we have the `UNIQUE` constraint on the database table + // if somebody attempts to spend the same credential in another, parallel request, + // one of them will fail + // + // mark the credential as spent + // TODO: technically this should be done under a storage transaction so that if we experience any + // failures later on, it'd get reverted + self.inner + .storage + .insert_spent_credential(serial_number, was_freepass, self.client.address) + .await?; self.increase_bandwidth(bandwidth).await?; let available_total = self.get_available_bandwidth().await?; From f348e6972aa9273b1d1c44d0b0a4d36a69facd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 13 Feb 2024 10:35:24 +0000 Subject: [PATCH 33/49] clippy --- gateway/src/node/storage/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs index bc6bbb76f9..5349686a86 100644 --- a/gateway/src/node/storage/mod.rs +++ b/gateway/src/node/storage/mod.rs @@ -448,15 +448,16 @@ impl Storage for InMemStorage { async fn insert_spent_credential( &self, - blinded_serial_number: BlindedSerialNumber, - client_address: DestinationAddressBytes, + _blinded_serial_number: BlindedSerialNumber, + _was_freepass: bool, + _client_address: DestinationAddressBytes, ) -> Result<(), StorageError> { todo!() } async fn contains_credential( &self, - blinded_serial_number: &BlindedSerialNumber, + _blinded_serial_number: &BlindedSerialNumber, ) -> Result { todo!() } From 688ac2efb5c06eb918af1b7b9606139e8c90affd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 10:12:40 +0000 Subject: [PATCH 34/49] added nym-cli command for importing credentials --- .../commands/src/coconut/generate_freepass.rs | 4 +- .../commands/src/coconut/import_credential.rs | 106 ++++++++++++++++++ common/commands/src/coconut/mod.rs | 2 + .../src/coconut/bandwidth/freepass.rs | 4 + .../src/coconut/bandwidth/issued.rs | 8 ++ .../src/coconut/bandwidth/voucher.rs | 4 + 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 common/commands/src/coconut/import_credential.rs diff --git a/common/commands/src/coconut/generate_freepass.rs b/common/commands/src/coconut/generate_freepass.rs index 98325ea710..b10dd2bbc4 100644 --- a/common/commands/src/coconut/generate_freepass.rs +++ b/common/commands/src/coconut/generate_freepass.rs @@ -35,10 +35,12 @@ fn parse_rfc3339_expiration_date(raw: &str) -> Result, + /// The expiration of the free pass(es) expresses as unix timestamp. + /// Can't be set to more than a week into the future. #[clap(long, group = "expiration")] pub(crate) expiration_timestamp: Option, diff --git a/common/commands/src/coconut/import_credential.rs b/common/commands/src/coconut/import_credential.rs new file mode 100644 index 0000000000..3d90e02c42 --- /dev/null +++ b/common/commands/src/coconut/import_credential.rs @@ -0,0 +1,106 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::utils::CommonConfigsWrapper; +use anyhow::bail; +use clap::ArgGroup; +use clap::Parser; +use log::{error, info}; +use nym_credential_storage::initialise_persistent_storage; +use nym_credential_storage::models::StorableIssuedCredential; +use nym_credential_storage::storage::Storage; +use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant; +use nym_credentials::IssuedBandwidthCredential; +use std::fs; +use std::path::PathBuf; +use time::OffsetDateTime; +use zeroize::Zeroizing; + +fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { + bs58::decode(raw).into_vec() +} + +#[derive(Debug, Parser)] +#[clap(group(ArgGroup::new("cred_data").required(true)))] +pub struct Args { + /// Config file of the client that is supposed to use the credential. + #[clap(long)] + pub(crate) client_config: PathBuf, + + /// Explicitly provide the encoded credential data (as base58) + #[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)] + pub(crate) credential_data: Option>, + + /// Specifies the path to file containing binary credential data + #[clap(long, group = "cred_data")] + pub(crate) credential_path: Option, + + // currently hidden as there exists only a single serialization standard + #[clap(long, hide = true, default_value_t = 1)] + pub(crate) version: u8, +} + +pub async fn execute(args: Args) -> anyhow::Result<()> { + let loaded = CommonConfigsWrapper::try_load(args.client_config)?; + + 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") + }; + + println!( + "using credentials store at '{}'", + credentials_store.display() + ); + + let raw_credential = match args.credential_data { + Some(data) => data, + None => { + // SAFETY: one of those arguments must have been set + fs::read(args.credential_path.unwrap())? + } + }; + let raw_credential = Zeroizing::new(raw_credential); + + // we're unpacking the data in order to make sure it's valid + // and to extract relevant metadata for storage purposes + let credential = match args.version { + 1 => Zeroizing::new(IssuedBandwidthCredential::unpack_v1(&raw_credential)?), + other => panic!("unknown credential serialization version {other}"), + }; + let persistent_storage = initialise_persistent_storage(credentials_store).await; + + info!("importing {}", credential.typ()); + match credential.variant_data() { + BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => { + info!("with value of {}", voucher_info.value()) + } + BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { + info!("with expiry at {}", freepass_info.expiry_date()); + if freepass_info.expiry_date() > OffsetDateTime::now_utc() { + error!("the free pass has already expired!"); + + // technically we can, but the gateway will just reject it so what's the point + bail!("can't import an expired free pass") + } + } + } + + let storable = StorableIssuedCredential { + serialization_revision: args.version, + credential_data: &raw_credential, + credential_type: credential.typ().to_string(), + epoch_id: credential + .epoch_id() + .try_into() + .expect("our epoch is has run over u32::MAX!"), + }; + + persistent_storage + .insert_issued_credential(storable) + .await?; + Ok(()) +} diff --git a/common/commands/src/coconut/mod.rs b/common/commands/src/coconut/mod.rs index c6499deed4..700c7d521f 100644 --- a/common/commands/src/coconut/mod.rs +++ b/common/commands/src/coconut/mod.rs @@ -4,6 +4,7 @@ use clap::{Args, Subcommand}; pub mod generate_freepass; +pub mod import_credential; pub mod issue_credentials; pub mod recover_credentials; @@ -19,4 +20,5 @@ pub enum CoconutCommands { GenerateFreepass(generate_freepass::Args), IssueCredentials(issue_credentials::Args), RecoverCredentials(recover_credentials::Args), + ImportCredential(import_credential::Args), } diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs index ed2b57000c..1321e59f1d 100644 --- a/common/credentials/src/coconut/bandwidth/freepass.rs +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -30,6 +30,10 @@ impl<'a> From<&'a FreePassIssuanceData> for FreePassIssuedData { } impl FreePassIssuedData { + pub fn expiry_date(&self) -> OffsetDateTime { + self.expiry_date + } + pub fn expiry_date_plain(&self) -> String { self.expiry_date.unix_timestamp().to_string() } diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index 8354408533..b9d3ee02b3 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -116,6 +116,14 @@ impl IssuedBandwidthCredential { } } + pub fn epoch_id(&self) -> EpochId { + self.epoch_id + } + + pub fn variant_data(&self) -> &BandwidthCredentialIssuedDataVariant { + &self.variant_data + } + pub fn current_serialization_revision(&self) -> u8 { CURRENT_SERIALIZATION_REVISION } diff --git a/common/credentials/src/coconut/bandwidth/voucher.rs b/common/credentials/src/coconut/bandwidth/voucher.rs index 06866d9606..f61f5c3544 100644 --- a/common/credentials/src/coconut/bandwidth/voucher.rs +++ b/common/credentials/src/coconut/bandwidth/voucher.rs @@ -30,6 +30,10 @@ impl<'a> From<&'a BandwidthVoucherIssuanceData> for BandwidthVoucherIssuedData { } impl BandwidthVoucherIssuedData { + pub fn value(&self) -> &Coin { + &self.value + } + pub fn value_plain(&self) -> String { self.value.amount.to_string() } From 7bbac26676d486bd34f5fdac638db5875d380a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 10:26:10 +0000 Subject: [PATCH 35/49] replaced usage of lazy_static to oncelock for build information --- clients/native/Cargo.toml | 1 - clients/native/src/commands/mod.rs | 11 ++++------- clients/socks5/Cargo.toml | 1 - clients/socks5/src/commands/mod.rs | 10 +++------- explorer-api/Cargo.toml | 1 - explorer-api/src/commands/mod.rs | 10 +++------- gateway/Cargo.toml | 1 - gateway/src/main.rs | 10 +++------- mixnode/Cargo.toml | 1 - mixnode/src/main.rs | 10 +++------- nym-api/Cargo.toml | 1 - sdk/lib/socks5-listener/Cargo.toml | 1 - sdk/lib/socks5-listener/src/lib.rs | 1 - service-providers/ip-packet-router/Cargo.toml | 1 - service-providers/ip-packet-router/src/cli/mod.rs | 9 +++------ service-providers/network-requester/Cargo.toml | 1 - service-providers/network-requester/src/cli/mod.rs | 9 +++------ 17 files changed, 22 insertions(+), 57 deletions(-) diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 076b7128c8..59e8d212c0 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -23,7 +23,6 @@ url = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } dirs = "4.0" -lazy_static = "1.4.0" log = { workspace = true } # self explanatory pretty_env_logger = "0.4" # for formatting log messages rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 3ebe929b16..c49b197f62 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -8,7 +8,6 @@ use crate::client::config::{BaseClientConfig, Config}; use crate::error::ClientError; use clap::CommandFactory; use clap::{Parser, Subcommand}; -use lazy_static::lazy_static; use log::{error, info}; use nym_bin_common::bin_info; use nym_bin_common::completions::{fig_generate, ArgShell}; @@ -21,18 +20,16 @@ use nym_client_core::error::ClientCoreError; use nym_config::OptionalSet; use std::error::Error; use std::net::IpAddr; +use std::sync::OnceLock; pub(crate) mod build_info; +pub(crate) mod import_credential; pub(crate) mod init; pub(crate) mod run; -lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} - -// Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } #[derive(Parser)] diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 89edffb4fc..00a1932209 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -9,7 +9,6 @@ license.workspace = true [dependencies] clap = { workspace = true, features = ["cargo", "derive"] } -lazy_static = "1.4.0" log = { workspace = true } pretty_env_logger = "0.4" serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 7bd5205791..3b2ebd25fa 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -9,7 +9,6 @@ use crate::config::{BaseClientConfig, Config, SocksClientPaths}; use crate::error::Socks5ClientError; use clap::CommandFactory; use clap::{Parser, Subcommand}; -use lazy_static::lazy_static; use log::{error, info}; use nym_bin_common::bin_info; use nym_bin_common::completions::{fig_generate, ArgShell}; @@ -24,18 +23,15 @@ use nym_config::OptionalSet; use nym_sphinx::params::{PacketSize, PacketType}; use std::error::Error; use std::net::IpAddr; +use std::sync::OnceLock; pub(crate) mod build_info; pub mod init; pub(crate) mod run; -lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} - -// Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } #[derive(Parser)] diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index f4d132d92a..eaf3876402 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -13,7 +13,6 @@ dotenvy = { workspace = true } humantime-serde = "1.0" isocountry = "0.3.2" itertools = "0.10.3" -lazy_static = "1.4.0" log = { workspace = true } maxminddb = "0.23.0" okapi = { version = "0.7.0", features = ["impl_json_schema"] } diff --git a/explorer-api/src/commands/mod.rs b/explorer-api/src/commands/mod.rs index 1e19d2efce..0c0855d065 100644 --- a/explorer-api/src/commands/mod.rs +++ b/explorer-api/src/commands/mod.rs @@ -2,16 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use clap::Parser; -use lazy_static::lazy_static; use nym_bin_common::bin_info; +use std::sync::OnceLock; -lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} - -// Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } #[derive(Parser)] diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 51f0307f76..ac7aefb4a4 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -29,7 +29,6 @@ dotenvy = { workspace = true } futures = { workspace = true } humantime-serde = "1.0.1" ipnetwork = "0.16" -lazy_static = "1.4.0" log = { workspace = true } once_cell = "1.7.2" pretty_env_logger = "0.4" diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 0ec4b7865a..f88398e0b9 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -6,13 +6,13 @@ use clap::{crate_name, crate_version, Parser}; use colored::Colorize; -use lazy_static::lazy_static; use log::error; use nym_bin_common::bin_info; use nym_bin_common::logging::{maybe_print_banner, setup_logging}; use nym_bin_common::output_format::OutputFormat; use nym_network_defaults::setup_env; use std::error::Error; +use std::sync::OnceLock; mod commands; mod config; @@ -21,13 +21,9 @@ mod http; mod node; pub(crate) mod support; -lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} - -// Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } #[derive(Parser)] diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 9ad7c5081f..a15308cb4a 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -26,7 +26,6 @@ cupid = "0.6.1" dirs = "4.0" futures = { workspace = true } humantime-serde = "1.0" -lazy_static = "1.4.0" log = { workspace = true } rand = "0.7.3" serde = { workspace = true, features = ["derive"] } diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index d6383477cc..a7b1592776 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -3,9 +3,9 @@ use ::nym_config::defaults::setup_env; use clap::{crate_name, crate_version, Parser}; -use lazy_static::lazy_static; use log::info; use nym_bin_common::bin_info; +use std::sync::OnceLock; #[allow(unused_imports)] use nym_bin_common::logging::{maybe_print_banner, setup_logging}; @@ -21,13 +21,9 @@ mod config; pub(crate) mod error; mod node; -lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} - -// Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } #[derive(Parser)] diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 536f7915a8..5a5f31d778 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -27,7 +27,6 @@ futures = { workspace = true } itertools = "0.12.0" humantime-serde = "1.0" k256 = { version = "*", features = ["ecdsa-core"] } # needed for the Verifier trait; pull whatever version is used by other dependencies -lazy_static = "1.4.0" log = { workspace = true } pin-project = "1.0" rand = "0.8.5" diff --git a/sdk/lib/socks5-listener/Cargo.toml b/sdk/lib/socks5-listener/Cargo.toml index 425154a5cb..129afb5e87 100644 --- a/sdk/lib/socks5-listener/Cargo.toml +++ b/sdk/lib/socks5-listener/Cargo.toml @@ -16,7 +16,6 @@ crate-type = ["cdylib", "staticlib", "rlib"] [dependencies] anyhow = { workspace = true } futures = { workspace = true } -lazy_static = "1.4.0" nym-bin-common = { path = "../../../common/bin-common"} nym-client-core = { path = "../../../common/client-core", default-features = false } nym-config-common = { path = "../../../common/config", package = "nym-config" } diff --git a/sdk/lib/socks5-listener/src/lib.rs b/sdk/lib/socks5-listener/src/lib.rs index 0c9e2f7771..352638f84d 100644 --- a/sdk/lib/socks5-listener/src/lib.rs +++ b/sdk/lib/socks5-listener/src/lib.rs @@ -5,7 +5,6 @@ use crate::config::{config_filepath_from_root, Config}; use crate::persistence::MobileClientStorage; use ::safer_ffi::prelude::*; use anyhow::{anyhow, Result}; -use lazy_static::lazy_static; use log::{debug, info, warn}; use nym_bin_common::logging::setup_logging; use nym_client_core::init::helpers::current_gateways; diff --git a/service-providers/ip-packet-router/Cargo.toml b/service-providers/ip-packet-router/Cargo.toml index 39f3e33e48..ce6674550d 100644 --- a/service-providers/ip-packet-router/Cargo.toml +++ b/service-providers/ip-packet-router/Cargo.toml @@ -15,7 +15,6 @@ bytes = "1.5.0" clap.workspace = true etherparse = "0.13.0" futures = { workspace = true } -lazy_static.workspace = true log = { workspace = true } nym-bin-common = { path = "../../common/bin-common" } nym-client-core = { path = "../../common/client-core" } diff --git a/service-providers/ip-packet-router/src/cli/mod.rs b/service-providers/ip-packet-router/src/cli/mod.rs index d17391eaed..eda51a5554 100644 --- a/service-providers/ip-packet-router/src/cli/mod.rs +++ b/service-providers/ip-packet-router/src/cli/mod.rs @@ -8,6 +8,7 @@ use nym_client_core::client::base_client::storage::gateway_details::{ use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::config::GatewayEndpointConfig; use nym_client_core::error::ClientCoreError; +use std::sync::OnceLock; use crate::config::{BaseClientConfig, Config}; use crate::error::IpPacketRouterError; @@ -17,13 +18,9 @@ mod init; mod run; mod sign; -lazy_static::lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} - -// Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } #[derive(Parser)] diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 7a2805cd17..323a11c286 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -24,7 +24,6 @@ dirs = "4.0" futures = { workspace = true } humantime-serde = "1.1.1" ipnetwork = "0.20.0" -lazy_static = { workspace = true } log = { workspace = true } pretty_env_logger = "0.4.0" publicsuffix = "2.2.3" diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index 2dba60cb16..874f191134 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -20,19 +20,16 @@ use nym_client_core::client::key_manager::persistence::OnDiskKeys; use nym_client_core::config::GatewayEndpointConfig; use nym_client_core::error::ClientCoreError; use nym_config::OptionalSet; +use std::sync::OnceLock; mod build_info; mod init; mod run; mod sign; -lazy_static::lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} - -// Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } #[derive(Parser)] From e7d0c1812ae6c2ddd4260597a117eef2e36952d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 10:50:08 +0000 Subject: [PATCH 36/49] added import commands for client binaries --- Cargo.toml | 1 + clients/native/Cargo.toml | 4 +- .../native/src/commands/import_credential.rs | 102 ++++++++++++++++++ clients/native/src/commands/mod.rs | 4 + clients/native/src/error.rs | 22 +++- clients/socks5/Cargo.toml | 4 +- .../socks5/src/commands/import_credential.rs | 102 ++++++++++++++++++ clients/socks5/src/commands/mod.rs | 5 + clients/socks5/src/error.rs | 22 +++- common/commands/Cargo.toml | 2 +- .../contracts-common/Cargo.toml | 2 +- common/crypto/Cargo.toml | 2 +- common/dkg/Cargo.toml | 2 +- common/nymcoconut/Cargo.toml | 2 +- common/nymsphinx/anonymous-replies/Cargo.toml | 2 +- common/topology/Cargo.toml | 2 +- contracts/Cargo.lock | 53 +++++---- contracts/Cargo.toml | 2 + contracts/mixnet/Cargo.toml | 4 +- contracts/name-service/Cargo.toml | 2 +- .../service-provider-directory/Cargo.toml | 2 +- gateway/Cargo.toml | 2 +- gateway/gateway-requests/Cargo.toml | 2 +- mixnode/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-api/nym-api-requests/Cargo.toml | 2 +- nym-connect/desktop/Cargo.lock | 17 +-- nym-wallet/Cargo.lock | 11 +- service-providers/ip-packet-router/Cargo.toml | 2 +- .../network-requester/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- tools/nym-cli/src/coconut/mod.rs | 3 + 32 files changed, 332 insertions(+), 58 deletions(-) create mode 100644 clients/native/src/commands/import_credential.rs create mode 100644 clients/socks5/src/commands/import_credential.rs diff --git a/Cargo.toml b/Cargo.toml index c92723a54c..6e78dea995 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,6 +143,7 @@ anyhow = "1.0.71" async-trait = "0.1.68" axum = "0.6.20" base64 = "0.21.4" +bs58 = "0.5.0" bip39 = { version = "2.0.0", features = ["zeroize"] } clap = "4.4.7" cfg-if = "1.0.0" diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 59e8d212c0..465ba5ea1d 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -21,17 +21,19 @@ futures = { workspace = true } # bunch of futures stuff, however, now that I thi # and the single instance of abortable we have should really be refactored anyway url = { workspace = true } +bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } dirs = "4.0" log = { workspace = true } # self explanatory -pretty_env_logger = "0.4" # for formatting log messages rand = { version = "0.7.3", features = ["wasm-bindgen"] } # rng-related traits + some rng implementation to use serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization serde_json = { workspace = true } thiserror = { workspace = true } tap = "1.0.1" +time = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } # async runtime tokio-tungstenite = { workspace = true } +zeroize = { workspace = true } ## internal nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } diff --git a/clients/native/src/commands/import_credential.rs b/clients/native/src/commands/import_credential.rs new file mode 100644 index 0000000000..9d156d32ff --- /dev/null +++ b/clients/native/src/commands/import_credential.rs @@ -0,0 +1,102 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::commands::try_load_current_config; +use crate::error::ClientError; +use clap::ArgGroup; +use log::{error, info}; +use nym_credential_storage::models::StorableIssuedCredential; +use nym_credential_storage::storage::Storage; +use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant; +use nym_credentials::IssuedBandwidthCredential; +use std::fs; +use std::path::PathBuf; +use time::OffsetDateTime; +use zeroize::Zeroizing; + +fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { + bs58::decode(raw).into_vec() +} + +#[derive(clap::Args)] +#[clap(group(ArgGroup::new("cred_data").required(true)))] +pub(crate) struct Args { + /// Id of client that is going to import the credential + #[clap(long)] + pub id: String, + + /// Explicitly provide the encoded credential data (as base58) + #[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)] + pub(crate) credential_data: Option>, + + /// Specifies the path to file containing binary credential data + #[clap(long, group = "cred_data")] + pub(crate) credential_path: Option, + + // currently hidden as there exists only a single serialization standard + #[clap(long, hide = true, default_value_t = 1)] + pub(crate) version: u8, +} + +pub(crate) async fn execute(args: Args) -> Result<(), ClientError> { + let config = try_load_current_config(&args.id)?; + + let credentials_store = nym_credential_storage::initialise_persistent_storage( + &config.storage_paths.common_paths.credentials_database, + ) + .await; + + let raw_credential = match args.credential_data { + Some(data) => data, + None => { + // SAFETY: one of those arguments must have been set + fs::read(args.credential_path.unwrap())? + } + }; + let raw_credential = Zeroizing::new(raw_credential); + + // we're unpacking the data in order to make sure it's valid + // and to extract relevant metadata for storage purposes + let credential = match args.version { + 1 => Zeroizing::new( + IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| { + ClientError::CredentialDeserializationFailure { + storage_revision: 1, + source, + } + })?, + ), + other => panic!("unknown credential serialization version {other}"), + }; + + info!("importing {}", credential.typ()); + match credential.variant_data() { + BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => { + info!("with value of {}", voucher_info.value()) + } + BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { + info!("with expiry at {}", freepass_info.expiry_date()); + if freepass_info.expiry_date() > OffsetDateTime::now_utc() { + error!("the free pass has already expired!"); + + // technically we can import it, but the gateway will just reject it so what's the point + return Err(ClientError::ExpiredCredentialImport { + expiration: freepass_info.expiry_date(), + }); + } + } + } + + let storable = StorableIssuedCredential { + serialization_revision: args.version, + credential_data: &raw_credential, + credential_type: credential.typ().to_string(), + epoch_id: credential + .epoch_id() + .try_into() + .expect("our epoch is has run over u32::MAX!"), + }; + + credentials_store.insert_issued_credential(storable).await?; + Ok(()) +} diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index c49b197f62..ff1b8087fd 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -55,6 +55,9 @@ pub(crate) enum Commands { /// Run the Nym client with provided configuration client optionally overriding set parameters Run(run::Run), + /// Import a pre-generated credential + ImportCredential(import_credential::Args), + /// Show build information of this binary BuildInfo(build_info::BuildInfo), @@ -83,6 +86,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box init::execute(m).await?, Commands::Run(m) => run::execute(m).await?, + Commands::ImportCredential(m) => import_credential::execute(m).await?, Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), diff --git a/clients/native/src/error.rs b/clients/native/src/error.rs index 59d022999a..8a21803736 100644 --- a/clients/native/src/error.rs +++ b/clients/native/src/error.rs @@ -1,11 +1,13 @@ use nym_client_core::error::ClientCoreError; +use nym_credential_storage::error::StorageError; +use time::OffsetDateTime; #[derive(thiserror::Error, Debug)] pub enum ClientError { #[error("I/O error: {0}")] IoError(#[from] std::io::Error), - #[error("client-core error: {0}")] + #[error(transparent)] ClientCoreError(#[from] ClientCoreError), #[error("Failed to load config for: {0}")] @@ -20,4 +22,22 @@ pub enum ClientError { #[error("Attempted to start the client in invalid socket mode")] InvalidSocketMode, + + #[error("failed to store credential: {source}")] + CredentialStorageFailure { + #[from] + source: StorageError, + }, + + #[error( + "failed to deserialize provided credential using revision {storage_revision}: {source}" + )] + CredentialDeserializationFailure { + storage_revision: u8, + #[source] + source: nym_credentials::error::Error, + }, + + #[error("attempted to import an expired credential (it expired on {expiration})")] + ExpiredCredentialImport { expiration: OffsetDateTime }, } diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 00a1932209..565b6c1c72 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -8,16 +8,18 @@ rust-version = "1.56" license.workspace = true [dependencies] +bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } log = { workspace = true } -pretty_env_logger = "0.4" serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization serde_json = { workspace = true } tap = "1.0.1" thiserror = { workspace = true } tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] } rand = "0.7.3" +time = { workspace = true } url = { workspace = true } +zeroize = { workspace = true } # internal nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] } diff --git a/clients/socks5/src/commands/import_credential.rs b/clients/socks5/src/commands/import_credential.rs new file mode 100644 index 0000000000..b4c050b815 --- /dev/null +++ b/clients/socks5/src/commands/import_credential.rs @@ -0,0 +1,102 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::commands::try_load_current_config; +use crate::error::Socks5ClientError; +use clap::ArgGroup; +use log::{error, info}; +use nym_credential_storage::models::StorableIssuedCredential; +use nym_credential_storage::storage::Storage; +use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant; +use nym_credentials::IssuedBandwidthCredential; +use std::fs; +use std::path::PathBuf; +use time::OffsetDateTime; +use zeroize::Zeroizing; + +fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { + bs58::decode(raw).into_vec() +} + +#[derive(clap::Args)] +#[clap(group(ArgGroup::new("cred_data").required(true)))] +pub(crate) struct Args { + /// Id of client that is going to import the credential + #[clap(long)] + pub id: String, + + /// Explicitly provide the encoded credential data (as base58) + #[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)] + pub(crate) credential_data: Option>, + + /// Specifies the path to file containing binary credential data + #[clap(long, group = "cred_data")] + pub(crate) credential_path: Option, + + // currently hidden as there exists only a single serialization standard + #[clap(long, hide = true, default_value_t = 1)] + pub(crate) version: u8, +} + +pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> { + let config = try_load_current_config(&args.id)?; + + let credentials_store = nym_credential_storage::initialise_persistent_storage( + &config.storage_paths.common_paths.credentials_database, + ) + .await; + + let raw_credential = match args.credential_data { + Some(data) => data, + None => { + // SAFETY: one of those arguments must have been set + fs::read(args.credential_path.unwrap())? + } + }; + let raw_credential = Zeroizing::new(raw_credential); + + // we're unpacking the data in order to make sure it's valid + // and to extract relevant metadata for storage purposes + let credential = match args.version { + 1 => Zeroizing::new( + IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| { + Socks5ClientError::CredentialDeserializationFailure { + storage_revision: 1, + source, + } + })?, + ), + other => panic!("unknown credential serialization version {other}"), + }; + + info!("importing {}", credential.typ()); + match credential.variant_data() { + BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => { + info!("with value of {}", voucher_info.value()) + } + BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { + info!("with expiry at {}", freepass_info.expiry_date()); + if freepass_info.expiry_date() > OffsetDateTime::now_utc() { + error!("the free pass has already expired!"); + + // technically we can import it, but the gateway will just reject it so what's the point + return Err(Socks5ClientError::ExpiredCredentialImport { + expiration: freepass_info.expiry_date(), + }); + } + } + } + + let storable = StorableIssuedCredential { + serialization_revision: args.version, + credential_data: &raw_credential, + credential_type: credential.typ().to_string(), + epoch_id: credential + .epoch_id() + .try_into() + .expect("our epoch is has run over u32::MAX!"), + }; + + credentials_store.insert_issued_credential(storable).await?; + Ok(()) +} diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 3b2ebd25fa..da3166fd7e 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -26,6 +26,7 @@ use std::net::IpAddr; use std::sync::OnceLock; pub(crate) mod build_info; +mod import_credential; pub mod init; pub(crate) mod run; @@ -57,6 +58,9 @@ pub(crate) enum Commands { /// Run the Nym client with provided configuration client optionally overriding set parameters Run(run::Run), + /// Import a pre-generated credential + ImportCredential(import_credential::Args), + /// Show build information of this binary BuildInfo(build_info::BuildInfo), @@ -88,6 +92,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box init::execute(m).await?, Commands::Run(m) => run::execute(m).await?, + Commands::ImportCredential(m) => import_credential::execute(m).await?, Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), diff --git a/clients/socks5/src/error.rs b/clients/socks5/src/error.rs index 0005cadf8d..bed06bd385 100644 --- a/clients/socks5/src/error.rs +++ b/clients/socks5/src/error.rs @@ -1,4 +1,6 @@ use nym_client_core::error::ClientCoreError; +use nym_credential_storage::error::StorageError; +use time::OffsetDateTime; #[derive(thiserror::Error, Debug)] pub enum Socks5ClientError { @@ -18,6 +20,24 @@ pub enum Socks5ClientError { #[error("Fail to bind address")] FailToBindAddress, - #[error("client-core error: {0}")] + #[error(transparent)] ClientCoreError(#[from] ClientCoreError), + + #[error("failed to store credential: {source}")] + CredentialStorageFailure { + #[from] + source: StorageError, + }, + + #[error( + "failed to deserialize provided credential using revision {storage_revision}: {source}" + )] + CredentialDeserializationFailure { + storage_revision: u8, + #[source] + source: nym_credentials::error::Error, + }, + + #[error("attempted to import an expired credential (it expired on {expiration})")] + ExpiredCredentialImport { expiration: OffsetDateTime }, } diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index 3a552ecc77..b2ef8bc3ea 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true anyhow = { workspace = true } base64 = "0.13.0" bip39 = { workspace = true } -bs58 = "0.4" +bs58 = { workspace = true } comfy-table = "6.0.0" cfg-if = "1.0.0" clap = { workspace = true, features = ["derive"] } diff --git a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml index fc55402be0..db46d56ce7 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml +++ b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml @@ -8,7 +8,7 @@ license = { workspace = true } repository = { workspace = true } [dependencies] -bs58 = "0.4.0" +bs58 = { workspace = true } cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } schemars = "0.8" diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index 481ecbf1f7..061dea6183 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -9,7 +9,7 @@ repository = { workspace = true } [dependencies] aes = { version = "0.8.1", optional = true } -bs58 = "0.4.0" +bs58 = { workspace = true } blake3 = { version = "1.3.1", features = ["traits-preview"], optional = true } ctr = { version = "0.9.1", optional = true } digest = { version = "0.10.3", optional = true } diff --git a/common/dkg/Cargo.toml b/common/dkg/Cargo.toml index 03b0d35c9f..a2d101a231 100644 --- a/common/dkg/Cargo.toml +++ b/common/dkg/Cargo.toml @@ -14,7 +14,7 @@ bitvec = "1.0.0" # as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm bls12_381 = { workspace = true, default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true } -bs58 = "0.4" +bs58 = { workspace = true } lazy_static = "1.4.0" diff --git a/common/nymcoconut/Cargo.toml b/common/nymcoconut/Cargo.toml index 9984550071..428bc9da2b 100644 --- a/common/nymcoconut/Cargo.toml +++ b/common/nymcoconut/Cargo.toml @@ -15,7 +15,7 @@ rand = "0.8" thiserror = { workspace = true } serde = { workspace = true } serde_derive = "1.0" -bs58 = "0.4.0" +bs58 = { workspace = true } sha2 = "0.9" zeroize = { workspace = true, optional = true } diff --git a/common/nymsphinx/anonymous-replies/Cargo.toml b/common/nymsphinx/anonymous-replies/Cargo.toml index 58c7c44890..9c057f31f5 100644 --- a/common/nymsphinx/anonymous-replies/Cargo.toml +++ b/common/nymsphinx/anonymous-replies/Cargo.toml @@ -9,7 +9,7 @@ repository = { workspace = true } [dependencies] rand = { version = "0.7.3", features = ["wasm-bindgen"] } -bs58 = "0.4" +bs58 = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index 3d43cfe229..f8ec395887 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -12,7 +12,7 @@ documentation = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bs58 = "0.4" +bs58 = { workspace = true } log = { workspace = true } rand = { version = "0.7.3", features = ["wasm-bindgen"] } thiserror = { workspace = true } diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 2ee4e93b1b..f7c83faa24 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -116,6 +116,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.12.1" @@ -822,7 +831,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.49", ] [[package]] @@ -1250,7 +1259,7 @@ dependencies = [ name = "nym-contracts-common" version = "0.5.0" dependencies = [ - "bs58", + "bs58 0.5.0", "cosmwasm-schema", "cosmwasm-std", "schemars", @@ -1262,7 +1271,7 @@ dependencies = [ name = "nym-crypto" version = "0.4.0" dependencies = [ - "bs58", + "bs58 0.5.0", "ed25519-dalek", "nym-pemstore", "nym-sphinx-types", @@ -1318,7 +1327,7 @@ dependencies = [ name = "nym-mixnet-contract" version = "1.5.1" dependencies = [ - "bs58", + "bs58 0.4.0", "cosmwasm-derive", "cosmwasm-schema", "cosmwasm-std", @@ -1341,7 +1350,7 @@ dependencies = [ name = "nym-mixnet-contract-common" version = "0.6.0" dependencies = [ - "bs58", + "bs58 0.4.0", "cosmwasm-schema", "cosmwasm-std", "cw2", @@ -1376,7 +1385,7 @@ name = "nym-name-service" version = "0.1.0" dependencies = [ "anyhow", - "bs58", + "bs58 0.4.0", "cosmwasm-schema", "cosmwasm-std", "cw-controllers", @@ -1423,7 +1432,7 @@ name = "nym-service-provider-directory" version = "0.1.0" dependencies = [ "anyhow", - "bs58", + "bs58 0.4.0", "cosmwasm-schema", "cosmwasm-std", "cw-controllers", @@ -1610,9 +1619,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -1648,9 +1657,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.30" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1890,9 +1899,9 @@ checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.190" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] @@ -1908,13 +1917,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.49", ] [[package]] @@ -1947,7 +1956,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.49", ] [[package]] @@ -2002,7 +2011,7 @@ dependencies = [ "aes", "arrayref", "blake2", - "bs58", + "bs58 0.4.0", "byteorder", "chacha", "curve25519-dalek", @@ -2061,9 +2070,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.32" +version = "2.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" dependencies = [ "proc-macro2", "quote", @@ -2100,7 +2109,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.49", ] [[package]] @@ -2449,5 +2458,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.49", ] diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 83f0a46531..74e6e4bb07 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -34,6 +34,7 @@ incremental = false overflow-checks = true [workspace.dependencies] +bs58 = "0.4.0" cosmwasm-crypto = "=1.3.0" cosmwasm-derive = "=1.3.0" cosmwasm-schema = "=1.3.0" @@ -49,5 +50,6 @@ cw3-fixed-multisig = "=1.1.0" cw4 = "=1.1.0" cw20 = "=1.1.0" semver = "1.0.21" +serde = "1.0.196" thiserror = "1.0.48" diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index c365dda0c6..9f2ba07124 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -37,8 +37,8 @@ cosmwasm-derive = { workspace = true } cw2 = { workspace = true } cw-storage-plus = { workspace = true } -bs58 = "0.4.0" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } +bs58 = { workspace = true } +serde = { workspace = true, default-features = false, features = ["derive"] } thiserror = { workspace = true } time = { version = "0.3", features = ["macros"] } semver = { workspace = true, default-features = false } diff --git a/contracts/name-service/Cargo.toml b/contracts/name-service/Cargo.toml index 13cbb7a6c0..c1bf210637 100644 --- a/contracts/name-service/Cargo.toml +++ b/contracts/name-service/Cargo.toml @@ -11,7 +11,7 @@ required-features = ["schema-gen"] crate-type = ["cdylib", "rlib"] [dependencies] -bs58 = "0.4.0" +bs58 = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } cw-controllers = { workspace = true } diff --git a/contracts/service-provider-directory/Cargo.toml b/contracts/service-provider-directory/Cargo.toml index 4acd39aa09..e4a75c50f0 100644 --- a/contracts/service-provider-directory/Cargo.toml +++ b/contracts/service-provider-directory/Cargo.toml @@ -11,7 +11,7 @@ required-features = ["schema-gen"] crate-type = ["cdylib", "rlib"] [dependencies] -bs58 = "0.4.0" +bs58 = { workspace = true } cosmwasm-schema = { workspace = true, optional = true } cosmwasm-std = { workspace = true } cw-controllers = { workspace = true } diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index ac7aefb4a4..028658525d 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -20,7 +20,7 @@ anyhow = { workspace = true } async-trait = { workspace = true } atty = "0.2" bip39 = { workspace = true } -bs58 = "0.4.0" +bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } colored = "2.0" dashmap = { workspace = true } diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index e771a71de1..2ca9a3b440 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bs58 = "0.4.0" +bs58 = { workspace = true } futures = { workspace = true } generic-array = { workspace = true, features = ["serde"] } log = { workspace = true } diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index a15308cb4a..489b998e2c 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -19,7 +19,7 @@ rust-version = "1.58.1" [dependencies] axum = { workspace = true } anyhow = { workspace = true } -bs58 = "0.4.0" +bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"] } colored = "2.0" cupid = "0.6.1" diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 5a5f31d778..0bb2b2c263 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.56" [dependencies] async-trait = { workspace = true } -bs58 = { version = "0.4.0" } +bs58 = { workspace = true } bip39 = { workspace = true } cfg-if = "1.0" clap = { workspace = true, features = ["cargo", "derive"] } diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index d76ce70675..456c319224 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bs58 = "0.4.0" +bs58 = { workspace = true } cosmrs = { workspace = true } cosmwasm-std = { workspace = true } getset = "0.1.1" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 3c146f252d..f8ee12014b 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -612,6 +612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ "sha2 0.10.8", + "tinyvec", ] [[package]] @@ -3692,7 +3693,7 @@ dependencies = [ name = "nym-api-requests" version = "0.1.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "cosmrs", "cosmwasm-std", "ecdsa 0.16.8", @@ -3796,7 +3797,7 @@ name = "nym-coconut" version = "0.5.0" dependencies = [ "bls12_381", - "bs58 0.4.0", + "bs58 0.5.0", "digest 0.9.0", "ff 0.13.0", "getrandom 0.2.10", @@ -3901,7 +3902,7 @@ dependencies = [ name = "nym-contracts-common" version = "0.5.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "cosmwasm-schema", "cosmwasm-std", "schemars", @@ -3955,7 +3956,7 @@ version = "0.4.0" dependencies = [ "aes 0.8.3", "blake3", - "bs58 0.4.0", + "bs58 0.5.0", "cipher 0.4.4", "ctr 0.9.2", "digest 0.10.7", @@ -3980,7 +3981,7 @@ version = "0.1.0" dependencies = [ "bitvec", "bls12_381", - "bs58 0.4.0", + "bs58 0.5.0", "ff 0.13.0", "group 0.13.0", "lazy_static", @@ -4073,7 +4074,7 @@ dependencies = [ name = "nym-gateway-requests" version = "0.1.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "futures", "generic-array 0.14.7", "log", @@ -4358,7 +4359,7 @@ dependencies = [ name = "nym-sphinx-anonymous-replies" version = "0.1.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "nym-crypto", "nym-sphinx-addressing", "nym-sphinx-params", @@ -4468,7 +4469,7 @@ name = "nym-topology" version = "0.1.0" dependencies = [ "async-trait", - "bs58 0.4.0", + "bs58 0.5.0", "log", "nym-api-requests", "nym-bin-common", diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 885674bddb..ff36739bb2 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -452,6 +452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ "sha2 0.10.8", + "tinyvec", ] [[package]] @@ -3096,7 +3097,7 @@ dependencies = [ name = "nym-api-requests" version = "0.1.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "cosmwasm-std", "ecdsa 0.16.8", @@ -3132,7 +3133,7 @@ name = "nym-coconut" version = "0.5.0" dependencies = [ "bls12_381", - "bs58 0.4.0", + "bs58 0.5.0", "digest 0.9.0", "ff 0.13.0", "getrandom 0.2.10", @@ -3186,7 +3187,7 @@ dependencies = [ name = "nym-contracts-common" version = "0.5.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "cosmwasm-schema", "cosmwasm-std", "schemars", @@ -3208,7 +3209,7 @@ dependencies = [ name = "nym-crypto" version = "0.4.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "ed25519-dalek", "nym-pemstore", "nym-sphinx-types", @@ -3227,7 +3228,7 @@ version = "0.1.0" dependencies = [ "bitvec", "bls12_381", - "bs58 0.4.0", + "bs58 0.5.0", "ff 0.13.0", "group 0.13.0", "lazy_static", diff --git a/service-providers/ip-packet-router/Cargo.toml b/service-providers/ip-packet-router/Cargo.toml index ce6674550d..e700505a96 100644 --- a/service-providers/ip-packet-router/Cargo.toml +++ b/service-providers/ip-packet-router/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [dependencies] bincode = "1.3.3" -bs58 = "0.4.0" +bs58 = { workspace = true } bytes = "1.5.0" clap.workspace = true etherparse = "0.13.0" diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 323a11c286..b2486db390 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] addr = "0.15.6" async-trait = { workspace = true } -bs58 = "0.4.0" +bs58 = { workspace = true } clap = { workspace = true, features = ["cargo", "derive"]} dirs = "4.0" futures = { workspace = true } diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index 29a6f85e2f..194466b2f6 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true [dependencies] base64 = "0.13.0" -bs58 = "0.4" +bs58 = { workspace = true } clap = { workspace = true, features = ["derive"] } clap_complete = "4.0" clap_complete_fig = "4.0" diff --git a/tools/nym-cli/src/coconut/mod.rs b/tools/nym-cli/src/coconut/mod.rs index 4df05bb5fd..2939175aed 100644 --- a/tools/nym-cli/src/coconut/mod.rs +++ b/tools/nym-cli/src/coconut/mod.rs @@ -28,6 +28,9 @@ pub(crate) async fn execute( ) .await? } + nym_cli_commands::coconut::CoconutCommands::ImportCredential(args) => { + nym_cli_commands::coconut::import_credential::execute(args).await? + } } Ok(()) } From dcd6dcc6e3c72d1dcba2e7348075a6af82732fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 11:10:51 +0000 Subject: [PATCH 37/49] restored accidentally removed lazy static in socks5 lib --- sdk/lib/socks5-listener/Cargo.toml | 1 + sdk/lib/socks5-listener/src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/sdk/lib/socks5-listener/Cargo.toml b/sdk/lib/socks5-listener/Cargo.toml index 129afb5e87..425154a5cb 100644 --- a/sdk/lib/socks5-listener/Cargo.toml +++ b/sdk/lib/socks5-listener/Cargo.toml @@ -16,6 +16,7 @@ crate-type = ["cdylib", "staticlib", "rlib"] [dependencies] anyhow = { workspace = true } futures = { workspace = true } +lazy_static = "1.4.0" nym-bin-common = { path = "../../../common/bin-common"} nym-client-core = { path = "../../../common/client-core", default-features = false } nym-config-common = { path = "../../../common/config", package = "nym-config" } diff --git a/sdk/lib/socks5-listener/src/lib.rs b/sdk/lib/socks5-listener/src/lib.rs index 352638f84d..0c9e2f7771 100644 --- a/sdk/lib/socks5-listener/src/lib.rs +++ b/sdk/lib/socks5-listener/src/lib.rs @@ -5,6 +5,7 @@ use crate::config::{config_filepath_from_root, Config}; use crate::persistence::MobileClientStorage; use ::safer_ffi::prelude::*; use anyhow::{anyhow, Result}; +use lazy_static::lazy_static; use log::{debug, info, warn}; use nym_bin_common::logging::setup_logging; use nym_client_core::init::helpers::current_gateways; From 387d07fb9350548454573d293f745eff2292569d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 11:34:51 +0000 Subject: [PATCH 38/49] additional logs --- common/client-libs/gateway-client/src/client.rs | 2 ++ .../node/client_handling/websocket/connection_handler/fresh.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 2a9750e7a1..d5c5ec6d42 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -382,6 +382,8 @@ impl GatewayClient { &self, gateway_protocol: Option, ) -> Result<(), GatewayClientError> { + debug!("gateway protocol: {gateway_protocol:?}, ours: {INITIAL_PROTOCOL_VERSION}"); + // right now there are no failure cases here, but this might change in the future match gateway_protocol { None => { diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index a2e9e98e7d..930080b19e 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -363,6 +363,7 @@ where &self, client_protocol: Option, ) -> Result { + debug!("client protocol: {client_protocol:?}, ours: {INITIAL_PROTOCOL_VERSION}"); let Some(client_protocol_version) = client_protocol else { warn!("the client we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0"); // note: in +1.2.0 we will have to return a hard error here From 5cf53b70020c1c0ebfb6ff2690d28637de3653be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 12:11:50 +0000 Subject: [PATCH 39/49] fixed logging --- Cargo.lock | 62 +++++++++++-------- .../client-libs/gateway-client/src/client.rs | 2 +- contracts/Cargo.lock | 2 +- .../websocket/connection_handler/fresh.rs | 2 +- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c93874c55..55105ed993 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -825,6 +825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ "sha2 0.10.8", + "tinyvec", ] [[package]] @@ -2318,6 +2319,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve 0.13.6", "rfc6979 0.4.0", + "serdect", "signature 2.1.0", "spki 0.7.2", ] @@ -2428,6 +2430,7 @@ dependencies = [ "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", + "serdect", "subtle 2.4.1", "zeroize", ] @@ -2517,7 +2520,6 @@ dependencies = [ "humantime-serde", "isocountry", "itertools 0.10.5", - "lazy_static", "log", "maxminddb", "nym-bin-common", @@ -4923,7 +4925,7 @@ dependencies = [ "anyhow", "async-trait", "bip39", - "bs58 0.4.0", + "bs58 0.5.0", "cfg-if", "clap 4.4.7", "console-subscriber", @@ -4937,7 +4939,7 @@ dependencies = [ "getset", "humantime-serde", "itertools 0.12.0", - "lazy_static", + "k256", "log", "nym-api-requests", "nym-bandwidth-controller", @@ -4995,9 +4997,10 @@ dependencies = [ name = "nym-api-requests" version = "0.1.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)", "cosmwasm-std", + "ecdsa 0.16.8", "getset", "nym-credentials-interface", "nym-crypto", @@ -5074,7 +5077,7 @@ dependencies = [ "anyhow", "base64 0.13.1", "bip39", - "bs58 0.4.0", + "bs58 0.5.0", "clap 4.4.7", "clap_complete", "clap_complete_fig", @@ -5099,7 +5102,7 @@ dependencies = [ "anyhow", "base64 0.13.1", "bip39", - "bs58 0.4.0", + "bs58 0.5.0", "cfg-if", "clap 4.4.7", "comfy-table", @@ -5151,10 +5154,10 @@ dependencies = [ name = "nym-client" version = "1.1.32" dependencies = [ + "bs58 0.5.0", "clap 4.4.7", "dirs 4.0.0", "futures", - "lazy_static", "log", "nym-bandwidth-controller", "nym-bin-common", @@ -5171,15 +5174,16 @@ dependencies = [ "nym-task", "nym-topology", "nym-validator-client", - "pretty_env_logger", "rand 0.7.3", "serde", "serde_json", "tap", "thiserror", + "time", "tokio", "tokio-tungstenite", "url", + "zeroize", ] [[package]] @@ -5270,7 +5274,7 @@ name = "nym-coconut" version = "0.5.0" dependencies = [ "bls12_381", - "bs58 0.4.0", + "bs58 0.5.0", "criterion", "digest 0.9.0", "doc-comment", @@ -5329,7 +5333,7 @@ dependencies = [ name = "nym-contracts-common" version = "0.5.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "cosmwasm-schema", "cosmwasm-std", "schemars", @@ -5401,7 +5405,7 @@ version = "0.4.0" dependencies = [ "aes 0.8.3", "blake3", - "bs58 0.4.0", + "bs58 0.5.0", "cipher 0.4.4", "ctr 0.9.2", "digest 0.10.7", @@ -5427,7 +5431,7 @@ version = "0.1.0" dependencies = [ "bitvec", "bls12_381", - "bs58 0.4.0", + "bs58 0.5.0", "criterion", "ff 0.13.0", "group 0.13.0", @@ -5507,7 +5511,7 @@ dependencies = [ "async-trait", "atty", "bip39", - "bs58 0.4.0", + "bs58 0.5.0", "clap 4.4.7", "colored", "dashmap", @@ -5518,7 +5522,6 @@ dependencies = [ "humantime-serde", "hyper", "ipnetwork 0.16.0", - "lazy_static", "log", "nym-api-requests", "nym-bin-common", @@ -5595,7 +5598,7 @@ dependencies = [ name = "nym-gateway-requests" version = "0.1.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "futures", "generic-array 0.14.7", "log", @@ -5652,12 +5655,11 @@ name = "nym-ip-packet-router" version = "0.1.0" dependencies = [ "bincode", - "bs58 0.4.0", + "bs58 0.5.0", "bytes", "clap 4.4.7", "etherparse", "futures", - "lazy_static", "log", "nym-bin-common", "nym-client-core", @@ -5726,7 +5728,7 @@ version = "1.1.34" dependencies = [ "anyhow", "axum", - "bs58 0.4.0", + "bs58 0.5.0", "cfg-if", "clap 4.4.7", "colored", @@ -5735,7 +5737,6 @@ dependencies = [ "dirs 4.0.0", "futures", "humantime-serde", - "lazy_static", "log", "nym-bin-common", "nym-config", @@ -5848,13 +5849,12 @@ dependencies = [ "anyhow", "async-file-watcher", "async-trait", - "bs58 0.4.0", + "bs58 0.5.0", "clap 4.4.7", "dirs 4.0.0", "futures", "humantime-serde", "ipnetwork 0.20.0", - "lazy_static", "log", "nym-bin-common", "nym-client-core", @@ -6138,8 +6138,8 @@ dependencies = [ name = "nym-socks5-client" version = "1.1.32" dependencies = [ + "bs58 0.5.0", "clap 4.4.7", - "lazy_static", "log", "nym-bin-common", "nym-client-core", @@ -6154,14 +6154,15 @@ dependencies = [ "nym-socks5-client-core", "nym-sphinx", "nym-topology", - "pretty_env_logger", "rand 0.7.3", "serde", "serde_json", "tap", "thiserror", + "time", "tokio", "url", + "zeroize", ] [[package]] @@ -6305,7 +6306,7 @@ dependencies = [ name = "nym-sphinx-anonymous-replies" version = "0.1.0" dependencies = [ - "bs58 0.4.0", + "bs58 0.5.0", "nym-crypto", "nym-sphinx-addressing", "nym-sphinx-params", @@ -6445,7 +6446,7 @@ name = "nym-topology" version = "0.1.0" dependencies = [ "async-trait", - "bs58 0.4.0", + "bs58 0.5.0", "log", "nym-api-requests", "nym-bin-common", @@ -8582,6 +8583,7 @@ dependencies = [ "der 0.7.8", "generic-array 0.14.7", "pkcs8 0.10.2", + "serdect", "subtle 2.4.1", "zeroize", ] @@ -8814,6 +8816,16 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct 0.2.0", + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index d5c5ec6d42..a6f0600d82 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -382,7 +382,7 @@ impl GatewayClient { &self, gateway_protocol: Option, ) -> Result<(), GatewayClientError> { - debug!("gateway protocol: {gateway_protocol:?}, ours: {INITIAL_PROTOCOL_VERSION}"); + debug!("gateway protocol: {gateway_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}"); // right now there are no failure cases here, but this might change in the future match gateway_protocol { diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index f7c83faa24..1b7383f41f 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -187,7 +187,7 @@ dependencies = [ name = "coconut-test" version = "0.1.0" dependencies = [ - "bs58", + "bs58 0.4.0", "cosmwasm-std", "cosmwasm-storage", "cw-controllers", diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 930080b19e..46dc7f5f80 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -363,7 +363,7 @@ where &self, client_protocol: Option, ) -> Result { - debug!("client protocol: {client_protocol:?}, ours: {INITIAL_PROTOCOL_VERSION}"); + debug!("client protocol: {client_protocol:?}, ours: {CURRENT_PROTOCOL_VERSION}"); let Some(client_protocol_version) = client_protocol else { warn!("the client we're connected to has not specified its protocol version. It's probably running version < 1.1.X, but that's still fine for now. It will become a hard error in 1.2.0"); // note: in +1.2.0 we will have to return a hard error here From ff01fc79e3cda41a4c72416e478816710ee8f6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 12:19:54 +0000 Subject: [PATCH 40/49] removed duplicate code --- nym-api/src/coconut/tests/mod.rs | 46 -------------------------------- 1 file changed, 46 deletions(-) diff --git a/nym-api/src/coconut/tests/mod.rs b/nym-api/src/coconut/tests/mod.rs index 9912916a0d..0cdb60a1b5 100644 --- a/nym-api/src/coconut/tests/mod.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -797,52 +797,6 @@ impl super::client::Client for DummyClient { }) } - async fn get_dealer_dealings_status( - &self, - epoch_id: EpochId, - dealer: String, - ) -> Result { - let guard = self.state.lock().unwrap(); - let key_size = guard.dkg_contract.contract_state.key_size; - - let dealer_addr = Addr::unchecked(&dealer); - - let Some(epoch_dealings) = guard.dkg_contract.dealings.get(&epoch_id) else { - return Ok(DealerDealingsStatusResponse { - epoch_id, - dealer: dealer_addr, - all_dealings_fully_submitted: false, - dealing_submission_status: Default::default(), - }); - }; - - let Some(dealer_dealings) = epoch_dealings.get(&dealer) else { - return Ok(DealerDealingsStatusResponse { - epoch_id, - dealer: dealer_addr, - all_dealings_fully_submitted: false, - dealing_submission_status: Default::default(), - }); - }; - - let mut dealing_submission_status: BTreeMap = BTreeMap::new(); - for dealing_index in 0..key_size { - let metadata = dealer_dealings - .get(&dealing_index) - .map(|d| d.metadata.clone()); - dealing_submission_status.insert(dealing_index, metadata.into()); - } - - Ok(DealerDealingsStatusResponse { - epoch_id, - dealer: Addr::unchecked(&dealer), - all_dealings_fully_submitted: dealing_submission_status - .values() - .all(|d| d.fully_submitted), - dealing_submission_status, - }) - } - async fn get_dealing_status( &self, epoch_id: EpochId, From 88a49dfc7e8d9d1167269ebcb4f1c62c616ba968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 15:26:51 +0000 Subject: [PATCH 41/49] making sure the retrieved credentials haven't expired --- common/bandwidth-controller/src/error.rs | 3 + common/bandwidth-controller/src/lib.rs | 87 ++++++++++++++----- .../20240206120000_add_credential_types.sql | 3 +- .../credential-storage/src/backends/memory.rs | 19 +++- .../credential-storage/src/backends/sqlite.rs | 27 ++++-- .../src/ephemeral_storage.rs | 15 ++-- common/credential-storage/src/models.rs | 1 + .../src/persistent_storage.rs | 15 ++-- common/credential-storage/src/storage.rs | 12 ++- .../src/coconut/bandwidth/freepass.rs | 4 + 10 files changed, 144 insertions(+), 42 deletions(-) diff --git a/common/bandwidth-controller/src/error.rs b/common/bandwidth-controller/src/error.rs index 4adda09b0f..44832aaab4 100644 --- a/common/bandwidth-controller/src/error.rs +++ b/common/bandwidth-controller/src/error.rs @@ -21,6 +21,9 @@ pub enum BandwidthControllerError { #[error("There was a credential storage error - {0}")] CredentialStorageError(Box), + #[error("the credential storage does not contain any usable credentials")] + NoCredentialsAvailable, + // this should really be fully incorporated into the above, but messing with coconut is the last thing I want to do now #[error(transparent)] StorageError(#[from] StorageError), diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index fbfcf41865..31d499dac9 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -3,10 +3,12 @@ use crate::error::BandwidthControllerError; use crate::utils::stored_credential_to_issued_bandwidth; -use log::{error, warn}; +use log::{debug, error, warn}; use nym_credential_storage::storage::Storage; +use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant; use nym_credentials::coconut::bandwidth::CredentialSpendingData; use nym_credentials::coconut::utils::obtain_aggregate_verification_key; +use nym_credentials::IssuedBandwidthCredential; use nym_credentials_interface::VerificationKey; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nym_api::EpochId; @@ -33,11 +35,67 @@ pub struct PreparedCredential { pub credential_id: i64, } +pub struct RetrievedCredential { + pub credential: IssuedBandwidthCredential, + pub credential_id: i64, +} + impl BandwidthController { pub fn new(storage: St, client: C) -> Self { BandwidthController { storage, client } } + /// Tries to retrieve one of the stored, unused credentials that hasn't yet expired. + /// It marks any retrieved intermediate credentials as expired. + pub async fn get_next_usable_credential( + &self, + ) -> Result + where + ::StorageError: Send + Sync + 'static, + { + loop { + let Some(maybe_next) = self + .storage + .get_next_unspent_credential() + .await + .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))? + else { + return Err(BandwidthControllerError::NoCredentialsAvailable); + }; + let id = maybe_next.id; + + // try to deserialize it + let valid_credential = match stored_credential_to_issued_bandwidth(maybe_next) { + // check if it has already expired + Ok(credential) => match credential.variant_data() { + BandwidthCredentialIssuedDataVariant::Voucher(_) => { + debug!("credential {id} is a bandwidth voucher"); + credential + } + BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { + debug!("credential {id} is a free pass"); + if freepass_info.expired() { + warn!("the free pass (id: {id}) has already expired! The expiration was set to {}", freepass_info.expiry_date()); + self.storage.mark_expired(id).await.map_err(|err| { + BandwidthControllerError::CredentialStorageError(Box::new(err)) + })?; + continue; + } + credential + } + }, + Err(err) => { + error!("failed to deserialize credential with id {id}: {err}. it may need to be manually removed from the storage"); + return Err(err); + } + }; + return Ok(RetrievedCredential { + credential: valid_credential, + credential_id: id, + }); + } + } + pub fn storage(&self) -> &St { &self.storage } @@ -61,29 +119,16 @@ impl BandwidthController { C: DkgQueryClient + Sync + Send, ::StorageError: Send + Sync + 'static, { - let retrieved_credential = self - .storage - .get_next_unspent_credential() - .await - .map_err(|err| BandwidthControllerError::CredentialStorageError(Box::new(err)))?; + let retrieved_credential = self.get_next_usable_credential().await?; - let epoch_id = retrieved_credential.epoch_id as EpochId; - let credential_id = retrieved_credential.id; + let epoch_id = retrieved_credential.credential.epoch_id(); + let credential_id = retrieved_credential.credential_id; - let issued_bandwidth = stored_credential_to_issued_bandwidth(retrieved_credential)?; + let verification_key = self.get_aggregate_verification_key(epoch_id).await?; - let verification_key = match self.get_aggregate_verification_key(epoch_id).await { - Ok(key) => key, - Err(err) => { - warn!("failed to obtain master verification key: {err}. Putting the credential back into the database"); - - // TODO: ERROR RECOVERY: - error!("unimplemented: putting the credential back into the database"); - return Err(err); - } - }; - - let spend_request = issued_bandwidth.prepare_for_spending(&verification_key)?; + let spend_request = retrieved_credential + .credential + .prepare_for_spending(&verification_key)?; Ok(PreparedCredential { data: spend_request, diff --git a/common/credential-storage/migrations/20240206120000_add_credential_types.sql b/common/credential-storage/migrations/20240206120000_add_credential_types.sql index 696d6d46a4..dfa7eda2ef 100644 --- a/common/credential-storage/migrations/20240206120000_add_credential_types.sql +++ b/common/credential-storage/migrations/20240206120000_add_credential_types.sql @@ -13,5 +13,6 @@ CREATE TABLE coconut_credentials credential_type TEXT NOT NULL, credential_data BLOB NOT NULL, epoch_id INTEGER NOT NULL, - consumed BOOLEAN NOT NULL + consumed BOOLEAN NOT NULL, + expired BOOLEAN NOT NULL ); \ No newline at end of file diff --git a/common/credential-storage/src/backends/memory.rs b/common/credential-storage/src/backends/memory.rs index 80538337c8..ddf5b7017e 100644 --- a/common/credential-storage/src/backends/memory.rs +++ b/common/credential-storage/src/backends/memory.rs @@ -48,13 +48,18 @@ impl CoconutCredentialManager { credential_type, epoch_id, consumed: false, + expired: false, }) } /// Tries to retrieve one of the stored, unused credentials. pub async fn get_next_unspent_credential(&self) -> Option { let creds = self.inner.read().await; - creds.data.iter().find(|c| !c.consumed).cloned() + creds + .data + .iter() + .find(|c| !c.consumed && !c.expired) + .cloned() } /// Consumes in the database the specified credential. @@ -68,4 +73,16 @@ impl CoconutCredentialManager { cred.consumed = true; } } + + /// Marks the specified credential as expired + /// + /// # Arguments + /// + /// * `id`: Id of the credential to mark as expired. + pub async fn mark_expired(&self, id: i64) { + let mut creds = self.inner.write().await; + if let Some(cred) = creds.data.get_mut(id as usize) { + cred.expired = true; + } + } } diff --git a/common/credential-storage/src/backends/sqlite.rs b/common/credential-storage/src/backends/sqlite.rs index d007c03c16..50465f434d 100644 --- a/common/credential-storage/src/backends/sqlite.rs +++ b/common/credential-storage/src/backends/sqlite.rs @@ -27,8 +27,8 @@ impl CoconutCredentialManager { ) -> Result<(), sqlx::Error> { sqlx::query!( r#" - INSERT INTO coconut_credentials(serialization_revision, credential_type, credential_data, epoch_id, consumed) - VALUES (?, ?, ?, ?, false) + INSERT INTO coconut_credentials(serialization_revision, credential_type, credential_data, epoch_id, consumed, expired) + VALUES (?, ?, ?, ?, false, false) "#, serialization_revision, credential_type, credential_data, epoch_id ).execute(&self.connection_pool).await?; @@ -38,9 +38,11 @@ impl CoconutCredentialManager { pub async fn get_next_unspent_credential( &self, ) -> Result, sqlx::Error> { - sqlx::query_as("SELECT * FROM coconut_credentials WHERE NOT consumed LIMIT 1") - .fetch_optional(&self.connection_pool) - .await + sqlx::query_as( + "SELECT * FROM coconut_credentials WHERE NOT consumed AND NOT expired LIMIT 1", + ) + .fetch_optional(&self.connection_pool) + .await } /// Consumes in the database the specified credential. @@ -57,4 +59,19 @@ impl CoconutCredentialManager { .await?; Ok(()) } + + /// Marks the specified credential as expired + /// + /// # Arguments + /// + /// * `id`: Id of the credential to mark as expired. + pub async fn mark_expired(&self, id: i64) -> Result<(), sqlx::Error> { + sqlx::query!( + "UPDATE coconut_credentials SET expired = TRUE WHERE id = ?", + id + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } } diff --git a/common/credential-storage/src/ephemeral_storage.rs b/common/credential-storage/src/ephemeral_storage.rs index aa6756ac78..5b1b5b23e9 100644 --- a/common/credential-storage/src/ephemeral_storage.rs +++ b/common/credential-storage/src/ephemeral_storage.rs @@ -44,14 +44,11 @@ impl Storage for EphemeralStorage { async fn get_next_unspent_credential( &self, - ) -> Result { - let credential = self + ) -> Result, Self::StorageError> { + Ok(self .coconut_credential_manager .get_next_unspent_credential() - .await - .ok_or(StorageError::NoCredential)?; - - Ok(credential) + .await) } async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> { @@ -61,4 +58,10 @@ impl Storage for EphemeralStorage { Ok(()) } + + async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError> { + self.coconut_credential_manager.mark_expired(id).await; + + Ok(()) + } } diff --git a/common/credential-storage/src/models.rs b/common/credential-storage/src/models.rs index 2695a190ad..5af5a3c089 100644 --- a/common/credential-storage/src/models.rs +++ b/common/credential-storage/src/models.rs @@ -27,6 +27,7 @@ pub struct StoredIssuedCredential { pub epoch_id: u32, pub consumed: bool, + pub expired: bool, } pub struct StorableIssuedCredential<'a> { diff --git a/common/credential-storage/src/persistent_storage.rs b/common/credential-storage/src/persistent_storage.rs index b6772fdf56..8d0961c38d 100644 --- a/common/credential-storage/src/persistent_storage.rs +++ b/common/credential-storage/src/persistent_storage.rs @@ -76,14 +76,11 @@ impl Storage for PersistentStorage { async fn get_next_unspent_credential( &self, - ) -> Result { - let credential = self + ) -> Result, Self::StorageError> { + Ok(self .coconut_credential_manager .get_next_unspent_credential() - .await? - .ok_or(StorageError::NoCredential)?; - - Ok(credential) + .await?) } async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> { @@ -93,4 +90,10 @@ impl Storage for PersistentStorage { Ok(()) } + + async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError> { + self.coconut_credential_manager.mark_expired(id).await?; + + Ok(()) + } } diff --git a/common/credential-storage/src/storage.rs b/common/credential-storage/src/storage.rs index 64fe0d6e82..8f8cb7798e 100644 --- a/common/credential-storage/src/storage.rs +++ b/common/credential-storage/src/storage.rs @@ -14,10 +14,11 @@ pub trait Storage: Send + Sync { bandwidth_credential: StorableIssuedCredential<'a>, ) -> Result<(), Self::StorageError>; - /// Tries to retrieve one of the stored, unused credentials. + /// Tries to retrieve one of the stored, unused credentials, + /// that is also not marked as expired async fn get_next_unspent_credential( &self, - ) -> Result; + ) -> Result, Self::StorageError>; /// Marks as consumed in the database the specified credential. /// @@ -25,4 +26,11 @@ pub trait Storage: Send + Sync { /// /// * `id`: Id of the credential to be consumed. async fn consume_coconut_credential(&self, id: i64) -> Result<(), Self::StorageError>; + + /// Marks the specified credential as expired + /// + /// # Arguments + /// + /// * `id`: Id of the credential to mark as expired. + async fn mark_expired(&self, id: i64) -> Result<(), Self::StorageError>; } diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs index 1321e59f1d..b90c8b2dee 100644 --- a/common/credentials/src/coconut/bandwidth/freepass.rs +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -30,6 +30,10 @@ impl<'a> From<&'a FreePassIssuanceData> for FreePassIssuedData { } impl FreePassIssuedData { + pub fn expired(&self) -> bool { + self.expiry_date <= OffsetDateTime::now_utc() + } + pub fn expiry_date(&self) -> OffsetDateTime { self.expiry_date } From d3e30e98f934a50e946926fb2dc5181309d687ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 16:01:29 +0000 Subject: [PATCH 42/49] preventing spending credentials with outdated gateways --- common/client-libs/gateway-client/src/client.rs | 16 +++++++++++++++- common/client-libs/gateway-client/src/error.rs | 3 +++ gateway/gateway-requests/src/lib.rs | 4 +++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index a6f0600d82..461e5ae4d0 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -20,7 +20,8 @@ use nym_gateway_requests::authentication::encrypted_address::EncryptedAddressByt use nym_gateway_requests::iv::IV; use nym_gateway_requests::registration::handshake::{client_handshake, SharedKeys}; use nym_gateway_requests::{ - BinaryRequest, ClientControlRequest, ServerResponse, CURRENT_PROTOCOL_VERSION, + BinaryRequest, ClientControlRequest, ServerResponse, CREDENTIAL_UPDATE_V1_PROTOCOL_VERSION, + CURRENT_PROTOCOL_VERSION, }; use nym_network_defaults::{REMAINING_BANDWIDTH_THRESHOLD, TOKENS_TO_BURN}; use nym_sphinx::forwarding::packet::MixPacket; @@ -493,6 +494,7 @@ impl GatewayClient { self.check_gateway_protocol(protocol_version)?; self.authenticated = status; self.bandwidth_remaining = bandwidth_remaining; + self.negotiated_protocol = protocol_version; Ok(()) } ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), @@ -579,6 +581,18 @@ impl GatewayClient { return self.try_claim_testnet_bandwidth().await; } + let Some(gateway_protocol) = self.negotiated_protocol else { + return Err(GatewayClientError::OutdatedGatewayCredentialVersion { + negotiated_protocol: None, + }); + }; + + if gateway_protocol < CREDENTIAL_UPDATE_V1_PROTOCOL_VERSION { + return Err(GatewayClientError::OutdatedGatewayCredentialVersion { + negotiated_protocol: Some(gateway_protocol), + }); + } + let prepared_credential = self .bandwidth_controller .as_ref() diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs index 081ea3f999..4bcaf4cfd1 100644 --- a/common/client-libs/gateway-client/src/error.rs +++ b/common/client-libs/gateway-client/src/error.rs @@ -47,6 +47,9 @@ pub enum GatewayClientError { #[error("Credential could not be serialized")] SerializeCredential, + #[error("can not spend bandwidth credential with the gateway as it's using outdated protocol (version: {negotiated_protocol:?})")] + OutdatedGatewayCredentialVersion { negotiated_protocol: Option }, + #[error("Client is not authenticated")] NotAuthenticated, diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index e9aae85d7f..45bc486322 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -13,13 +13,15 @@ pub mod models; pub mod registration; pub mod types; +pub const CURRENT_PROTOCOL_VERSION: u8 = CREDENTIAL_UPDATE_V1_PROTOCOL_VERSION; + /// Defines the current version of the communication protocol between gateway and clients. /// It has to be incremented for any breaking change. // history: // 1 - initial release // 2 - changes to client credentials structure pub const INITIAL_PROTOCOL_VERSION: u8 = 1; -pub const CURRENT_PROTOCOL_VERSION: u8 = 2; +pub const CREDENTIAL_UPDATE_V1_PROTOCOL_VERSION: u8 = 2; pub type GatewayMac = HmacOutput; From d62a41b9c1475fdda7246cb1896d564624042b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 17:09:16 +0000 Subject: [PATCH 43/49] fixed client route used for free pass --- common/client-libs/validator-client/src/nym_api/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index a2ad37e5d8..5cf79d462e 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -397,7 +397,7 @@ pub trait NymApiClientExt: ApiClient { routes::API_VERSION, routes::COCONUT_ROUTES, routes::BANDWIDTH, - routes::COCONUT_FREE_PASS_NONCE, + routes::COCONUT_FREE_PASS, ], NO_PARAMS, request, From 1a8814ccdce5a9eb9fb3f56325ac194b73ec61b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 17:45:39 +0000 Subject: [PATCH 44/49] changed nonces to be random bytes to prevent replay attacks --- .../src/coconut/bandwidth/freepass.rs | 7 ++- .../20240209120000_free_pass_count.sql | 13 +++++ .../20240209120000_free_pass_nonces.sql | 12 ----- .../nym-api-requests/src/coconut/models.rs | 10 ++-- nym-api/src/coconut/api_routes/mod.rs | 31 +++++++----- nym-api/src/coconut/error.rs | 7 ++- nym-api/src/coconut/state.rs | 9 +++- nym-api/src/coconut/storage/manager.rs | 50 ++----------------- nym-api/src/coconut/storage/mod.rs | 12 ++--- 9 files changed, 58 insertions(+), 93 deletions(-) create mode 100644 nym-api/migrations/20240209120000_free_pass_count.sql delete mode 100644 nym-api/migrations/20240209120000_free_pass_nonces.sql diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs index b90c8b2dee..5bb971c2cf 100644 --- a/common/credentials/src/coconut/bandwidth/freepass.rs +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -93,7 +93,7 @@ impl FreePassIssuanceData { pub async fn obtain_free_pass_nonce( &self, client: &nym_validator_client::client::NymApiClient, - ) -> Result { + ) -> Result<[u8; 16], Error> { let server_response = client.free_pass_nonce().await?; Ok(server_response.current_nonce) } @@ -102,12 +102,11 @@ impl FreePassIssuanceData { &self, signing_request: &CredentialSigningData, account_data: &AccountData, - issuer_nonce: u32, + issuer_nonce: [u8; 16], ) -> Result { - let plaintext = issuer_nonce.to_be_bytes(); let nonce_signature = account_data .private_key() - .sign(&plaintext) + .sign(&issuer_nonce) .map_err(|_| Error::Secp256k1SignFailure)?; Ok(FreePassRequest { diff --git a/nym-api/migrations/20240209120000_free_pass_count.sql b/nym-api/migrations/20240209120000_free_pass_count.sql new file mode 100644 index 0000000000..f468e925cb --- /dev/null +++ b/nym-api/migrations/20240209120000_free_pass_count.sql @@ -0,0 +1,13 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE TABLE issued_freepass +( + id INTEGER PRIMARY KEY CHECK (id = 0), + issued INTEGER NOT NULL +); + +INSERT INTO issued_freepass(id, issued) +VALUES (0, 0); \ No newline at end of file diff --git a/nym-api/migrations/20240209120000_free_pass_nonces.sql b/nym-api/migrations/20240209120000_free_pass_nonces.sql deleted file mode 100644 index 441910036b..0000000000 --- a/nym-api/migrations/20240209120000_free_pass_nonces.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2024 - Nym Technologies SA - * SPDX-License-Identifier: Apache-2.0 - */ - -CREATE TABLE issued_freepass -( - id INTEGER PRIMARY KEY CHECK (id = 0), - current_nonce INTEGER NOT NULL -); - -INSERT INTO issued_freepass(id, current_nonce) VALUES (0,0); \ No newline at end of file diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index aaa980cc5a..50df750a82 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -105,7 +105,7 @@ impl BlindSignRequestBody { #[derive(Debug, Serialize, Deserialize)] pub struct FreePassNonceResponse { - pub current_nonce: u32, + pub current_nonce: [u8; 16], } #[derive(Debug, Serialize, Deserialize)] @@ -147,7 +147,7 @@ pub struct FreePassRequest { // we need to include a nonce here to prevent replay attacks // (and not making the nym-api store the serial numbers of all issued credential) - pub used_nonce: u32, + pub used_nonce: [u8; 16], /// Signature on the nonce /// to prove the possession of the cosmos key/address @@ -160,7 +160,7 @@ impl FreePassRequest { pub fn new( cosmos_pubkey: cosmrs::crypto::PublicKey, inner_sign_request: BlindSignRequest, - used_nonce: u32, + used_nonce: [u8; 16], nonce_signature: cosmrs::crypto::secp256k1::Signature, public_attributes_plain: Vec, ) -> Self { @@ -177,10 +177,6 @@ impl FreePassRequest { self.cosmos_pubkey.into() } - pub fn nonce_plaintext(&self) -> [u8; 4] { - self.used_nonce.to_be_bytes() - } - pub fn public_attributes_hashed(&self) -> Vec { self.public_attributes_plain .iter() diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 013f600897..9b38eaf135 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -23,6 +23,8 @@ use nym_credentials::coconut::bandwidth::{ bandwidth_credential_params, CredentialType, IssuanceBandwidthCredential, }; use nym_validator_client::nyxd::Coin; +use rand::rngs::OsRng; +use rand::RngCore; use rocket::serde::json::Json; use rocket::State as RocketState; use std::ops::Deref; @@ -74,10 +76,12 @@ pub async fn get_current_free_pass_nonce( ) -> Result> { debug!("Received free pass nonce request"); - let current_nonce = state.storage.get_current_freepass_nonce().await?; - debug!("the current expected nonce is {current_nonce}"); + let current_nonce = state.freepass_nonce.read().await; + debug!("the current expected nonce is {current_nonce:?}"); - Ok(Json(FreePassNonceResponse { current_nonce })) + Ok(Json(FreePassNonceResponse { + current_nonce: *current_nonce, + })) } #[post("/free-pass", data = "")] @@ -117,12 +121,13 @@ pub async fn post_free_pass( }); } - let current_nonce = state.storage.get_current_freepass_nonce().await?; - debug!("the current expected nonce is {current_nonce}"); + // get the write lock on the nonce to block other requests (since we don't need concurrency and nym is the only one getting them) + let mut current_nonce = state.freepass_nonce.write().await; + debug!("the current expected nonce is {current_nonce:?}"); - if current_nonce != freepass_request_body.used_nonce { + if *current_nonce != freepass_request_body.used_nonce { return Err(CoconutError::InvalidNonce { - current: current_nonce, + current: *current_nonce, received: freepass_request_body.used_nonce, }); } @@ -147,7 +152,7 @@ pub async fn post_free_pass( // make sure the signature actually verifies secp256k1_pubkey .verify( - &freepass_request_body.nonce_plaintext(), + &freepass_request_body.used_nonce, &freepass_request_body.nonce_signature, ) .map_err(|_| CoconutError::FreePassSignatureVerificationFailure)?; @@ -157,11 +162,11 @@ pub async fn post_free_pass( let blinded_signature = blind_sign(freepass_request_body.deref(), signing_key.keys.secret_key())?; - // update the nonce in storage (and also check if a parallel request hasn't updated it; if so we return an error. no race conditions allowed) - state - .storage - .update_and_validate_freepass_nonce(current_nonce + 1) - .await?; + // update the number of issued free passes + state.storage.increment_issued_freepasses().await?; + + // update the nonce + OsRng.fill_bytes(current_nonce.as_mut_slice()); // finally return the credential to the client Ok(Json(BlindedSignatureResponse { blinded_signature })) diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index d5f6ac81d6..593e78a016 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -46,8 +46,11 @@ pub enum CoconutError { #[error("failed to verify signature on the provided free pass request")] FreePassSignatureVerificationFailure, - #[error("the provided signing nonce is invalid. the current value is: {current}. got {received} instead")] - InvalidNonce { current: u32, received: u32 }, + #[error("the provided signing nonce is invalid. the current value is: {current:?}. got {received:?} instead")] + InvalidNonce { + current: [u8; 16], + received: [u8; 16], + }, #[error("only secp256k1 keys are supported for free pass issuance")] UnsupportedNonSecp256k1Key, diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs index c85c77575a..3e04110888 100644 --- a/nym-api/src/coconut/state.rs +++ b/nym-api/src/coconut/state.rs @@ -14,8 +14,10 @@ use nym_coconut::{BlindedSignature, VerificationKey}; use nym_coconut_dkg_common::types::EpochId; use nym_crypto::asymmetric::identity; use nym_validator_client::nyxd::{AccountId, Hash, TxResponse}; +use rand::rngs::OsRng; +use rand::RngCore; use std::sync::Arc; -use tokio::sync::OnceCell; +use tokio::sync::{OnceCell, RwLock}; pub use nym_credentials::coconut::bandwidth::bandwidth_credential_params; @@ -27,6 +29,7 @@ pub struct State { pub(crate) identity_keypair: identity::KeyPair, pub(crate) comm_channel: Arc, pub(crate) storage: NymApiStorage, + pub(crate) freepass_nonce: Arc>, } impl State { @@ -45,6 +48,9 @@ impl State { let client = Arc::new(client); let comm_channel = Arc::new(comm_channel); + let mut nonce = [0u8; 16]; + OsRng.fill_bytes(&mut nonce); + Self { client, bandwidth_contract_admin: OnceCell::new(), @@ -53,6 +59,7 @@ impl State { identity_keypair, comm_channel, storage, + freepass_nonce: Arc::new(RwLock::new(nonce)), } } diff --git a/nym-api/src/coconut/storage/manager.rs b/nym-api/src/coconut/storage/manager.rs index 17816bebd8..313a2db047 100644 --- a/nym-api/src/coconut/storage/manager.rs +++ b/nym-api/src/coconut/storage/manager.rs @@ -122,16 +122,7 @@ pub trait CoconutStorageManagerExt { limit: u32, ) -> Result, sqlx::Error>; - /// Attempts to retrieve the current value of the freepass nonce. - async fn get_current_freepass_nonce(&self) -> Result; - - /// Attempt to update the currently stored nonce to the provided value whilst ensuring - /// it's strictly equal the current value plus 1 - /// - /// # Arguments - /// - /// * `new`: the new value of the free pass nonce - async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), sqlx::Error>; + async fn increment_issued_freepasses(&self) -> Result<(), sqlx::Error>; } #[async_trait] @@ -391,42 +382,11 @@ impl CoconutStorageManagerExt for StorageManager { .await } - /// Attempts to retrieve the current value of the freepass nonce. - async fn get_current_freepass_nonce(&self) -> Result { - sqlx::query!("SELECT current_nonce as 'current_nonce: u32' FROM issued_freepass") - .fetch_one(&self.connection_pool) - .await - .map(|row| row.current_nonce) - } - - /// Attempt to update the currently stored nonce to the provided value whilst ensuring - /// it's strictly equal the current value plus 1 - /// - /// # Arguments - /// - /// * `new`: the new value of the free pass nonce - async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), sqlx::Error> { - let mut tx = self.connection_pool.begin().await?; - - let currently_stored = - sqlx::query!("SELECT current_nonce as 'current_nonce: u32' FROM issued_freepass") - .fetch_one(&mut tx) - .await? - .current_nonce; - - if currently_stored + 1 != new { - // this is not the best error but I really don't want to be creating a new enum - return Err(sqlx::Error::Decode(Box::new(UnexpectedNonce { - current: currently_stored, - got: new, - }))); - } - - sqlx::query!("UPDATE issued_freepass SET current_nonce = ?", new) - .execute(&mut tx) + async fn increment_issued_freepasses(&self) -> Result<(), sqlx::Error> { + sqlx::query!("UPDATE issued_freepass SET issued = issued + 1",) + .execute(&self.connection_pool) .await?; - - tx.commit().await + Ok(()) } } diff --git a/nym-api/src/coconut/storage/mod.rs b/nym-api/src/coconut/storage/mod.rs index f77703cea8..45cecf1f23 100644 --- a/nym-api/src/coconut/storage/mod.rs +++ b/nym-api/src/coconut/storage/mod.rs @@ -64,9 +64,7 @@ pub trait CoconutStorageExt { pagination: Pagination, ) -> Result, NymApiStorageError>; - async fn get_current_freepass_nonce(&self) -> Result; - - async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), NymApiStorageError>; + async fn increment_issued_freepasses(&self) -> Result<(), NymApiStorageError>; } #[async_trait] @@ -168,11 +166,7 @@ impl CoconutStorageExt for NymApiStorage { .await?) } - async fn get_current_freepass_nonce(&self) -> Result { - Ok(self.manager.get_current_freepass_nonce().await?) - } - - async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), NymApiStorageError> { - Ok(self.manager.update_and_validate_freepass_nonce(new).await?) + async fn increment_issued_freepasses(&self) -> Result<(), NymApiStorageError> { + Ok(self.manager.increment_issued_freepasses().await?) } } From 04373589b1c887374cdf1ec0457fd2e5ec165a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 17:51:19 +0000 Subject: [PATCH 45/49] added import-credential command to network requester --- Cargo.lock | 3 + .../network-requester/Cargo.toml | 4 + .../src/cli/import_credential.rs | 102 ++++++++++++++++++ .../network-requester/src/cli/mod.rs | 5 + .../network-requester/src/error.rs | 20 ++++ 5 files changed, 134 insertions(+) create mode 100644 service-providers/network-requester/src/cli/import_credential.rs diff --git a/Cargo.lock b/Cargo.lock index 55105ed993..6f88f34cb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5861,6 +5861,7 @@ dependencies = [ "nym-client-websocket-requests", "nym-config", "nym-credential-storage", + "nym-credentials", "nym-crypto", "nym-exit-policy", "nym-network-defaults", @@ -5884,9 +5885,11 @@ dependencies = [ "tap", "tempfile", "thiserror", + "time", "tokio", "tokio-tungstenite", "url", + "zeroize", ] [[package]] diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index b2486db390..c0f75f1670 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -38,6 +38,8 @@ thiserror = { workspace = true } tokio = { workspace = true, features = [ "net", "rt-multi-thread", "macros" ] } tokio-tungstenite = { workspace = true } url = { workspace = true } +time = "0.3.30" +zeroize = "1.6.0" # internal async-file-watcher = { path = "../../common/async-file-watcher" } @@ -45,6 +47,7 @@ nym-bin-common = { path = "../../common/bin-common", features = ["output_format" nym-client-core = { path = "../../common/client-core", features = ["cli"] } nym-client-websocket-requests = { path = "../../clients/native/websocket-requests" } nym-config = { path = "../../common/config" } +nym-credentials = { path = "../../common/credentials" } nym-credential-storage = { path = "../../common/credential-storage" } nym-crypto = { path = "../../common/crypto" } nym-network-defaults = { path = "../../common/network-defaults" } @@ -59,6 +62,7 @@ nym-task = { path = "../../common/task" } nym-types = { path = "../../common/types" } nym-exit-policy = { path = "../../common/exit-policy", features = ["client"] } + [dev-dependencies] tempfile = "3.5.0" anyhow = { workspace = true } diff --git a/service-providers/network-requester/src/cli/import_credential.rs b/service-providers/network-requester/src/cli/import_credential.rs new file mode 100644 index 0000000000..5f5c599c3c --- /dev/null +++ b/service-providers/network-requester/src/cli/import_credential.rs @@ -0,0 +1,102 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::cli::try_load_current_config; +use crate::error::NetworkRequesterError; +use clap::ArgGroup; +use log::{error, info}; +use nym_credential_storage::models::StorableIssuedCredential; +use nym_credential_storage::storage::Storage; +use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVariant; +use nym_credentials::IssuedBandwidthCredential; +use std::fs; +use std::path::PathBuf; +use time::OffsetDateTime; +use zeroize::Zeroizing; + +fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { + bs58::decode(raw).into_vec() +} + +#[derive(clap::Args)] +#[clap(group(ArgGroup::new("cred_data").required(true)))] +pub(crate) struct Args { + /// Id of client that is going to import the credential + #[clap(long)] + pub id: String, + + /// Explicitly provide the encoded credential data (as base58) + #[clap(long, group = "cred_data", value_parser = parse_encoded_credential_data)] + pub(crate) credential_data: Option>, + + /// Specifies the path to file containing binary credential data + #[clap(long, group = "cred_data")] + pub(crate) credential_path: Option, + + // currently hidden as there exists only a single serialization standard + #[clap(long, hide = true, default_value_t = 1)] + pub(crate) version: u8, +} + +pub(crate) async fn execute(args: Args) -> Result<(), NetworkRequesterError> { + let config = try_load_current_config(&args.id)?; + + let credentials_store = nym_credential_storage::initialise_persistent_storage( + &config.storage_paths.common_paths.credentials_database, + ) + .await; + + let raw_credential = match args.credential_data { + Some(data) => data, + None => { + // SAFETY: one of those arguments must have been set + fs::read(args.credential_path.unwrap())? + } + }; + let raw_credential = Zeroizing::new(raw_credential); + + // we're unpacking the data in order to make sure it's valid + // and to extract relevant metadata for storage purposes + let credential = match args.version { + 1 => Zeroizing::new( + IssuedBandwidthCredential::unpack_v1(&raw_credential).map_err(|source| { + NetworkRequesterError::CredentialDeserializationFailure { + storage_revision: 1, + source, + } + })?, + ), + other => panic!("unknown credential serialization version {other}"), + }; + + info!("importing {}", credential.typ()); + match credential.variant_data() { + BandwidthCredentialIssuedDataVariant::Voucher(voucher_info) => { + info!("with value of {}", voucher_info.value()) + } + BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { + info!("with expiry at {}", freepass_info.expiry_date()); + if freepass_info.expiry_date() > OffsetDateTime::now_utc() { + error!("the free pass has already expired!"); + + // technically we can import it, but the gateway will just reject it so what's the point + return Err(NetworkRequesterError::ExpiredCredentialImport { + expiration: freepass_info.expiry_date(), + }); + } + } + } + + let storable = StorableIssuedCredential { + serialization_revision: args.version, + credential_data: &raw_credential, + credential_type: credential.typ().to_string(), + epoch_id: credential + .epoch_id() + .try_into() + .expect("our epoch is has run over u32::MAX!"), + }; + + credentials_store.insert_issued_credential(storable).await?; + Ok(()) +} diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index 874f191134..cd5e84071e 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -23,6 +23,7 @@ use nym_config::OptionalSet; use std::sync::OnceLock; mod build_info; +mod import_credential; mod init; mod run; mod sign; @@ -60,6 +61,9 @@ pub(crate) enum Commands { /// Sign to prove ownership of this network requester Sign(sign::Sign), + /// Import a pre-generated credential + ImportCredential(import_credential::Args), + /// Show build information of this binary BuildInfo(build_info::BuildInfo), @@ -154,6 +158,7 @@ pub(crate) async fn execute(args: Cli) -> Result<(), NetworkRequesterError> { Commands::Init(m) => init::execute(m).await?, Commands::Run(m) => run::execute(&m).await?, Commands::Sign(m) => sign::execute(&m).await?, + Commands::ImportCredential(m) => import_credential::execute(m).await?, Commands::BuildInfo(m) => build_info::execute(m), Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), diff --git a/service-providers/network-requester/src/error.rs b/service-providers/network-requester/src/error.rs index 47d7b12bbc..ce14ed7cf9 100644 --- a/service-providers/network-requester/src/error.rs +++ b/service-providers/network-requester/src/error.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: GPL-3.0-only pub use nym_client_core::error::ClientCoreError; +use nym_credential_storage::error::StorageError; use nym_exit_policy::policy::PolicyError; use nym_socks5_requests::{RemoteAddress, Socks5RequestError}; use std::net::SocketAddr; +use time::OffsetDateTime; #[derive(thiserror::Error, Debug)] pub enum NetworkRequesterError { @@ -67,4 +69,22 @@ pub enum NetworkRequesterError { #[error("can't setup an exit policy without any upstream urls")] NoUpstreamExitPolicy, + + #[error("failed to store credential: {source}")] + CredentialStorageFailure { + #[from] + source: StorageError, + }, + + #[error( + "failed to deserialize provided credential using revision {storage_revision}: {source}" + )] + CredentialDeserializationFailure { + storage_revision: u8, + #[source] + source: nym_credentials::error::Error, + }, + + #[error("attempted to import an expired credential (it expired on {expiration})")] + ExpiredCredentialImport { expiration: OffsetDateTime }, } From 3ec2ea904f9b040c24420e1a3e2fd66d19074465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 17:55:36 +0000 Subject: [PATCH 46/49] fixed local expiration check --- clients/native/src/commands/import_credential.rs | 2 +- clients/socks5/src/commands/import_credential.rs | 2 +- common/commands/src/coconut/import_credential.rs | 2 +- .../network-requester/src/cli/import_credential.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clients/native/src/commands/import_credential.rs b/clients/native/src/commands/import_credential.rs index 9d156d32ff..11916ced72 100644 --- a/clients/native/src/commands/import_credential.rs +++ b/clients/native/src/commands/import_credential.rs @@ -76,7 +76,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), ClientError> { } BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { info!("with expiry at {}", freepass_info.expiry_date()); - if freepass_info.expiry_date() > OffsetDateTime::now_utc() { + if freepass_info.expired() { error!("the free pass has already expired!"); // technically we can import it, but the gateway will just reject it so what's the point diff --git a/clients/socks5/src/commands/import_credential.rs b/clients/socks5/src/commands/import_credential.rs index b4c050b815..a4306c1a09 100644 --- a/clients/socks5/src/commands/import_credential.rs +++ b/clients/socks5/src/commands/import_credential.rs @@ -76,7 +76,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), Socks5ClientError> { } BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { info!("with expiry at {}", freepass_info.expiry_date()); - if freepass_info.expiry_date() > OffsetDateTime::now_utc() { + if freepass_info.expired() { error!("the free pass has already expired!"); // technically we can import it, but the gateway will just reject it so what's the point diff --git a/common/commands/src/coconut/import_credential.rs b/common/commands/src/coconut/import_credential.rs index 3d90e02c42..6d5612ac53 100644 --- a/common/commands/src/coconut/import_credential.rs +++ b/common/commands/src/coconut/import_credential.rs @@ -80,7 +80,7 @@ pub async fn execute(args: Args) -> anyhow::Result<()> { } BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { info!("with expiry at {}", freepass_info.expiry_date()); - if freepass_info.expiry_date() > OffsetDateTime::now_utc() { + if freepass_info.expired() { error!("the free pass has already expired!"); // technically we can, but the gateway will just reject it so what's the point diff --git a/service-providers/network-requester/src/cli/import_credential.rs b/service-providers/network-requester/src/cli/import_credential.rs index 5f5c599c3c..a2d71d3c92 100644 --- a/service-providers/network-requester/src/cli/import_credential.rs +++ b/service-providers/network-requester/src/cli/import_credential.rs @@ -76,7 +76,7 @@ pub(crate) async fn execute(args: Args) -> Result<(), NetworkRequesterError> { } BandwidthCredentialIssuedDataVariant::FreePass(freepass_info) => { info!("with expiry at {}", freepass_info.expiry_date()); - if freepass_info.expiry_date() > OffsetDateTime::now_utc() { + if freepass_info.expired() { error!("the free pass has already expired!"); // technically we can import it, but the gateway will just reject it so what's the point From f96f74f2f118c902930ccdb6f854acb9d4cc8d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 19 Feb 2024 18:27:16 +0000 Subject: [PATCH 47/49] removed unused imports --- clients/native/src/commands/import_credential.rs | 1 - clients/socks5/src/commands/import_credential.rs | 1 - common/commands/src/coconut/import_credential.rs | 1 - service-providers/network-requester/src/cli/import_credential.rs | 1 - 4 files changed, 4 deletions(-) diff --git a/clients/native/src/commands/import_credential.rs b/clients/native/src/commands/import_credential.rs index 11916ced72..de211aef01 100644 --- a/clients/native/src/commands/import_credential.rs +++ b/clients/native/src/commands/import_credential.rs @@ -11,7 +11,6 @@ use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVa use nym_credentials::IssuedBandwidthCredential; use std::fs; use std::path::PathBuf; -use time::OffsetDateTime; use zeroize::Zeroizing; fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { diff --git a/clients/socks5/src/commands/import_credential.rs b/clients/socks5/src/commands/import_credential.rs index a4306c1a09..2ce3457015 100644 --- a/clients/socks5/src/commands/import_credential.rs +++ b/clients/socks5/src/commands/import_credential.rs @@ -11,7 +11,6 @@ use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVa use nym_credentials::IssuedBandwidthCredential; use std::fs; use std::path::PathBuf; -use time::OffsetDateTime; use zeroize::Zeroizing; fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { diff --git a/common/commands/src/coconut/import_credential.rs b/common/commands/src/coconut/import_credential.rs index 6d5612ac53..337bf9125b 100644 --- a/common/commands/src/coconut/import_credential.rs +++ b/common/commands/src/coconut/import_credential.rs @@ -13,7 +13,6 @@ use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVa use nym_credentials::IssuedBandwidthCredential; use std::fs; use std::path::PathBuf; -use time::OffsetDateTime; use zeroize::Zeroizing; fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { diff --git a/service-providers/network-requester/src/cli/import_credential.rs b/service-providers/network-requester/src/cli/import_credential.rs index a2d71d3c92..6d0b8fffb1 100644 --- a/service-providers/network-requester/src/cli/import_credential.rs +++ b/service-providers/network-requester/src/cli/import_credential.rs @@ -11,7 +11,6 @@ use nym_credentials::coconut::bandwidth::issued::BandwidthCredentialIssuedDataVa use nym_credentials::IssuedBandwidthCredential; use std::fs; use std::path::PathBuf; -use time::OffsetDateTime; use zeroize::Zeroizing; fn parse_encoded_credential_data(raw: &str) -> bs58::decode::Result> { From cae97663c14b64873ec449ca98717aba650e84ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 20 Feb 2024 11:03:44 +0000 Subject: [PATCH 48/49] additional gateway logs --- .../src/coconut/bandwidth/freepass.rs | 2 +- .../src/coconut/bandwidth/issued.rs | 2 +- .../src/coconut/bandwidth/voucher.rs | 2 +- gateway/src/node/client_handling/bandwidth.rs | 1 + .../connection_handler/authenticated.rs | 34 +++++++++++++++++-- .../websocket/connection_handler/coconut.rs | 12 +++++++ 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/common/credentials/src/coconut/bandwidth/freepass.rs b/common/credentials/src/coconut/bandwidth/freepass.rs index 5bb971c2cf..57893b259d 100644 --- a/common/credentials/src/coconut/bandwidth/freepass.rs +++ b/common/credentials/src/coconut/bandwidth/freepass.rs @@ -14,7 +14,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; pub const MAX_FREE_PASS_VALIDITY: Duration = Duration::WEEK; // 1 week -#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] pub struct FreePassIssuedData { /// the plain validity value of this credential expressed as unix timestamp #[zeroize(skip)] diff --git a/common/credentials/src/coconut/bandwidth/issued.rs b/common/credentials/src/coconut/bandwidth/issued.rs index b9d3ee02b3..486fd7c7ee 100644 --- a/common/credentials/src/coconut/bandwidth/issued.rs +++ b/common/credentials/src/coconut/bandwidth/issued.rs @@ -20,7 +20,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; pub const CURRENT_SERIALIZATION_REVISION: u8 = 1; -#[derive(Zeroize, Serialize, Deserialize)] +#[derive(Debug, Zeroize, Serialize, Deserialize)] pub enum BandwidthCredentialIssuedDataVariant { Voucher(BandwidthVoucherIssuedData), FreePass(FreePassIssuedData), diff --git a/common/credentials/src/coconut/bandwidth/voucher.rs b/common/credentials/src/coconut/bandwidth/voucher.rs index f61f5c3544..acce944fae 100644 --- a/common/credentials/src/coconut/bandwidth/voucher.rs +++ b/common/credentials/src/coconut/bandwidth/voucher.rs @@ -13,7 +13,7 @@ use nym_validator_client::nyxd::{Coin, Hash}; use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; -#[derive(Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] +#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)] pub struct BandwidthVoucherIssuedData { /// the plain value (e.g., bandwidth) encoded in this voucher // note: for legacy reasons we're only using the value of the coin and ignoring the denom diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index 47c6447a7a..10d1f50b3c 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -36,6 +36,7 @@ pub enum BandwidthError { }, } +#[derive(Debug, Copy, Clone)] pub struct Bandwidth { value: u64, } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index 1c22df93b7..6c9456224b 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -21,7 +21,7 @@ use futures::{ }; use log::*; use nym_credentials::coconut::bandwidth::{bandwidth_credential_params, CredentialType}; -use nym_credentials_interface::CoconutError; +use nym_credentials_interface::{Base58, CoconutError}; use nym_gateway_requests::models::CredentialSpendingRequest; use nym_gateway_requests::{ iv::{IVConversionError, IV}, @@ -227,15 +227,23 @@ where ) -> Result { // check if the credential hasn't been spent before let serial_number = credential.data.blinded_serial_number(); + trace!("processing credential {}", serial_number.to_bs58()); + let already_spent = self .inner .storage .contains_credential(&serial_number) .await?; if already_spent { + trace!("the credential has already been spent before"); return Err(RequestHandlingError::BandwidthCredentialAlreadySpent); } + trace!( + "attempting to obtain aggregate verification key for epoch {}", + credential.data.epoch_id + ); + let aggregated_verification_key = self .inner .coconut_verifier @@ -243,26 +251,32 @@ where .await?; if !credential.data.validate_type_attribute() { + trace!("mismatch in the type attribute"); return Err(RequestHandlingError::InvalidTypeAttribute); } let Some(bandwidth_attribute) = credential.data.get_bandwidth_attribute() else { + trace!("missing bandwidth attribute"); return Err(RequestHandlingError::MissingBandwidthAttribute); }; // this will extract token amounts out of bandwidth vouchers and validate expiry of free passes let bandwidth = Bandwidth::try_from_raw_value(bandwidth_attribute, credential.data.typ)?; + trace!("embedded bandwidth: {bandwidth:?}"); + // locally verify the credential let params = bandwidth_credential_params(); if !credential.data.verify(params, &aggregated_verification_key) { + trace!("the credential did not verify correctly"); return Err(RequestHandlingError::InvalidBandwidthCredential( - String::from("credential failed to verify on gateway"), + String::from("local credential verification has failed"), )); } let was_freepass = match credential.data.typ { CredentialType::Voucher => { + trace!("the credential is a bandwidth voucher. attempting to release the funds"); let api_clients = self .inner .coconut_verifier @@ -291,11 +305,13 @@ where // mark the credential as spent // TODO: technically this should be done under a storage transaction so that if we experience any // failures later on, it'd get reverted + trace!("storing serial number information"); self.inner .storage .insert_spent_credential(serial_number, was_freepass, self.client.address) .await?; + trace!("increasing client bandwidth"); self.increase_bandwidth(bandwidth).await?; let available_total = self.get_available_bandwidth().await?; @@ -307,6 +323,8 @@ where enc_credential: Vec, iv: Vec, ) -> Result { + debug!("handling v1 bandwidth request"); + let iv = IV::try_from_bytes(&iv)?; let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential_v1( enc_credential, @@ -329,6 +347,8 @@ where enc_credential: Vec, iv: Vec, ) -> Result { + debug!("handling v2 bandwidth request"); + let iv = IV::try_from_bytes(&iv)?; let credential = ClientControlRequest::try_from_enc_coconut_bandwidth_credential_v2( enc_credential, @@ -342,6 +362,8 @@ where async fn handle_claim_testnet_bandwidth( &mut self, ) -> Result { + debug!("handling testnet bandwidth request"); + if self.inner.only_coconut_credentials { return Err(RequestHandlingError::OnlyCoconutCredentials); } @@ -389,6 +411,7 @@ where /// /// * `bin_msg`: raw message to handle. async fn handle_binary(&self, bin_msg: Vec) -> Message { + trace!("binary request"); // this function decrypts the request and checks the MAC match BinaryRequest::try_from_encrypted_tagged_bytes(bin_msg, &self.client.shared_keys) { Err(e) => { @@ -413,6 +436,7 @@ where /// /// * `raw_request`: raw message to handle. async fn handle_text(&mut self, raw_request: String) -> Message { + trace!("text request"); match ClientControlRequest::try_from(raw_request) { Err(e) => RequestHandlingError::InvalidTextRequest(e).into_error_message(), Ok(request) => match request { @@ -465,6 +489,12 @@ where /// /// * `raw_request`: raw received websocket message. async fn handle_request(&mut self, raw_request: Message) -> Option { + // TODO: this should be added via tracing + debug!( + "handling request from {}", + self.client.address.as_base58_string() + ); + // apparently tungstenite auto-handles ping/pong/close messages so for now let's ignore // them and let's test that claim. If that's not the case, just copy code from // desktop nym-client websocket as I've manually handled everything there diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index d013f8fda7..d075e01bb8 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -112,10 +112,15 @@ impl CoconutVerifier { // the key was already in the map if let Ok(mapped) = RwLockReadGuard::try_map(guard, |clients| clients.get(&epoch_id)) { + trace!("we already had cached api clients for epoch {epoch_id}"); return Ok(mapped); } let api_clients = self.query_api_clients(epoch_id).await?; + trace!( + "obtained {} api clients for epoch {epoch_id} from the contract", + api_clients.len() + ); // EDGE CASE: // if this epoch is from the past, we can't query for its threshold @@ -131,6 +136,7 @@ impl CoconutVerifier { let mut guard = self.api_clients.write().await; guard.insert(epoch_id, api_clients); let guard = guard.downgrade(); + trace!("stored api clients for epoch {epoch_id}"); // SAFETY: // we just inserted the entry into the map while NEVER dropping the lock (only downgraded it) @@ -149,10 +155,15 @@ impl CoconutVerifier { // the key was already in the map if let Ok(mapped) = RwLockReadGuard::try_map(guard, |keys| keys.get(&epoch_id)) { + trace!("we already had cached verification key for epoch {epoch_id}"); return Ok(mapped); } let api_clients = self.api_clients(epoch_id).await?; + trace!( + "attempting to obtain verification key from {} api clients", + api_clients.len() + ); let aggregated_verification_key = nym_credentials::obtain_aggregate_verification_key(&api_clients)?; @@ -160,6 +171,7 @@ impl CoconutVerifier { let mut guard = self.master_keys.write().await; guard.insert(epoch_id, aggregated_verification_key); let guard = guard.downgrade(); + trace!("stored aggregated verification key for epoch {epoch_id}"); // SAFETY: // we just inserted the entry into the map while NEVER dropping the lock (only downgraded it) From 1d481db1792b27ba66be1102e681149a1df2cd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 20 Feb 2024 11:07:04 +0000 Subject: [PATCH 49/49] additional log for dkg address --- .../src/nyxd/contract_traits/dkg_query_client.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs index dcf6590457..c85f85c399 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs @@ -8,6 +8,7 @@ use crate::nyxd::CosmWasmClient; use async_trait::async_trait; use cosmrs::AccountId; use cosmwasm_std::Addr; +use log::trace; use nym_coconut_dkg_common::types::{ChunkIndex, NodeIndex, StateAdvanceResponse}; use serde::Deserialize; @@ -230,6 +231,7 @@ where let dkg_contract_address = &self .dkg_contract_address() .ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?; + trace!("using the following dkg contract: {dkg_contract_address}"); self.query_contract_smart(dkg_contract_address, &query) .await }