diff --git a/Cargo.lock b/Cargo.lock index 0784362ddd..c5d2737dbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6486,7 +6486,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 c937d48fc3..7598f73df2 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 d1249c0e25..12a575a647 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3929,7 +3929,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)