add signatures cache on API

This commit is contained in:
Simon Wicky
2024-05-07 10:20:01 +02:00
parent 5f656e69b5
commit 7ed11617d2
4 changed files with 204 additions and 26 deletions
+108 -10
View File
@@ -1,12 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use std::sync::atomic::{AtomicU64, Ordering};
use crate::coconut::error::CoconutError;
use crate::coconut::state::bandwidth_credential_params;
use nym_api_requests::coconut::models::FreePassRequest;
use nym_api_requests::coconut::BlindSignRequestBody;
use nym_coconut::{Attribute, BlindSignRequest, BlindedSignature, SecretKey};
use nym_compact_ecash::setup::{sign_coin_indices, CoinIndexSignature, Parameters};
use nym_validator_client::nyxd::error::NyxdError::AbciError;
use tokio::sync::RwLock;
// If the result is already established, the vote might be redundant and
// thus the transaction might fail
@@ -52,13 +54,109 @@ pub(crate) fn blind_sign<C: CredentialRequest>(
request: &C,
signing_key: &SecretKey,
) -> Result<BlindedSignature, CoconutError> {
let public_attributes = request.public_attributes();
let attributes_ref = public_attributes.iter().collect::<Vec<_>>();
Ok(nym_coconut::blind_sign(
bandwidth_credential_params(),
signing_key,
request.blind_sign_request(),
&attributes_ref,
)?)
pub(crate) struct CoinIndexSignatureCache {
pub(crate) epoch_id: AtomicU64,
pub(crate) signatures: RwLock<Option<Vec<CoinIndexSignature>>>,
}
impl CoinIndexSignatureCache {
pub(crate) fn new() -> Self {
CoinIndexSignatureCache {
epoch_id: AtomicU64::new(u64::MAX),
signatures: RwLock::new(None),
}
}
// if the epoch id cached is the one expected, return the cached signatures, else return None
pub(crate) async fn get_signatures(
&self,
expected_epoch_id: u64,
) -> Option<Vec<CoinIndexSignature>> {
if self.epoch_id.load(Ordering::Acquire) == expected_epoch_id {
let signatures = self.signatures.read().await;
signatures.clone()
} else {
None
}
}
// refreshes (if needed) and returns the signatures.
pub(crate) async fn refresh_signatures(
&self,
expected_epoch_id: u64,
ecash_parameters: &Parameters,
verification_key: &VerificationKeyAuth,
secret_key: &SecretKeyAuth,
) -> Vec<CoinIndexSignature> {
let mut signatures = self.signatures.write().await;
//if this fails, it means someone else updated the signatures in the meantime
// => We don't have to update them, and we know they exist
// (this check can spare us some signing)
if self.epoch_id.load(Ordering::Acquire) != expected_epoch_id {
*signatures = Some(sign_coin_indices(
ecash_parameters,
verification_key,
secret_key,
));
self.epoch_id.store(expected_epoch_id, Ordering::Release);
}
signatures.clone().unwrap() // Either we or someone else update the signatures, so they must be there
}
}
pub(crate) struct ExpirationDateSignatureCache {
pub(crate) epoch_id: AtomicU64,
pub(crate) expiration_date: AtomicU64,
pub(crate) signatures: RwLock<Option<Vec<ExpirationDateSignature>>>,
}
impl ExpirationDateSignatureCache {
pub(crate) fn new() -> Self {
ExpirationDateSignatureCache {
epoch_id: AtomicU64::new(u64::MAX),
expiration_date: AtomicU64::new(u64::MAX),
signatures: RwLock::new(None),
}
}
// if the epoch id cached and expiration_date cached are the ones expected, return the cached signatures, else return None
pub(crate) async fn get_signatures(
&self,
expected_epoch_id: u64,
expected_exp_date: u64,
) -> Option<Vec<ExpirationDateSignature>> {
if self.epoch_id.load(Ordering::Acquire) == expected_epoch_id
&& self.expiration_date.load(Ordering::Acquire) == expected_exp_date
{
let signatures = self.signatures.read().await;
signatures.clone()
} else {
None
}
}
// refreshes (if needed) and returns the signatures.
pub(crate) async fn refresh_signatures(
&self,
expected_epoch_id: u64,
expected_exp_date: u64,
secret_key: &SecretKeyAuth,
) -> Vec<ExpirationDateSignature> {
let mut signatures = self.signatures.write().await;
//if this fails, it means someone else updated the signatures in the meantime
// => We don't have to update them, and we know they exist
// (this check can spare us some signing)
if self.epoch_id.load(Ordering::Acquire) != expected_epoch_id
|| self.expiration_date.load(Ordering::Acquire) != expected_exp_date
{
*signatures = Some(sign_expiration_date(secret_key, expected_exp_date));
self.epoch_id.store(expected_epoch_id, Ordering::Release);
self.expiration_date
.store(expected_exp_date, Ordering::Release);
}
signatures.clone().unwrap() // Either we or someone else update the signatures, so they must be there
}
}
}
+1 -9
View File
@@ -29,7 +29,6 @@ pub(crate) const MINIMUM_BALANCE: u128 = 10_000000;
pub fn stage<C, D>(
client: C,
mix_denom: String,
identity_keypair: identity::KeyPair,
key_pair: KeyPair,
comm_channel: D,
@@ -39,14 +38,7 @@ where
C: LocalClient + Send + Sync + 'static,
D: APICommunicationChannel + Send + Sync + 'static,
{
let state = State::new(
client,
mix_denom,
identity_keypair,
key_pair,
comm_channel,
storage,
);
let state = State::new(client, identity_keypair, key_pair, comm_channel, storage);
AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async {
rocket.manage(state).mount(
// this format! is so ugly...
+95 -6
View File
@@ -10,7 +10,17 @@ 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::{BlindedSignature, VerificationKey};
use nym_compact_ecash::{
constants,
scheme::expiration_date_signatures::{sign_expiration_date, ExpirationDateSignature},
setup::{setup, CoinIndexSignature},
utils::BlindedSignature,
VerificationKeyAuth,
};
use super::{
error::CoconutError,
helpers::{accepted_vote_err, CoinIndexSignatureCache, ExpirationDateSignatureCache},
};
use nym_coconut_dkg_common::types::EpochId;
use nym_crypto::asymmetric::identity;
use nym_validator_client::nyxd::{AccountId, Hash, TxResponse};
@@ -24,18 +34,18 @@ pub use nym_credentials::coconut::bandwidth::bandwidth_credential_params;
pub struct State {
pub(crate) client: Arc<dyn LocalClient + Send + Sync>,
pub(crate) bandwidth_contract_admin: OnceCell<Option<AccountId>>,
pub(crate) mix_denom: String,
pub(crate) coconut_keypair: KeyPair,
pub(crate) identity_keypair: identity::KeyPair,
pub(crate) comm_channel: Arc<dyn APICommunicationChannel + Send + Sync>,
pub(crate) storage: NymApiStorage,
coin_indices_sigs_cache: Arc<CoinIndexSignatureCache>,
exp_date_sigs_cache: Arc<ExpirationDateSignatureCache>,
pub(crate) freepass_nonce: Arc<RwLock<[u8; 16]>>,
}
impl State {
pub(crate) fn new<C, D>(
client: C,
mix_denom: String,
identity_keypair: identity::KeyPair,
key_pair: KeyPair,
comm_channel: D,
@@ -54,11 +64,12 @@ impl State {
Self {
client,
bandwidth_contract_admin: OnceCell::new(),
mix_denom,
coconut_keypair: key_pair,
identity_keypair,
comm_channel,
storage,
coin_indices_sigs_cache: Arc::new(CoinIndexSignatureCache::new()),
exp_date_sigs_cache: Arc::new(ExpirationDateSignatureCache::new()),
freepass_nonce: Arc::new(RwLock::new(nonce)),
}
}
@@ -104,7 +115,7 @@ impl State {
request_body.tx_hash,
blinded_signature,
&encoded_commitments,
&request_body.public_attributes_plain,
request_body.expiration_date.try_into().unwrap(), //will fail in approx 290 billion years
);
let signature = self.identity_keypair.private_key().sign(plaintext);
@@ -120,7 +131,7 @@ impl State {
blinded_signature,
signature,
encoded_commitments,
request_body.public_attributes_plain,
request_body.expiration_date.try_into().unwrap(), //will fail in approx 290 billion years
)
.await?;
@@ -153,4 +164,82 @@ impl State {
.aggregated_verification_key(epoch_id)
.await
}
pub async fn get_coin_indices_signatures(&self) -> Result<Vec<CoinIndexSignature>> {
let current_epoch = self.client.get_current_epoch().await?;
match self
.coin_indices_sigs_cache
.get_signatures(current_epoch.epoch_id)
.await
{
Some(signatures) => Ok(signatures),
None => {
let ecash_params = setup(constants::NB_TICKETS);
let verification_key = self.verification_key(current_epoch.epoch_id).await?;
let maybe_keypair_guard = self.coconut_keypair.get().await;
let Some(keypair_guard) = maybe_keypair_guard.as_ref() else {
return Err(CoconutError::KeyPairNotDerivedYet);
};
let Some(signing_key) = keypair_guard.as_ref() else {
return Err(CoconutError::KeyPairNotDerivedYet);
};
Ok(self
.coin_indices_sigs_cache
.refresh_signatures(
current_epoch.epoch_id,
&ecash_params,
&verification_key,
&signing_key.keys.secret_key(),
)
.await)
}
}
}
pub async fn get_exp_date_signatures(&self) -> Result<Vec<ExpirationDateSignature>> {
let current_epoch = self.client.get_current_epoch().await?;
let expiration_ts = cred_exp_date_timestamp();
match self
.exp_date_sigs_cache
.get_signatures(current_epoch.epoch_id, expiration_ts)
.await
{
Some(signatures) => Ok(signatures),
None => {
let maybe_keypair_guard = self.coconut_keypair.get().await;
let Some(keypair_guard) = maybe_keypair_guard.as_ref() else {
return Err(CoconutError::KeyPairNotDerivedYet);
};
let Some(signing_key) = keypair_guard.as_ref() else {
return Err(CoconutError::KeyPairNotDerivedYet);
};
Ok(self
.exp_date_sigs_cache
.refresh_signatures(
current_epoch.epoch_id,
expiration_ts,
&signing_key.keys.secret_key(),
)
.await)
}
}
}
//this one gives the signatures for a particular day. No cache because it's only gonna be used for recovery attempt and freepasses
pub async fn get_exp_date_signatures_timestamp(
&self,
timestamp: u64,
) -> Result<Vec<ExpirationDateSignature>> {
let maybe_keypair_guard = self.coconut_keypair.get().await;
let Some(keypair_guard) = maybe_keypair_guard.as_ref() else {
return Err(CoconutError::KeyPairNotDerivedYet);
};
let Some(signing_key) = keypair_guard.as_ref() else {
return Err(CoconutError::KeyPairNotDerivedYet);
};
Ok(sign_expiration_date(
&signing_key.keys.secret_key(),
timestamp,
))
}
}
-1
View File
@@ -98,7 +98,6 @@ pub(crate) async fn setup_rocket(
let comm_channel = QueryCommunicationChannel::new(nyxd_client.clone());
rocket.attach(coconut::stage(
nyxd_client.clone(),
mix_denom,
identity_keypair,
coconut_keypair,
comm_channel,