diff --git a/nym-api/migrations/20240409120000_add_credential_table.sql b/nym-api/migrations/20240409120000_add_credential_table.sql new file mode 100644 index 0000000000..45598f711b --- /dev/null +++ b/nym-api/migrations/20240409120000_add_credential_table.sql @@ -0,0 +1,14 @@ +/* + * Copyright 2023 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +create table spent_credentials +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + credential TEXT NOT NULL, + serial_number TEXT NOT NULL, + gateway_address TEXT NOT NULL, + proposal_id TEXT NOT NULL + +); diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs index 053fccfba6..1daf39c49d 100644 --- a/nym-api/src/coconut/state.rs +++ b/nym-api/src/coconut/state.rs @@ -169,6 +169,34 @@ impl State { .aggregated_verification_key(epoch_id) .await } + + pub async fn store_credential( + &self, + credential: &CredentialSpendingData, + gateway_addr: &AccountId, + proposal_id: u64, + ) -> Result<()> { + self.storage + .insert_credential( + credential, + credential.serial_number_b58(), + gateway_addr, + proposal_id, + ) + .await + .map_err(|err| err.into()) + } + + pub async fn get_credential_by_sn( + &self, + serial_number_bs58: String, + ) -> Result> { + self.storage + .get_credential(serial_number_bs58) + .await + .map_err(|err| err.into()) + } + pub async fn get_coin_indices_signatures(&self) -> Result> { let current_epoch = self.client.get_current_epoch().await?; match self diff --git a/nym-api/src/coconut/storage/manager.rs b/nym-api/src/coconut/storage/manager.rs index 2454829015..65ccc75af0 100644 --- a/nym-api/src/coconut/storage/manager.rs +++ b/nym-api/src/coconut/storage/manager.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use crate::coconut::storage::models::{EpochCredentials, IssuedCredential}; +use crate::coconut::storage::models::{EpochCredentials, IssuedCredential, SpentCredential}; use crate::support::storage::manager::StorageManager; use nym_coconut_dkg_common::types::EpochId; use thiserror::Error; @@ -94,6 +94,25 @@ pub trait CoconutStorageManagerExt { limit: u32, ) -> Result, sqlx::Error>; + /// Creates new credential entry for a given gateway addr. + /// + /// # Arguments + /// + /// * `credential`: base58 repr of a credential. + /// * `gateway_addr`: cosmos address of the gateway + async fn insert_credential( + &self, + credential: String, + serial_number: String, + gateway_addr: String, + proposal_id: String, + ) -> Result<(), sqlx::Error>; + + async fn get_credential( + &self, + serial_number: String, + ) -> Result, sqlx::Error>; + async fn increment_issued_freepasses(&self) -> Result<(), sqlx::Error>; } @@ -354,6 +373,44 @@ impl CoconutStorageManagerExt for StorageManager { .await } + /// Creates new credential entry for a given gateway addr. + /// + /// # Arguments + /// + /// * `credential`: base58 repr of a credential. + /// * `gateway_addr`: cosmos address of the gateway + async fn insert_credential( + &self, + credential: String, + serial_number: String, + gateway_addr: String, + proposal_id: String, + ) -> Result<(), sqlx::Error> { + sqlx::query!( + "INSERT INTO spent_credentials(credential, serial_number, gateway_address, proposal_id) VALUES (?, ?, ?, ?)", + credential, + serial_number, + gateway_addr, + proposal_id, + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + async fn get_credential( + &self, + serial_number: String, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + SpentCredential, + "SELECT credential from spent_credentials where serial_number = ?", + serial_number, + ) + .fetch_optional(&self.connection_pool) + .await + } + async fn increment_issued_freepasses(&self) -> Result<(), sqlx::Error> { sqlx::query!("UPDATE issued_freepass SET issued = issued + 1",) .execute(&self.connection_pool) diff --git a/nym-api/src/coconut/storage/mod.rs b/nym-api/src/coconut/storage/mod.rs index bde37ed2e4..2e048a479c 100644 --- a/nym-api/src/coconut/storage/mod.rs +++ b/nym-api/src/coconut/storage/mod.rs @@ -6,12 +6,12 @@ use crate::coconut::storage::models::{join_attributes, EpochCredentials, IssuedC use crate::node_status_api::models::NymApiStorageError; use crate::support::storage::NymApiStorage; use nym_api_requests::coconut::models::Pagination; -use nym_coconut::{Base58, BlindedSignature}; use nym_coconut_dkg_common::types::EpochId; use nym_compact_ecash::utils::BlindedSignature; use nym_compact_ecash::Base58; +use nym_credentials::CredentialSpendingData; use nym_crypto::asymmetric::identity; -use nym_validator_client::nyxd::Hash; +use nym_validator_client::nyxd::{AccountId, Hash}; pub(crate) mod manager; pub(crate) mod models; @@ -67,6 +67,19 @@ pub trait CoconutStorageExt { pagination: Pagination, ) -> Result, NymApiStorageError>; + async fn insert_credential( + &self, + credential: &CredentialSpendingData, + serial_number_bs58: String, + gateway_addr: &AccountId, + proposal_id: u64, + ) -> Result<(), NymApiStorageError>; + + async fn get_credential( + &self, + serial_number_bs58: String, + ) -> Result, NymApiStorageError>; + async fn increment_issued_freepasses(&self) -> Result<(), NymApiStorageError>; } @@ -169,6 +182,40 @@ impl CoconutStorageExt for NymApiStorage { .await?) } + async fn insert_credential( + &self, + credential: &CredentialSpendingData, + serial_number_bs58: String, + gateway_addr: &AccountId, + proposal_id: u64, + ) -> Result<(), NymApiStorageError> { + self.manager + .insert_credential( + credential.to_bs58(), + serial_number_bs58, + gateway_addr.to_string(), + proposal_id.to_string(), + ) + .await + .map_err(|err| err.into()) + } + + async fn get_credential( + &self, + serial_number_bs58: String, + ) -> Result, NymApiStorageError> { + let credential = self.manager.get_credential(serial_number_bs58).await?; + credential + .map(|cred| { + CredentialSpendingData::try_from_bs58(cred.credential).map_err(|_| { + NymApiStorageError::DatabaseInconsistency { + reason: "impossible to deserialize credential".to_string(), + } + }) + }) + .transpose() + } + async fn increment_issued_freepasses(&self) -> Result<(), NymApiStorageError> { Ok(self.manager.increment_issued_freepasses().await?) } diff --git a/nym-api/src/coconut/storage/models.rs b/nym-api/src/coconut/storage/models.rs index 33248d48ae..ac149487e8 100644 --- a/nym-api/src/coconut/storage/models.rs +++ b/nym-api/src/coconut/storage/models.rs @@ -33,6 +33,10 @@ impl From for EpochCredentialsResponse { } } +pub struct SpentCredential { + pub credential: String, +} + #[derive(FromRow)] pub struct IssuedCredential { pub id: i64,