diff --git a/nym-api/migrations/20240209120000_free_pass_nonces.sql b/nym-api/migrations/20240209120000_free_pass_nonces.sql index da6fd73d37..441910036b 100644 --- a/nym-api/migrations/20240209120000_free_pass_nonces.sql +++ b/nym-api/migrations/20240209120000_free_pass_nonces.sql @@ -3,8 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -CREATE TABLE ISSUED_FREEPASS +CREATE TABLE issued_freepass ( id INTEGER PRIMARY KEY CHECK (id = 0), current_nonce INTEGER NOT NULL -); \ No newline at end of file +); + +INSERT INTO issued_freepass(id, current_nonce) VALUES (0,0); \ No newline at end of file diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs index 295a8b2720..0a3c3c0138 100644 --- a/nym-api/nym-api-requests/src/coconut/models.rs +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -149,7 +149,7 @@ pub struct FreePassRequest { // we need to include a nonce here to prevent replay attacks // (and not making the nym-api store the serial numbers of all issued credential) - pub used_nonce: u64, + pub used_nonce: u32, /// Signature on the nonce /// to prove the possession of the cosmos key/address @@ -163,7 +163,7 @@ impl FreePassRequest { self.cosmos_pubkey.into() } - pub fn nonce_plaintext(&self) -> [u8; 8] { + pub fn nonce_plaintext(&self) -> [u8; 4] { self.used_nonce.to_be_bytes() } diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index 9efa07b2b7..969b7dfeb2 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -45,7 +45,7 @@ pub enum CoconutError { FreePassSignatureVerificationFailure, #[error("the provided signing nonce is invalid. the current value is: {current}. got {received} instead")] - InvalidNonce { current: u64, received: u64 }, + InvalidNonce { current: u32, received: u32 }, #[error("only secp256k1 keys are supported for free pass issuance")] UnsupportedNonSecp256k1Key, diff --git a/nym-api/src/coconut/storage/manager.rs b/nym-api/src/coconut/storage/manager.rs index e40b2a4a96..17816bebd8 100644 --- a/nym-api/src/coconut/storage/manager.rs +++ b/nym-api/src/coconut/storage/manager.rs @@ -4,6 +4,7 @@ use crate::coconut::storage::models::{EpochCredentials, IssuedCredential}; use crate::support::storage::manager::StorageManager; use nym_coconut_dkg_common::types::EpochId; +use thiserror::Error; #[async_trait] pub trait CoconutStorageManagerExt { @@ -120,6 +121,17 @@ pub trait CoconutStorageManagerExt { start_after: i64, limit: u32, ) -> Result, sqlx::Error>; + + /// Attempts to retrieve the current value of the freepass nonce. + async fn get_current_freepass_nonce(&self) -> Result; + + /// Attempt to update the currently stored nonce to the provided value whilst ensuring + /// it's strictly equal the current value plus 1 + /// + /// # Arguments + /// + /// * `new`: the new value of the free pass nonce + async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), sqlx::Error>; } #[async_trait] @@ -378,4 +390,49 @@ impl CoconutStorageManagerExt for StorageManager { .fetch_all(&self.connection_pool) .await } + + /// Attempts to retrieve the current value of the freepass nonce. + async fn get_current_freepass_nonce(&self) -> Result { + sqlx::query!("SELECT current_nonce as 'current_nonce: u32' FROM issued_freepass") + .fetch_one(&self.connection_pool) + .await + .map(|row| row.current_nonce) + } + + /// Attempt to update the currently stored nonce to the provided value whilst ensuring + /// it's strictly equal the current value plus 1 + /// + /// # Arguments + /// + /// * `new`: the new value of the free pass nonce + async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), sqlx::Error> { + let mut tx = self.connection_pool.begin().await?; + + let currently_stored = + sqlx::query!("SELECT current_nonce as 'current_nonce: u32' FROM issued_freepass") + .fetch_one(&mut tx) + .await? + .current_nonce; + + if currently_stored + 1 != new { + // this is not the best error but I really don't want to be creating a new enum + return Err(sqlx::Error::Decode(Box::new(UnexpectedNonce { + current: currently_stored, + got: new, + }))); + } + + sqlx::query!("UPDATE issued_freepass SET current_nonce = ?", new) + .execute(&mut tx) + .await?; + + tx.commit().await + } +} + +#[derive(Debug, Error)] +#[error("tried to store an invalid nonce. the received value is {got} while current is {current}. expected {current} + 1")] +pub struct UnexpectedNonce { + current: u32, + got: u32, } diff --git a/nym-api/src/coconut/storage/mod.rs b/nym-api/src/coconut/storage/mod.rs index ad7a98ff05..89156ee7da 100644 --- a/nym-api/src/coconut/storage/mod.rs +++ b/nym-api/src/coconut/storage/mod.rs @@ -64,9 +64,9 @@ pub trait CoconutStorageExt { pagination: Pagination, ) -> Result, NymApiStorageError>; - async fn get_current_freepass_nonce(&self) -> Result; + async fn get_current_freepass_nonce(&self) -> Result; - async fn update_and_validate_freepass_nonce(&self, new: u64) -> Result<(), NymApiStorageError>; + async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), NymApiStorageError>; } #[async_trait] @@ -168,11 +168,11 @@ impl CoconutStorageExt for NymApiStorage { .await?) } - async fn get_current_freepass_nonce(&self) -> Result { + async fn get_current_freepass_nonce(&self) -> Result { todo!() } - async fn update_and_validate_freepass_nonce(&self, new: u64) -> Result<(), NymApiStorageError> { + async fn update_and_validate_freepass_nonce(&self, new: u32) -> Result<(), NymApiStorageError> { todo!() } }