From ec248ec721628045acc08182859e9267ae596f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 26 Jan 2024 15:13:25 +0000 Subject: [PATCH] added database code for the serial number storage --- .../credential-utils/src/recovery_storage.rs | 2 +- common/credentials-interface/src/lib.rs | 6 +- .../src/coconut/bandwidth/issuance.rs | 15 ++--- common/nymcoconut/src/lib.rs | 1 + common/nymcoconut/src/scheme/double_use.rs | 49 +++++++++++--- common/nymcoconut/src/scheme/verification.rs | 41 +++++------- .../20240126120000_store_serial_numbers.sql | 11 +++ gateway/src/node/storage/bandwidth.rs | 52 +++++++++++++- gateway/src/node/storage/mod.rs | 67 +++++++++++++++++++ gateway/src/node/storage/models.rs | 14 +++- 10 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 gateway/migrations/20240126120000_store_serial_numbers.sql diff --git a/common/credential-utils/src/recovery_storage.rs b/common/credential-utils/src/recovery_storage.rs index f003b562cd..0344a3ad86 100644 --- a/common/credential-utils/src/recovery_storage.rs +++ b/common/credential-utils/src/recovery_storage.rs @@ -53,7 +53,7 @@ impl RecoveryStorage { pub fn voucher_filename(voucher: &IssuanceBandwidthCredential) -> String { let prefix = voucher.typ().to_string(); - let suffix = voucher.blinded_g1_serial_number_bs58(); + let suffix = voucher.blinded_serial_number_bs58(); format!("{prefix}-{suffix}.{DUMPED_VOUCHER_EXTENSION}") } diff --git a/common/credentials-interface/src/lib.rs b/common/credentials-interface/src/lib.rs index 7acc98499c..ed3a2ca03c 100644 --- a/common/credentials-interface/src/lib.rs +++ b/common/credentials-interface/src/lib.rs @@ -10,9 +10,9 @@ use thiserror::Error; pub use nym_coconut::{ aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar, keygen, prepare_blind_sign, prove_bandwidth_credential, verify_credential, Attribute, Base58, - BlindSignRequest, BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, - PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, VerificationKey, - VerifyCredentialRequest, + BlindSignRequest, BlindedSerialNumber, BlindedSignature, Bytable, CoconutError, KeyPair, + Parameters, PrivateAttribute, PublicAttribute, SecretKey, Signature, SignatureShare, + VerificationKey, VerifyCredentialRequest, }; pub const VOUCHER_INFO_TYPE: &str = "BandwidthVoucher"; diff --git a/common/credentials/src/coconut/bandwidth/issuance.rs b/common/credentials/src/coconut/bandwidth/issuance.rs index 5441a899fc..bc56bda0fd 100644 --- a/common/credentials/src/coconut/bandwidth/issuance.rs +++ b/common/credentials/src/coconut/bandwidth/issuance.rs @@ -9,10 +9,10 @@ use crate::coconut::bandwidth::{ }; use crate::coconut::utils::scalar_serde_helper; use crate::error::Error; -use bls12_381::G1Projective; use nym_credentials_interface::{ - aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSignature, - Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, VerificationKey, + aggregate_signature_shares, hash_to_scalar, prepare_blind_sign, Attribute, BlindedSerialNumber, + BlindedSignature, Parameters, PrivateAttribute, PublicAttribute, Signature, SignatureShare, + VerificationKey, }; use nym_crypto::asymmetric::{encryption, identity}; use nym_validator_client::nym_api::EpochId; @@ -140,15 +140,14 @@ impl IssuanceBandwidthCredential { Self::new(FreePassIssuanceData::new(expiry_date)) } - pub fn blind_serial_number_in_g1subgroup(&self) -> G1Projective { - bandwidth_credential_params().gen1() * self.serial_number + pub fn blind_serial_number(&self) -> BlindedSerialNumber { + (bandwidth_credential_params().gen2() * self.serial_number).into() } - // NOT TO BE CONFUSED WITH BLINDED SERIAL NUMBER IN CREDENTIAL ITSELF - pub fn blinded_g1_serial_number_bs58(&self) -> String { + pub fn blinded_serial_number_bs58(&self) -> String { use nym_credentials_interface::Base58; - self.blind_serial_number_in_g1subgroup().to_bs58() + self.blind_serial_number().to_bs58() } pub fn typ(&self) -> CredentialType { diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs index 4ac8245c37..e2a3138b3a 100644 --- a/common/nymcoconut/src/lib.rs +++ b/common/nymcoconut/src/lib.rs @@ -24,6 +24,7 @@ pub use scheme::setup::Parameters; pub use scheme::verification::check_vk_pairing; pub use scheme::verification::prove_bandwidth_credential; pub use scheme::verification::verify_credential; +pub use scheme::verification::BlindedSerialNumber; pub use scheme::verification::VerifyCredentialRequest; pub use scheme::BlindedSignature; pub use scheme::Signature; diff --git a/common/nymcoconut/src/scheme/double_use.rs b/common/nymcoconut/src/scheme/double_use.rs index ce3cf58d67..c8f89820b4 100644 --- a/common/nymcoconut/src/scheme/double_use.rs +++ b/common/nymcoconut/src/scheme/double_use.rs @@ -1,17 +1,46 @@ -// Copyright 2022 - Nym Technologies SA +// Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use bls12_381::G2Projective; -use group::Curve; -use std::convert::TryFrom; -use std::convert::TryInto; - use crate::error::{CoconutError, Result}; use crate::traits::{Base58, Bytable}; use crate::utils::try_deserialize_g2_projective; +use bls12_381::{G2Affine, G2Projective}; +use group::Curve; +use std::convert::TryFrom; +use std::convert::TryInto; +use std::fmt::{Debug, Formatter}; +use std::ops::Deref; -pub struct BlindedSerialNumber { - pub(crate) inner: G2Projective, +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct BlindedSerialNumber(G2Projective); + +// use custom Debug implementation to show base58 encoding (rather than raw curve elements) +impl Debug for BlindedSerialNumber { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("BlindedSerialNumber") + .field(&self.to_bs58()) + .finish() + } +} + +impl From for BlindedSerialNumber { + fn from(value: G2Projective) -> Self { + BlindedSerialNumber(value) + } +} + +impl From for BlindedSerialNumber { + fn from(value: G2Affine) -> Self { + BlindedSerialNumber(value.into()) + } +} + +impl Deref for BlindedSerialNumber { + type Target = G2Projective; + + fn deref(&self) -> &Self::Target { + &self.0 + } } impl TryFrom<&[u8]> for BlindedSerialNumber { @@ -34,13 +63,13 @@ impl TryFrom<&[u8]> for BlindedSerialNumber { ), )?; - Ok(BlindedSerialNumber { inner }) + Ok(BlindedSerialNumber(inner)) } } impl Bytable for BlindedSerialNumber { fn to_byte_vec(&self) -> Vec { - self.inner.to_affine().to_compressed().to_vec() + self.0.to_affine().to_compressed().to_vec() } fn try_from_byte_slice(slice: &[u8]) -> Result { diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index cbf36fde2f..e9957a7c73 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -1,22 +1,21 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use core::ops::Neg; -use std::convert::TryFrom; -use std::convert::TryInto; - -use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar}; -use group::{Curve, Group}; - use crate::error::{CoconutError, Result}; use crate::proofs::ProofKappaZeta; -use crate::scheme::double_use::BlindedSerialNumber; use crate::scheme::setup::Parameters; use crate::scheme::Signature; use crate::scheme::VerificationKey; use crate::traits::{Base58, Bytable}; use crate::utils::try_deserialize_g2_projective; use crate::Attribute; +use bls12_381::{multi_miller_loop, G1Affine, G2Prepared, G2Projective, Scalar}; +use core::ops::Neg; +use group::{Curve, Group}; +use std::convert::TryFrom; +use std::convert::TryInto; + +pub use crate::scheme::double_use::BlindedSerialNumber; // TODO NAMING: this whole thing // Theta @@ -25,7 +24,7 @@ pub struct VerifyCredentialRequest { // blinded_message (kappa) pub blinded_message: G2Projective, // blinded serial number (zeta) - pub blinded_serial_number: G2Projective, + pub blinded_serial_number: BlindedSerialNumber, // sigma pub credential: Signature, // pi_v @@ -53,15 +52,10 @@ impl TryFrom<&[u8]> for VerifyCredentialRequest { ), )?; - // safety: we just checked for the length so the unwraps are fine - #[allow(clippy::unwrap_used)] - let blinded_serial_number_bytes = bytes[96..192].try_into().unwrap(); - let blinded_serial_number = try_deserialize_g2_projective( - &blinded_serial_number_bytes, - CoconutError::Deserialization( - "failed to deserialize the blinded serial number (zeta)".to_string(), - ), - )?; + let blinded_serial_number_bytes = &bytes[96..192]; + let blinded_serial_number = + BlindedSerialNumber::try_from_byte_slice(blinded_serial_number_bytes)?; + let credential = Signature::try_from(&bytes[192..288])?; let pi_v = ProofKappaZeta::from_bytes(&bytes[288..])?; @@ -87,7 +81,7 @@ impl VerifyCredentialRequest { pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result { let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?; - let ret = self.blinded_serial_number.eq(&blinded_serial_number.inner); + let ret = self.blinded_serial_number.eq(&blinded_serial_number); Ok(ret) } @@ -112,10 +106,7 @@ impl VerifyCredentialRequest { } pub fn blinded_serial_number_bs58(&self) -> String { - let blinded_serial_nuumber = BlindedSerialNumber { - inner: self.blinded_serial_number, - }; - blinded_serial_nuumber.to_bs58() + self.blinded_serial_number.to_bs58() } } @@ -198,7 +189,7 @@ pub fn prove_bandwidth_credential( Ok(VerifyCredentialRequest { blinded_message, - blinded_serial_number, + blinded_serial_number: blinded_serial_number.into(), credential: signature_prime, pi_v, }) diff --git a/gateway/migrations/20240126120000_store_serial_numbers.sql b/gateway/migrations/20240126120000_store_serial_numbers.sql new file mode 100644 index 0000000000..b7e1384dad --- /dev/null +++ b/gateway/migrations/20240126120000_store_serial_numbers.sql @@ -0,0 +1,11 @@ +/* + * Copyright 2024 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE TABLE spent_credential +( + blinded_serial_number_bs58 TEXT NOT NULL PRIMARY KEY UNIQUE, + was_freepass BOOLEAN NOT NULL, + client_address_bs58 TEXT NOT NULL REFERENCES shared_keys (client_address_bs58) +); \ No newline at end of file diff --git a/gateway/src/node/storage/bandwidth.rs b/gateway/src/node/storage/bandwidth.rs index 536f9b69f0..3eb4dab057 100644 --- a/gateway/src/node/storage/bandwidth.rs +++ b/gateway/src/node/storage/bandwidth.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::node::storage::models::PersistedBandwidth; +use crate::node::storage::models::{PersistedBandwidth, SpentCredential}; #[derive(Clone)] pub(crate) struct BandwidthManager { @@ -103,4 +103,54 @@ impl BandwidthManager { .await?; Ok(()) } + + /// Mark received credential as spent and insert it into the storage. + /// + /// # Arguments + /// + /// * `blinded_serial_number_bs58`: the unique blinded serial number embedded in the credential + /// * `was_freepass`: indicates whether the spent credential was a freepass + /// * `client_address_bs58`: address of the client that spent the credential + pub(crate) async fn insert_spent_credential( + &self, + blinded_serial_number_bs58: &str, + was_freepass: bool, + client_address_bs58: &str, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + r#" + INSERT INTO spent_credential + (blinded_serial_number_bs58, was_freepass, client_address_bs58) + VALUES (?, ?, ?) + "#, + blinded_serial_number_bs58, + was_freepass, + client_address_bs58 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + /// Retrieve the spent credential with the provided blinded serial number from the storage. + /// + /// # Arguments + /// + /// * `blinded_serial_number_bs58`: the unique blinded serial number embedded in the credential + pub(crate) async fn retrieve_spent_credential( + &self, + blinded_serial_number_bs58: &str, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + SpentCredential, + r#" + SELECT * FROM spent_credential + WHERE blinded_serial_number_bs58 = ? + LIMIT 1 + "#, + blinded_serial_number_bs58, + ) + .fetch_optional(&self.connection_pool) + .await + } } diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs index 77f87437ea..bc6bbb76f9 100644 --- a/gateway/src/node/storage/mod.rs +++ b/gateway/src/node/storage/mod.rs @@ -8,6 +8,7 @@ use crate::node::storage::models::{PersistedSharedKeys, StoredMessage}; use crate::node::storage::shared_keys::SharedKeysManager; use async_trait::async_trait; use log::{debug, error}; +use nym_credentials_interface::{Base58, BlindedSerialNumber}; use nym_gateway_requests::registration::handshake::SharedKeys; use nym_sphinx::DestinationAddressBytes; use sqlx::ConnectOptions; @@ -134,6 +135,29 @@ pub(crate) trait Storage: Send + Sync { client_address: DestinationAddressBytes, amount: i64, ) -> Result<(), StorageError>; + + /// Mark received credential as spent and insert it into the storage. + /// + /// # Arguments + /// + /// * `blinded_serial_number`: the unique blinded serial number embedded in the credential + /// * `client_address`: address of the client that spent the credential + async fn insert_spent_credential( + &self, + blinded_serial_number: BlindedSerialNumber, + was_freepass: bool, + client_address: DestinationAddressBytes, + ) -> Result<(), StorageError>; + + /// Check if the credential with the provided blinded serial number if already present in the storage. + /// + /// # Arguments + /// + /// * `blinded_serial_number`: the unique blinded serial number embedded in the credential + async fn contains_credential( + &self, + blinded_serial_number: &BlindedSerialNumber, + ) -> Result; } // note that clone here is fine as upon cloning the same underlying pool will be used @@ -304,6 +328,34 @@ impl Storage for PersistentStorage { .await?; Ok(()) } + + async fn insert_spent_credential( + &self, + blinded_serial_number: BlindedSerialNumber, + was_freepass: bool, + client_address: DestinationAddressBytes, + ) -> Result<(), StorageError> { + self.bandwidth_manager + .insert_spent_credential( + &blinded_serial_number.to_bs58(), + was_freepass, + &client_address.as_base58_string(), + ) + .await?; + Ok(()) + } + + async fn contains_credential( + &self, + blinded_serial_number: &BlindedSerialNumber, + ) -> Result { + let cred = self + .bandwidth_manager + .retrieve_spent_credential(&blinded_serial_number.to_bs58()) + .await?; + + Ok(cred.is_some()) + } } /// In-memory implementation of `Storage`. The intention is primarily in testing environments. @@ -393,4 +445,19 @@ impl Storage for InMemStorage { ) -> Result<(), StorageError> { todo!() } + + async fn insert_spent_credential( + &self, + blinded_serial_number: BlindedSerialNumber, + client_address: DestinationAddressBytes, + ) -> Result<(), StorageError> { + todo!() + } + + async fn contains_credential( + &self, + blinded_serial_number: &BlindedSerialNumber, + ) -> Result { + todo!() + } } diff --git a/gateway/src/node/storage/models.rs b/gateway/src/node/storage/models.rs index c98fb4c26b..2bef3b7265 100644 --- a/gateway/src/node/storage/models.rs +++ b/gateway/src/node/storage/models.rs @@ -1,6 +1,8 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use sqlx::FromRow; + pub(crate) struct PersistedSharedKeys { pub(crate) client_address_bs58: String, pub(crate) derived_aes128_ctr_blake3_hmac_keys_bs58: String, @@ -18,3 +20,13 @@ pub(crate) struct PersistedBandwidth { pub(crate) client_address_bs58: String, pub(crate) available: i64, } + +#[derive(Debug, Clone, FromRow)] +pub(crate) struct SpentCredential { + #[allow(dead_code)] + pub(crate) blinded_serial_number_bs58: String, + #[allow(dead_code)] + pub(crate) was_freepass: bool, + #[allow(dead_code)] + pub(crate) client_address_bs58: String, +}