Compare commits

...

6 Commits

Author SHA1 Message Date
benedettadavico 3eb862790c cargo fmt 2024-02-08 15:52:38 +01:00
benedetta davico 7bce29e9ac Merge branch 'develop' into feature/local-gateway-credential-verification 2024-02-08 15:39:47 +01:00
Jędrzej Stuczyński b9195ae40d clippy 2024-02-07 11:59:02 +00:00
Jędrzej Stuczyński 825a138cf3 clippy 2024-02-07 11:59:02 +00:00
Jędrzej Stuczyński ecf85049c6 locally marking credentials as spent 2024-02-07 11:59:02 +00:00
Jędrzej Stuczyński 0890407886 added database code for the serial number storage 2024-02-07 11:59:02 +00:00
12 changed files with 256 additions and 56 deletions
+8 -3
View File
@@ -13,8 +13,9 @@ use error::CoconutInterfaceError;
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,
BlindedSerialNumber, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters,
PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, Theta,
VerificationKey,
};
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)]
@@ -49,7 +50,11 @@ impl Credential {
}
}
pub fn blinded_serial_number(&self) -> String {
pub fn blinded_serial_number(&self) -> &BlindedSerialNumber {
&self.theta.blinded_serial_number
}
pub fn blinded_serial_number_bs58(&self) -> String {
self.theta.blinded_serial_number_bs58()
}
+1
View File
@@ -23,6 +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::BlindedSerialNumber;
pub use scheme::verification::Theta;
pub use scheme::BlindedSignature;
pub use scheme::Signature;
+39 -10
View File
@@ -1,17 +1,46 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::G2Projective;
use group::Curve;
use std::convert::TryFrom;
use std::convert::TryInto;
use crate::error::{CoconutError, Result};
use crate::traits::{Base58, Bytable};
use crate::utils::try_deserialize_g2_projective;
use bls12_381::{G2Affine, G2Projective};
use group::Curve;
use std::convert::TryFrom;
use std::convert::TryInto;
use std::fmt::{Debug, Formatter};
use std::ops::Deref;
pub struct BlindedSerialNumber {
pub(crate) inner: G2Projective,
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct BlindedSerialNumber(G2Projective);
// use custom Debug implementation to show base58 encoding (rather than raw curve elements)
impl Debug for BlindedSerialNumber {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("BlindedSerialNumber")
.field(&self.to_bs58())
.finish()
}
}
impl From<G2Projective> for BlindedSerialNumber {
fn from(value: G2Projective) -> Self {
BlindedSerialNumber(value)
}
}
impl From<G2Affine> for BlindedSerialNumber {
fn from(value: G2Affine) -> Self {
BlindedSerialNumber(value.into())
}
}
impl Deref for BlindedSerialNumber {
type Target = G2Projective;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl TryFrom<&[u8]> for BlindedSerialNumber {
@@ -34,13 +63,13 @@ impl TryFrom<&[u8]> for BlindedSerialNumber {
),
)?;
Ok(BlindedSerialNumber { inner })
Ok(BlindedSerialNumber(inner))
}
}
impl Bytable for BlindedSerialNumber {
fn to_byte_vec(&self) -> Vec<u8> {
self.inner.to_affine().to_compressed().to_vec()
self.0.to_affine().to_compressed().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
+16 -25
View File
@@ -1,22 +1,21 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use core::ops::Neg;
use std::convert::TryFrom;
use std::convert::TryInto;
use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar};
use group::{Curve, Group};
use crate::error::{CoconutError, Result};
use crate::proofs::ProofKappaZeta;
use crate::scheme::double_use::BlindedSerialNumber;
use crate::scheme::setup::Parameters;
use crate::scheme::Signature;
use crate::scheme::VerificationKey;
use crate::traits::{Base58, Bytable};
use crate::utils::try_deserialize_g2_projective;
use crate::Attribute;
use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar};
use core::ops::Neg;
use group::{Curve, Group};
use std::convert::TryFrom;
use std::convert::TryInto;
pub use crate::scheme::double_use::BlindedSerialNumber;
// TODO NAMING: this whole thing
// Theta
@@ -25,7 +24,7 @@ pub struct Theta {
// blinded_message (kappa)
pub blinded_message: G2Projective,
// blinded serial number (zeta)
pub blinded_serial_number: G2Projective,
pub blinded_serial_number: BlindedSerialNumber,
// sigma
pub credential: Signature,
// pi_v
@@ -53,15 +52,10 @@ impl TryFrom<&[u8]> for Theta {
),
)?;
// safety: we just checked for the length so the unwraps are fine
#[allow(clippy::unwrap_used)]
let blinded_serial_number_bytes = bytes[96..192].try_into().unwrap();
let blinded_serial_number = try_deserialize_g2_projective(
&blinded_serial_number_bytes,
CoconutError::Deserialization(
"failed to deserialize the blinded serial number (zeta)".to_string(),
),
)?;
let blinded_serial_number_bytes = &bytes[96..192];
let blinded_serial_number =
BlindedSerialNumber::try_from_byte_slice(blinded_serial_number_bytes)?;
let credential = Signature::try_from(&bytes[192..288])?;
let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?;
@@ -87,7 +81,7 @@ impl Theta {
pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result<bool> {
let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?;
let ret = self.blinded_serial_number.eq(&blinded_serial_number.inner);
let ret = self.blinded_serial_number.eq(&blinded_serial_number);
Ok(ret)
}
@@ -112,10 +106,7 @@ impl Theta {
}
pub fn blinded_serial_number_bs58(&self) -> String {
let blinded_serial_nuumber = BlindedSerialNumber {
inner: self.blinded_serial_number,
};
blinded_serial_nuumber.to_bs58()
self.blinded_serial_number.to_bs58()
}
}
@@ -198,7 +189,7 @@ pub fn prove_bandwidth_credential(
Ok(Theta {
blinded_message,
blinded_serial_number,
blinded_serial_number: blinded_serial_number.into(),
credential: signature_prime,
pi_v,
})
@@ -0,0 +1,10 @@
/*
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
CREATE TABLE spent_credential
(
blinded_serial_number_bs58 TEXT NOT NULL PRIMARY KEY UNIQUE,
client_address_bs58 TEXT NOT NULL REFERENCES shared_keys(client_address_bs58)
);
@@ -19,8 +19,10 @@ use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
use std::cmp::max;
use std::{convert::TryFrom, process, time::Duration};
use crate::node::client_handling::websocket::connection_handler::coconut::BANDWIDTH_PER_CREDENTIAL;
use crate::node::{
client_handling::{
bandwidth::Bandwidth,
@@ -58,6 +60,9 @@ pub(crate) enum RequestHandlingError {
#[error("Provided bandwidth credential did not verify correctly on {0}")]
InvalidBandwidthCredential(String),
#[error("the provided bandwidth credential has already been spent before at this gateway")]
BandwidthCredentialAlreadySpent,
#[error("This gateway is only accepting coconut credentials for bandwidth")]
OnlyCoconutCredentials,
@@ -229,6 +234,17 @@ where
iv,
)?;
// check if the credential hasn't been spent before
let already_spent = self
.inner
.storage
.contains_credential(credential.blinded_serial_number())
.await?;
if already_spent {
return Err(RequestHandlingError::BandwidthCredentialAlreadySpent);
}
// locally verify the credential
let aggregated_verification_key = self
.inner
.coconut_verifier
@@ -241,19 +257,36 @@ where
));
}
let api_clients = self
.inner
.coconut_verifier
.api_clients(*credential.epoch_id())
// technically this is not atomic, i.e. checking for the spending and then marking as spent,
// but because we have the `UNIQUE` constraint on the database table
// if somebody attempts to spend the same credential in another, parallel request,
// one of them will fail
//
// mark the credential as spent
// TODO: technically this should be done under a storage transaction so that if we experience any
// failures later on, it'd get reverted
self.inner
.storage
.insert_spent_credential(*credential.blinded_serial_number(), self.client.address)
.await?;
self.inner
.coconut_verifier
.release_funds(&api_clients, &credential)
.await?;
// OLD CODE FOR RELEASING FUNDS
// let api_clients = self
// .inner
// .coconut_verifier
// .api_clients(*credential.epoch_id())
// .await?;
//
// self.inner
// .coconut_verifier
// .release_funds(&api_clients, &credential)
// .await?;
let bandwidth = Bandwidth::from(credential);
let bandwidth_value = bandwidth.value();
// if somebody decided to use a credential with bunch of tokens in it, sure, grant them that bandwidth
// otherwise use the default value
let bandwidth_value = max(bandwidth.value(), BANDWIDTH_PER_CREDENTIAL);
if bandwidth_value > i64::MAX as u64 {
// note that this would have represented more than 1 exabyte,
@@ -20,7 +20,10 @@ use std::collections::HashMap;
use std::ops::Deref;
use tokio::sync::{RwLock, RwLockReadGuard};
pub(crate) const BANDWIDTH_PER_CREDENTIAL: u64 = 1024 * 1024 * 1024; // 1GB
pub(crate) struct CoconutVerifier {
#[allow(dead_code)]
address: AccountId,
nyxd_client: RwLock<DirectSigningHttpRpcNyxdClient>,
@@ -29,6 +32,8 @@ pub(crate) struct CoconutVerifier {
// keys never change during epochs
master_keys: RwLock<HashMap<EpochId, VerificationKey>>,
#[allow(dead_code)]
mix_denom_base: String,
}
@@ -151,6 +156,7 @@ impl CoconutVerifier {
Ok(all_coconut_api_clients(self.nyxd_client.read().await.deref(), epoch_id).await?)
}
#[allow(dead_code)]
pub async fn release_funds(
&self,
api_clients: &[CoconutApiClient],
@@ -165,7 +171,7 @@ impl CoconutVerifier {
credential.voucher_value().into(),
self.mix_denom_base.clone(),
),
credential.blinded_serial_number(),
credential.blinded_serial_number_bs58(),
self.address.to_string(),
None,
)
+48 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::storage::models::PersistedBandwidth;
use crate::node::storage::models::{PersistedBandwidth, SpentCredential};
#[derive(Clone)]
pub(crate) struct BandwidthManager {
@@ -103,4 +103,51 @@ impl BandwidthManager {
.await?;
Ok(())
}
/// Mark received credential as spent and insert it into the storage.
///
/// # Arguments
///
/// * `blinded_serial_number_bs58`: the unique blinded serial number embedded in the credential
/// * `client_address_bs58`: address of the client that spent the credential
pub(crate) async fn insert_spent_credential(
&self,
blinded_serial_number_bs58: &str,
client_address_bs58: &str,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO spent_credential
(blinded_serial_number_bs58, client_address_bs58)
VALUES (?, ?)
"#,
blinded_serial_number_bs58,
client_address_bs58
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Retrieve the spent credential with the provided blinded serial number from the storage.
///
/// # Arguments
///
/// * `blinded_serial_number_bs58`: the unique blinded serial number embedded in the credential
pub(crate) async fn retrieve_spent_credential(
&self,
blinded_serial_number_bs58: &str,
) -> Result<Option<SpentCredential>, sqlx::Error> {
sqlx::query_as!(
SpentCredential,
r#"
SELECT * FROM spent_credential
WHERE blinded_serial_number_bs58 = ?
LIMIT 1
"#,
blinded_serial_number_bs58,
)
.fetch_optional(&self.connection_pool)
.await
}
}
+64
View File
@@ -8,6 +8,7 @@ use crate::node::storage::models::{PersistedSharedKeys, StoredMessage};
use crate::node::storage::shared_keys::SharedKeysManager;
use async_trait::async_trait;
use log::{debug, error};
use nym_coconut_interface::{Base58, BlindedSerialNumber};
use nym_gateway_requests::registration::handshake::SharedKeys;
use nym_sphinx::DestinationAddressBytes;
use sqlx::ConnectOptions;
@@ -134,6 +135,28 @@ pub(crate) trait Storage: Send + Sync {
client_address: DestinationAddressBytes,
amount: i64,
) -> Result<(), StorageError>;
/// Mark received credential as spent and insert it into the storage.
///
/// # Arguments
///
/// * `blinded_serial_number`: the unique blinded serial number embedded in the credential
/// * `client_address`: address of the client that spent the credential
async fn insert_spent_credential(
&self,
blinded_serial_number: BlindedSerialNumber,
client_address: DestinationAddressBytes,
) -> Result<(), StorageError>;
/// Check if the credential with the provided blinded serial number if already present in the storage.
///
/// # Arguments
///
/// * `blinded_serial_number`: the unique blinded serial number embedded in the credential
async fn contains_credential(
&self,
blinded_serial_number: &BlindedSerialNumber,
) -> Result<bool, StorageError>;
}
// note that clone here is fine as upon cloning the same underlying pool will be used
@@ -304,6 +327,32 @@ impl Storage for PersistentStorage {
.await?;
Ok(())
}
async fn insert_spent_credential(
&self,
blinded_serial_number: BlindedSerialNumber,
client_address: DestinationAddressBytes,
) -> Result<(), StorageError> {
self.bandwidth_manager
.insert_spent_credential(
&blinded_serial_number.to_bs58(),
&client_address.as_base58_string(),
)
.await?;
Ok(())
}
async fn contains_credential(
&self,
blinded_serial_number: &BlindedSerialNumber,
) -> Result<bool, StorageError> {
let cred = self
.bandwidth_manager
.retrieve_spent_credential(&blinded_serial_number.to_bs58())
.await?;
Ok(cred.is_some())
}
}
/// In-memory implementation of `Storage`. The intention is primarily in testing environments.
@@ -393,4 +442,19 @@ impl Storage for InMemStorage {
) -> Result<(), StorageError> {
todo!()
}
async fn insert_spent_credential(
&self,
_blinded_serial_number: BlindedSerialNumber,
_client_address: DestinationAddressBytes,
) -> Result<(), StorageError> {
todo!()
}
async fn contains_credential(
&self,
_blinded_serial_number: &BlindedSerialNumber,
) -> Result<bool, StorageError> {
todo!()
}
}
+11 -1
View File
@@ -1,6 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use sqlx::FromRow;
pub(crate) struct PersistedSharedKeys {
pub(crate) client_address_bs58: String,
pub(crate) derived_aes128_ctr_blake3_hmac_keys_bs58: String,
@@ -18,3 +20,11 @@ pub(crate) struct PersistedBandwidth {
pub(crate) client_address_bs58: String,
pub(crate) available: i64,
}
#[derive(Debug, Clone, FromRow)]
pub(crate) struct SpentCredential {
#[allow(dead_code)]
pub(crate) blinded_serial_number_bs58: String,
#[allow(dead_code)]
pub(crate) client_address_bs58: String,
}
+5 -1
View File
@@ -113,7 +113,11 @@ pub async fn verify_bandwidth_credential(
// Credential has not been spent before, and is on its way of being spent
let credential_status = state
.client
.get_spent_credential(verify_credential_body.credential.blinded_serial_number())
.get_spent_credential(
verify_credential_body
.credential
.blinded_serial_number_bs58(),
)
.await?
.spend_credential
.ok_or(CoconutError::InvalidCredentialStatus {
+5 -5
View File
@@ -1787,7 +1787,7 @@ mod credential_tests {
);
// Test the endpoint with no msg in the proposal action
proposal.description = credential.blinded_serial_number();
proposal.description = credential.blinded_serial_number_bs58();
chain
.lock()
.unwrap()
@@ -1851,7 +1851,7 @@ mod credential_tests {
.bandwidth_contract
.spent_credentials
.insert(
credential.blinded_serial_number(),
credential.blinded_serial_number_bs58(),
SpendCredentialResponse::new(None),
);
@@ -1875,7 +1875,7 @@ mod credential_tests {
// Test the endpoint with a credential that doesn't verify correctly
let mut spent_credential = SpendCredential::new(
funds.clone().into(),
credential.blinded_serial_number(),
credential.blinded_serial_number_bs58(),
Addr::unchecked("unimportant"),
);
chain
@@ -1884,7 +1884,7 @@ mod credential_tests {
.bandwidth_contract
.spent_credentials
.insert(
credential.blinded_serial_number(),
credential.blinded_serial_number_bs58(),
SpendCredentialResponse::new(Some(spent_credential.clone())),
);
let bad_credential = Credential::new(
@@ -2019,7 +2019,7 @@ mod credential_tests {
.bandwidth_contract
.spent_credentials
.insert(
credential.blinded_serial_number(),
credential.blinded_serial_number_bs58(),
SpendCredentialResponse::new(Some(spent_credential)),
);
let response = client