change API routes for new blind signing

This commit is contained in:
Simon Wicky
2024-05-07 10:29:06 +02:00
parent 7ed11617d2
commit fb340c4028
7 changed files with 159 additions and 96 deletions
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::BlindedSignature;
use nym_compact_ecash::utils::BlindedSignature;
use tendermint::hash::Hash;
// recomputes plaintext on the credential nym-api has used for signing
@@ -12,7 +12,7 @@ pub fn issued_credential_plaintext(
tx_hash: Hash,
blinded_partial_credential: &BlindedSignature,
bs58_encoded_private_attributes_commitments: &[String],
public_attributes: &[String],
expiration_date: i64,
) -> Vec<u8> {
epoch_id
.to_be_bytes()
@@ -24,10 +24,6 @@ pub fn issued_credential_plaintext(
.iter()
.flat_map(|attr| attr.as_bytes().iter().copied()),
)
.chain(
public_attributes
.iter()
.flat_map(|attr| attr.as_bytes().iter().copied()),
)
.chain(expiration_date.to_be_bytes())
.collect()
}
+58 -16
View File
@@ -12,15 +12,13 @@ use nym_api_requests::coconut::models::{
IssuedCredentialResponse, IssuedCredentialsResponse,
};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
use nym_coconut_bandwidth_contract_common::spend_credential::{
funds_from_cosmos_msgs, SpendCredentialStatus,
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
PartialExpirationDateSignatureResponse, VerifyEcashCredentialBody,
};
use nym_coconut_dkg_common::types::EpochId;
use nym_credentials::coconut::bandwidth::freepass::MAX_FREE_PASS_VALIDITY;
use nym_credentials::coconut::bandwidth::{
bandwidth_credential_params, CredentialType, IssuanceBandwidthCredential,
use nym_compact_ecash::error::CompactEcashError;
use nym_credentials::coconut::utils::{
cred_exp_date_timestamp, freepass_exp_date_timestamp, today_timestamp,
};
use nym_validator_client::nyxd::Coin;
use rand::rngs::OsRng;
@@ -185,14 +183,6 @@ pub async fn post_blind_sign(
debug!("Received blind sign request");
trace!("body: {:?}", blind_sign_request_body);
// early check: does the request have the expected number of public attributes?
debug!("performing basic request validation");
if blind_sign_request_body.public_attributes_plain.len()
!= IssuanceBandwidthCredential::PUBLIC_ATTRIBUTES as usize
{
return Err(CoconutError::InconsistentPublicAttributes);
}
// check if we already issued a credential for this tx hash
debug!(
"checking if we have already issued credential for this tx_hash (hash: {})",
@@ -215,12 +205,32 @@ pub async fn post_blind_sign(
return Err(CoconutError::KeyPairNotDerivedYet);
};
//check if account was blacklisted
let pub_key_bs58 = blind_sign_request_body.ecash_pubkey.to_base58_string();
let blacklist_response = state
.client
.get_blacklisted_account(pub_key_bs58.clone())
.await?;
if let Some(account) = blacklist_response.account {
if account.public_key() == pub_key_bs58 {
//Theoretically useless check
return Err(CoconutError::BlacklistedAccount);
}
}
// get the transaction details of the claimed deposit
debug!("getting transaction details from the chain");
let tx = state
.get_transaction(blind_sign_request_body.tx_hash)
.await?;
//check expiration date validity
if blind_sign_request_body.expiration_date > cred_exp_date_timestamp() {
return Err(
CompactEcashError::ExpirationDate("Invalid expiration date".to_string()).into(),
);
}
// check validity of the request
debug!("fully validating received request");
state.validate_request(&blind_sign_request_body, tx).await?;
@@ -229,7 +239,7 @@ pub async fn post_blind_sign(
debug!("producing the partial credential");
let blinded_signature = blind_sign(
blind_sign_request_body.deref(),
signing_key.keys.secret_key(),
&signing_key.keys.secret_key(),
)?;
// store the information locally
@@ -371,3 +381,35 @@ pub async fn issued_credentials(
build_credentials_response(credentials).map(Json)
}
#[get("/expiration-date-signatures")]
pub async fn expiration_date_signatures(
state: &RocketState<State>,
) -> Result<Json<PartialExpirationDateSignatureResponse>> {
let expiration_date_signatures = state.get_exp_date_signatures().await?;
Ok(Json(PartialExpirationDateSignatureResponse::new(
&expiration_date_signatures,
)))
}
#[get("/expiration-date-signatures/<timestamp>")]
pub async fn expiration_date_signatures_timestamp(
timestamp: u64,
state: &RocketState<State>,
) -> Result<Json<PartialExpirationDateSignatureResponse>> {
let expiration_date_signatures = state.get_exp_date_signatures_timestamp(timestamp).await?;
Ok(Json(PartialExpirationDateSignatureResponse::new(
&expiration_date_signatures,
)))
}
#[get("/coin-indices-signatures")]
pub async fn coin_indices_signatures(
state: &RocketState<State>,
) -> Result<Json<PartialCoinIndicesSignatureResponse>> {
let coin_indices_signatures = state.get_coin_indices_signatures().await?;
Ok(Json(PartialCoinIndicesSignatureResponse::new(
&coin_indices_signatures,
)))
}
+5
View File
@@ -19,6 +19,7 @@ use nym_coconut_dkg_common::types::{
use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare};
use nym_contracts_common::IdentityKey;
use nym_dkg::Threshold;
use nym_ecash_contract_common::blacklist::BlacklistedAccountResponse;
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
use nym_validator_client::nyxd::{AccountId, Fee, Hash, TxResponse};
@@ -42,6 +43,10 @@ pub trait Client {
&self,
blinded_serial_number: String,
) -> Result<SpendCredentialResponse>;
async fn get_blacklisted_account(
&self,
public_key: String,
) -> Result<BlacklistedAccountResponse>;
async fn contract_state(&self) -> Result<State>;
+51 -66
View File
@@ -3,25 +3,20 @@
use crate::coconut::error::{CoconutError, Result};
use nym_api_requests::coconut::BlindSignRequestBody;
use nym_coconut_bandwidth_contract_common::events::{
use nym_credentials::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData;
use nym_credentials::coconut::bandwidth::CredentialType;
use nym_crypto::asymmetric::identity;
use nym_ecash_contract_common::events::{
COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY,
DEPOSIT_INFO, DEPOSIT_VALUE,
};
use nym_credentials::coconut::bandwidth::voucher::BandwidthVoucherIssuanceData;
use nym_credentials::coconut::bandwidth::IssuanceBandwidthCredential;
use nym_crypto::asymmetric::identity;
use nym_validator_client::nyxd::helpers::find_tx_attribute;
use nym_validator_client::nyxd::TxResponse;
pub async fn validate_deposit_tx(request: &BlindSignRequestBody, tx: TxResponse) -> Result<()> {
if request.public_attributes_plain.len()
!= IssuanceBandwidthCredential::PUBLIC_ATTRIBUTES as usize
{
return Err(CoconutError::InconsistentPublicAttributes);
}
// extract actual public attributes + associated x25519 public key
let deposit_value = find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_VALUE)
// we're not using it anymore, but for legacy reasons it must be present
let _deposit_value = find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_VALUE)
.ok_or(CoconutError::DepositValueNotFound)?;
let deposit_info = find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_INFO)
@@ -42,23 +37,14 @@ pub async fn validate_deposit_tx(request: &BlindSignRequestBody, tx: TxResponse)
)
.ok_or(CoconutError::DepositEncrKeyNotFound)?;
// check public attributes against request data
// check public attributes against static data
// (thinking about it attaching that data might be redundant since we have the source of truth on the chain)
// safety: we won't read data out of bounds since we just checked we have BandwidthVoucher::PUBLIC_ATTRIBUTES values in the vec
if deposit_value != request.public_attributes_plain[0] {
return Err(CoconutError::InconsistentDepositValue {
request: request.public_attributes_plain[0].clone(),
on_chain: deposit_value,
});
}
if deposit_info != request.public_attributes_plain[1] {
if deposit_info != CredentialType::TicketBook.to_string() {
return Err(CoconutError::InconsistentDepositInfo {
request: request.public_attributes_plain[1].clone(),
request: CredentialType::TicketBook.to_string(),
on_chain: deposit_info,
});
}
// verify signature
let x25519 = identity::PublicKey::from_base58_string(x25519_raw)?;
let plaintext = BandwidthVoucherIssuanceData::request_plaintext(
@@ -74,15 +60,16 @@ pub async fn validate_deposit_tx(request: &BlindSignRequestBody, tx: TxResponse)
mod test {
use super::*;
use crate::coconut::tests::{tx_entry_fixture, voucher_fixture};
use cosmwasm_std::coin;
use nym_coconut::BlindSignRequest;
use nym_coconut_bandwidth_contract_common::events::DEPOSITED_FUNDS_EVENT_TYPE;
use nym_compact_ecash::{
generate_keypair_user, scheme::withdrawal::WithdrawalRequest, setup::GroupParameters,
};
use nym_credentials::coconut::bandwidth::CredentialType;
use nym_ecash_contract_common::events::DEPOSITED_FUNDS_EVENT_TYPE;
use nym_validator_client::nyxd::{Event, EventAttribute};
#[tokio::test]
async fn validate_deposit_tx_test() {
let voucher = voucher_fixture(coin(1234, "unym"), None);
let voucher = voucher_fixture(None);
let signing_data = voucher.prepare_for_signing();
let voucher_data = voucher.get_variant_data().voucher_data().unwrap();
let correct_request = voucher_data.create_blind_sign_request_body(&signing_data);
@@ -90,12 +77,12 @@ mod test {
let mut tx_entry = tx_entry_fixture(correct_request.tx_hash);
let good_deposit_attribute = EventAttribute {
key: DEPOSIT_VALUE.to_string(),
value: correct_request.public_attributes_plain[0].clone(),
value: "random string, it only needs to be there".to_string(),
index: false,
};
let good_info_attribute = EventAttribute {
key: DEPOSIT_INFO.to_string(),
value: correct_request.public_attributes_plain[1].clone(),
value: CredentialType::TicketBook.to_string(),
index: false,
};
let good_identity_key_attribute = EventAttribute {
@@ -122,11 +109,6 @@ mod test {
);
tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![
EventAttribute {
key: DEPOSIT_VALUE.parse().unwrap(),
value: "10".parse().unwrap(),
index: false,
},
good_info_attribute.clone(),
good_identity_key_attribute.clone(),
good_encryption_key_attribute.clone(),
@@ -136,11 +118,7 @@ mod test {
.unwrap_err();
assert_eq!(
err.to_string(),
CoconutError::InconsistentDepositValue {
request: "1234".to_string(),
on_chain: "10".to_string()
}
.to_string()
CoconutError::DepositValueNotFound.to_string()
);
tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![
@@ -173,7 +151,7 @@ mod test {
err.to_string(),
CoconutError::InconsistentDepositInfo {
on_chain: "bandwidth deposit info".to_string(),
request: CredentialType::Voucher.to_string(),
request: CredentialType::TicketBook.to_string(),
}
.to_string(),
);
@@ -281,32 +259,38 @@ mod test {
.to_string(),
);
// hard-coded values, that generate a correct signature
let blind_sign_req = BlindSignRequest::from_bytes(&[
176, 113, 19, 237, 218, 252, 113, 20, 225, 238, 59, 88, 217, 45, 233, 178, 65, 28, 242,
0, 222, 48, 110, 216, 26, 111, 51, 235, 61, 74, 200, 15, 130, 245, 45, 170, 155, 190,
156, 77, 180, 142, 29, 63, 15, 224, 150, 31, 139, 24, 65, 175, 143, 153, 11, 203, 33,
16, 152, 22, 221, 203, 99, 233, 208, 142, 161, 194, 46, 227, 177, 96, 119, 30, 175, 69,
104, 14, 2, 191, 26, 94, 30, 165, 15, 28, 40, 176, 1, 78, 253, 79, 20, 137, 102, 74, 2,
0, 0, 0, 0, 0, 0, 0, 131, 133, 112, 115, 53, 98, 58, 166, 240, 70, 185, 210, 203, 12,
114, 66, 180, 38, 139, 12, 187, 45, 250, 201, 68, 102, 159, 172, 218, 124, 151, 23,
172, 18, 216, 122, 246, 7, 185, 76, 20, 167, 123, 122, 152, 241, 175, 226, 176, 8, 170,
70, 140, 252, 36, 130, 67, 204, 111, 116, 107, 92, 200, 77, 252, 31, 138, 18, 10, 215,
165, 243, 95, 199, 193, 61, 200, 187, 22, 198, 109, 213, 145, 71, 171, 132, 174, 68,
105, 248, 0, 115, 50, 55, 199, 84, 67, 16, 125, 216, 250, 154, 115, 174, 9, 206, 44,
88, 63, 163, 124, 10, 239, 64, 158, 191, 27, 169, 177, 194, 223, 142, 202, 206, 189,
122, 123, 91, 171, 15, 40, 192, 148, 75, 174, 24, 116, 229, 127, 170, 110, 183, 151, 2,
118, 168, 22, 113, 87, 237, 91, 228, 249, 120, 114, 255, 53, 175, 245, 39, 2, 0, 0, 0,
0, 0, 0, 0, 225, 45, 230, 25, 62, 202, 96, 166, 171, 241, 206, 137, 254, 51, 154, 255,
122, 130, 107, 54, 5, 206, 207, 120, 193, 214, 64, 10, 111, 195, 86, 55, 201, 36, 10,
18, 154, 158, 183, 87, 185, 59, 228, 89, 134, 193, 217, 188, 64, 164, 249, 21, 248, 20,
207, 58, 31, 10, 19, 176, 246, 150, 45, 48, 2, 0, 0, 0, 0, 0, 0, 0, 173, 60, 65, 209,
100, 114, 138, 186, 158, 150, 109, 230, 111, 86, 101, 72, 194, 237, 173, 195, 139, 175,
238, 25, 169, 18, 188, 75, 77, 54, 111, 20, 115, 235, 195, 2, 123, 133, 164, 81, 15,
45, 11, 84, 139, 38, 8, 224, 197, 181, 95, 147, 49, 77, 193, 207, 52, 141, 195, 195,
66, 137, 17, 32,
])
//hard-coded values, that generate a correct signature
let blind_sign_req = WithdrawalRequest::try_from(
[
176u8, 113, 19, 237, 218, 252, 113, 20, 225, 238, 59, 88, 217, 45, 233, 178, 65,
28, 242, 0, 222, 48, 110, 216, 26, 111, 51, 235, 61, 74, 200, 15, 130, 245, 45,
170, 155, 190, 156, 77, 180, 142, 29, 63, 15, 224, 150, 31, 139, 24, 65, 175, 143,
153, 11, 203, 33, 16, 152, 22, 221, 203, 99, 233, 208, 142, 161, 194, 46, 227, 177,
96, 119, 30, 175, 69, 104, 14, 2, 191, 26, 94, 30, 165, 15, 28, 40, 176, 1, 78,
253, 79, 20, 137, 102, 74, 2, 0, 0, 0, 0, 0, 0, 0, 131, 133, 112, 115, 53, 98, 58,
166, 240, 70, 185, 210, 203, 12, 114, 66, 180, 38, 139, 12, 187, 45, 250, 201, 68,
102, 159, 172, 218, 124, 151, 23, 172, 18, 216, 122, 246, 7, 185, 76, 20, 167, 123,
122, 152, 241, 175, 226, 176, 8, 170, 70, 140, 252, 36, 130, 67, 204, 111, 116,
107, 92, 200, 77, 252, 31, 138, 18, 10, 215, 165, 243, 95, 199, 193, 61, 200, 187,
22, 198, 109, 213, 145, 71, 171, 132, 174, 68, 105, 248, 0, 115, 50, 55, 199, 84,
67, 16, 125, 216, 250, 154, 115, 174, 9, 206, 44, 88, 63, 163, 124, 10, 239, 64,
158, 191, 27, 169, 177, 194, 223, 142, 202, 206, 189, 122, 123, 91, 171, 15, 40,
192, 148, 75, 174, 24, 116, 229, 127, 170, 110, 183, 151, 2, 118, 168, 22, 113, 87,
237, 91, 228, 249, 120, 114, 255, 53, 175, 245, 39, 2, 0, 0, 0, 0, 0, 0, 0, 225,
45, 230, 25, 62, 202, 96, 166, 171, 241, 206, 137, 254, 51, 154, 255, 122, 130,
107, 54, 5, 206, 207, 120, 193, 214, 64, 10, 111, 195, 86, 55, 201, 36, 10, 18,
154, 158, 183, 87, 185, 59, 228, 89, 134, 193, 217, 188, 64, 164, 249, 21, 248, 20,
207, 58, 31, 10, 19, 176, 246, 150, 45, 48, 2, 0, 0, 0, 0, 0, 0, 0, 173, 60, 65,
209, 100, 114, 138, 186, 158, 150, 109, 230, 111, 86, 101, 72, 194, 237, 173, 195,
139, 175, 238, 25, 169, 18, 188, 75, 77, 54, 111, 20, 115, 235, 195, 2, 123, 133,
164, 81, 15, 45, 11, 84, 139, 38, 8, 224, 197, 181, 95, 147, 49, 77, 193, 207, 52,
141, 195, 195, 66, 137, 17, 32,
]
.as_slice(),
)
.unwrap();
let expiration_date = 1708300800; // Feb, 19th, 2024
let ecash_keypair = generate_keypair_user(&GroupParameters::new());
let correct_request = BlindSignRequestBody::new(
blind_sign_req,
@@ -314,7 +298,8 @@ mod test {
.parse()
.unwrap(),
"3vUCc6MCN5AC2LNgDYjRB1QeErZSN1S8f6K14JHjpUcKWXbjGYFExA8DbwQQBki9gyUqrpBF94Drttb4eMcGQXkp".parse().unwrap(),
voucher.get_plain_public_attributes(),
ecash_keypair.public_key(),
expiration_date,
);
tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![
good_deposit_attribute.clone(),
+6
View File
@@ -133,6 +133,12 @@ pub enum CoconutError {
#[error("coconut internal error: {0}")]
CoconutInternalError(#[from] nym_coconut::CoconutError),
#[error("Compact ecash internal error - {0}")]
CompactEcashInternalError(#[from] nym_compact_ecash::error::CompactEcashError),
#[error("Account linked to this public key has been blacklisted")]
BlacklistedAccount,
#[error("could not find a deposit event in the transaction provided")]
DepositEventNotFound,
+26 -7
View File
@@ -6,7 +6,13 @@ 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_compact_ecash::scheme::expiration_date_signatures::{
sign_expiration_date, ExpirationDateSignature,
};
use nym_compact_ecash::scheme::keygen::SecretKeyAuth;
use nym_compact_ecash::setup::{sign_coin_indices, CoinIndexSignature, Parameters};
use nym_compact_ecash::utils::BlindedSignature;
use nym_compact_ecash::{PublicKeyUser, VerificationKeyAuth, WithdrawalRequest};
use nym_validator_client::nyxd::error::NyxdError::AbciError;
use tokio::sync::RwLock;
@@ -25,18 +31,22 @@ pub(crate) fn accepted_vote_err(ret: Result<(), CoconutError>) -> Result<(), Coc
}
pub(crate) trait CredentialRequest {
fn blind_sign_request(&self) -> &BlindSignRequest;
fn public_attributes(&self) -> Vec<Attribute>;
fn withdrawal_request(&self) -> &WithdrawalRequest;
fn expiration_date(&self) -> u64;
fn ecash_pubkey(&self) -> PublicKeyUser;
}
impl CredentialRequest for BlindSignRequestBody {
fn blind_sign_request(&self) -> &BlindSignRequest {
fn withdrawal_request(&self) -> &WithdrawalRequest {
&self.inner_sign_request
}
fn public_attributes(&self) -> Vec<Attribute> {
self.public_attributes_hashed()
fn expiration_date(&self) -> u64 {
self.expiration_date
}
fn ecash_pubkey(&self) -> PublicKeyUser {
self.ecash_pubkey.clone()
}
}
@@ -52,8 +62,17 @@ impl CredentialRequest for FreePassRequest {
pub(crate) fn blind_sign<C: CredentialRequest>(
request: &C,
signing_key: &SecretKey,
signing_key: &SecretKeyAuth,
) -> Result<BlindedSignature, CoconutError> {
Ok(nym_compact_ecash::scheme::withdrawal::issue(
bandwidth_credential_params().grp(),
signing_key.clone(),
request.ecash_pubkey().clone(),
request.withdrawal_request(),
request.expiration_date(),
)?)
}
pub(crate) struct CoinIndexSignatureCache {
pub(crate) epoch_id: AtomicU64,
pub(crate) signatures: RwLock<Option<Vec<CoinIndexSignature>>>,
+10
View File
@@ -23,6 +23,7 @@ use nym_coconut_dkg_common::{
use nym_config::defaults::{ChainDetails, NymNetworkDetails};
use nym_coconut_dkg_common::dealer::RegisteredDealerDetails;
use nym_ecash_contract_common::blacklist::BlacklistedAccountResponse;
use nym_mixnet_contract_common::families::FamilyHead;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::reward_params::RewardingParams;
@@ -406,6 +407,15 @@ impl crate::coconut::client::Client for Client {
get_spent_credential(blinded_serial_number).await?
))
}
async fn get_blacklisted_account(
&self,
public_key: String,
) -> crate::coconut::error::Result<BlacklistedAccountResponse> {
Ok(nyxd_query!(
self,
get_blacklisted_account(public_key).await?
))
}
async fn contract_state(&self) -> crate::coconut::error::Result<State> {
Ok(nyxd_query!(self, get_state().await?))