revamped BandwidthVoucher to allow for different kinds of bandwidth credentials
This commit is contained in:
Generated
+2
@@ -6486,7 +6486,9 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-validator-client",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
||||
@@ -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,
|
||||
<St as 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(),
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<C, St> {
|
||||
storage: St,
|
||||
@@ -39,42 +39,37 @@ impl<C, St: Storage> BandwidthController<C, St> {
|
||||
C: DkgQueryClient + Sync + Send,
|
||||
<St as Storage>::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>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<IssuedBandwidthCredential, BandwidthControllerError> {
|
||||
/*
|
||||
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!()
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -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<StoredIssuedCredential, Self::StorageError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
||||
self.coconut_credential_manager
|
||||
.consume_coconut_credential(id)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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,
|
||||
}
|
||||
|
||||
@@ -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<StoredIssuedCredential, Self::StorageError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn consume_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
|
||||
self.coconut_credential_manager
|
||||
.consume_coconut_credential(id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<CoconutCredential, Self::StorageError>;
|
||||
|
||||
/// Tries to retrieve one of the stored, unused credentials.
|
||||
async fn get_next_unspent_credential(
|
||||
&self,
|
||||
) -> Result<StoredIssuedCredential, Self::StorageError>;
|
||||
|
||||
/// Marks as consumed in the database the specified credential.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
@@ -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<Vec<BandwidthVoucher>> {
|
||||
pub fn unconsumed_vouchers(&self) -> Result<Vec<IssuanceBandwidthCredential>> {
|
||||
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<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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<()> {
|
||||
|
||||
@@ -126,24 +126,25 @@ pub async fn recover_credentials<C>(
|
||||
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::<u128>()?;
|
||||
|
||||
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::<u128>()?;
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,428 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<Attribute>,
|
||||
|
||||
#[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<u8> {
|
||||
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<Self, Error> {
|
||||
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::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
|
||||
.ok_or_else(scalar_err)?;
|
||||
buff.copy_from_slice(&bytes[32..2 * 32]);
|
||||
let binding_number = Option::<PrivateAttribute>::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::<Attribute>::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<Attribute> {
|
||||
&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<String> {
|
||||
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<u8> {
|
||||
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<Credential, Error> {
|
||||
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()
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<OffsetDateTime>) -> 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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<FreePassIssuanceData> for BandwidthCredentialIssuanceDataVariant {
|
||||
fn from(value: FreePassIssuanceData) -> Self {
|
||||
BandwidthCredentialIssuanceDataVariant::FreePass(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BandwidthVoucherIssuanceData> 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<B: Into<BandwidthCredentialIssuanceDataVariant>>(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<OffsetDateTime>) -> 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<String> {
|
||||
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<Option<CredentialSigningData>>,
|
||||
) -> Result<Signature, Error> {
|
||||
// if we provided signing data, do use them, otherwise generate fresh data
|
||||
let signing_data = signing_data
|
||||
.into()
|
||||
.unwrap_or_else(|| self.prepare_for_signing());
|
||||
|
||||
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<Signature, Error> {
|
||||
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<u8> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// TODO: is that actually needed?
|
||||
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<FreePassIssuedData> for BandwidthCredentialIssuedDataVariant {
|
||||
fn from(value: FreePassIssuedData) -> Self {
|
||||
BandwidthCredentialIssuedDataVariant::FreePass(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BandwidthVoucherIssuedData> 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<String> {
|
||||
vec![
|
||||
self.variant_data.public_value_plain(),
|
||||
self.typ().to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn prepare_for_spending(
|
||||
&self,
|
||||
verification_key: &VerificationKey,
|
||||
) -> Result<CredentialSpendingData, Error> {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<Parameters> = 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<Scalar>,
|
||||
|
||||
pub(crate) blind_sign_request: BlindSignRequest,
|
||||
|
||||
pub(crate) public_attributes_plain: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CredentialSpendingData {
|
||||
pub(crate) verify_credential_request: VerifyCredentialRequest,
|
||||
|
||||
pub(crate) public_attributes_plain: Vec<String>,
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<u8> {
|
||||
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<BlindedSignature, Error> {
|
||||
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<Scalar>,
|
||||
//
|
||||
// #[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<u8> {
|
||||
// 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<Self, Error> {
|
||||
// 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::<PrivateAttribute>::from(PrivateAttribute::from_bytes(&buff))
|
||||
// .ok_or_else(scalar_err)?;
|
||||
// buff.copy_from_slice(&bytes[32..2 * 32]);
|
||||
// let binding_number = Option::<PrivateAttribute>::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::<Attribute>::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<Attribute> {
|
||||
// &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<String> {
|
||||
// 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<u8> {
|
||||
// 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<Credential, Error> {
|
||||
// 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()
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub trait NymCredential {
|
||||
fn prove_credential(&self);
|
||||
|
||||
// pub attr
|
||||
// hashed
|
||||
// private
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod bandwidth;
|
||||
pub mod credential;
|
||||
pub mod utils;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<Signature, Error> {
|
||||
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<Signature, Error> {
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Theta> {
|
||||
fn try_from(bytes: &[u8]) -> Result<VerifyCredentialRequest> {
|
||||
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> {
|
||||
Theta::try_from(bytes)
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<VerifyCredentialRequest> {
|
||||
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<u8> {
|
||||
self.to_bytes()
|
||||
}
|
||||
|
||||
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
|
||||
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<Theta> {
|
||||
) -> Result<VerifyCredentialRequest> {
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn theta_from_keys_and_attributes(
|
||||
coconut_keypairs: &Vec<KeyPair>,
|
||||
indices: &[scheme::SignerIndex],
|
||||
public_attributes: &[&PublicAttribute],
|
||||
) -> Result<Theta, CoconutError> {
|
||||
) -> Result<VerifyCredentialRequest, CoconutError> {
|
||||
let serial_number = params.random_scalar();
|
||||
let binding_number = params.random_scalar();
|
||||
let private_attributes = vec![&serial_number, &binding_number];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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<Parameters> = 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<dyn LocalClient + Send + Sync>,
|
||||
|
||||
Generated
+2
@@ -3929,7 +3929,9 @@ dependencies = [
|
||||
"nym-coconut-interface",
|
||||
"nym-crypto",
|
||||
"nym-validator-client",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2023-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<Parameters> = OnceLock::new();
|
||||
BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(BandwidthVoucher::default_parameters)
|
||||
}
|
||||
|
||||
pub struct CredentialIssuanceMonitor {
|
||||
nyxd_client: NyxdClient,
|
||||
monitoring_results: MonitoringResults,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user