diff --git a/nym-api/nym-api-requests/src/coconut/helpers.rs b/nym-api/nym-api-requests/src/coconut/helpers.rs index 73d1a7f42f..584d0200fa 100644 --- a/nym-api/nym-api-requests/src/coconut/helpers.rs +++ b/nym-api/nym-api-requests/src/coconut/helpers.rs @@ -1,7 +1,7 @@ // Copyright 2023 - Nym Technologies SA // 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 { 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() } diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs index 31bfd4bf6a..d661f88a31 100644 --- a/nym-api/src/coconut/api_routes/mod.rs +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -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, +) -> Result> { + let expiration_date_signatures = state.get_exp_date_signatures().await?; + + Ok(Json(PartialExpirationDateSignatureResponse::new( + &expiration_date_signatures, + ))) +} + +#[get("/expiration-date-signatures/")] +pub async fn expiration_date_signatures_timestamp( + timestamp: u64, + state: &RocketState, +) -> Result> { + 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, +) -> Result> { + let coin_indices_signatures = state.get_coin_indices_signatures().await?; + Ok(Json(PartialCoinIndicesSignatureResponse::new( + &coin_indices_signatures, + ))) +} diff --git a/nym-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs index d009989996..931b438b77 100644 --- a/nym-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -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; + async fn get_blacklisted_account( + &self, + public_key: String, + ) -> Result; async fn contract_state(&self) -> Result; diff --git a/nym-api/src/coconut/deposit.rs b/nym-api/src/coconut/deposit.rs index 985e9fadaa..955f161443 100644 --- a/nym-api/src/coconut/deposit.rs +++ b/nym-api/src/coconut/deposit.rs @@ -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(), diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index b4587191bb..3c99c1eb63 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -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, diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs index f521da8940..bb1692a321 100644 --- a/nym-api/src/coconut/helpers.rs +++ b/nym-api/src/coconut/helpers.rs @@ -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; + 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 { - 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( request: &C, - signing_key: &SecretKey, + signing_key: &SecretKeyAuth, ) -> Result { + 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>>, diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 79fb39b663..337a94d30b 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -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 { + Ok(nyxd_query!( + self, + get_blacklisted_account(public_key).await? + )) + } async fn contract_state(&self) -> crate::coconut::error::Result { Ok(nyxd_query!(self, get_state().await?))