revamped BandwidthVoucher to allow for different kinds of bandwidth credentials

This commit is contained in:
Jędrzej Stuczyński
2024-02-06 17:34:34 +00:00
parent aedff7fe30
commit 279db3cc83
35 changed files with 1323 additions and 659 deletions
Generated
+2
View File
@@ -6486,7 +6486,9 @@ dependencies = [
"nym-crypto",
"nym-validator-client",
"rand 0.7.3",
"serde",
"thiserror",
"time",
"zeroize",
]
+17 -24
View File
@@ -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(
&params,
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 }
}
}
+26 -31
View File
@@ -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>
+41
View File
@@ -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!()
}
+9 -8
View File
@@ -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)
+20 -1
View File
@@ -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)
+8 -1
View File
@@ -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
+12 -11
View File
@@ -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<()> {
+21 -20
View File
@@ -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)
}
+2
View File
@@ -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"
-428
View File
@@ -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(
&params,
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(
&params,
"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(
// // &params,
// // 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(
&params,
"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
}
+2 -1
View File
@@ -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;
+17 -69
View File
@@ -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,
+1 -2
View File
@@ -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
+2 -2
View File
@@ -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);
+3 -2
View File
@@ -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);
+1 -1
View File
@@ -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;
+17 -14
View File
@@ -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
);
}
}
+1 -1
View File
@@ -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];
+3 -3
View File
@@ -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);
}
+9 -4
View File
@@ -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(())
+2 -12
View File
@@ -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>,
+2
View File
@@ -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,
+2 -2
View File
@@ -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)