add signatures cache on API
This commit is contained in:
+108
-10
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user