From b4128c2c2a9df7bf3bacdbbb8e2e9ef250a3382e Mon Sep 17 00:00:00 2001 From: Özgür Kesim Date: Wed, 1 Mar 2023 11:11:46 +0100 Subject: WiP: age-withdraw implementation, part 1/n Commit phase of the age-withdraw protocol implemented, according to https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction --- src/exchange/Makefile.am | 2 + .../taler-exchange-httpd_aml-decision-get.c | 2 +- src/exchange/taler-exchange-httpd_metrics.h | 35 ++++---- src/exchangedb/Makefile.am | 1 + src/exchangedb/plugin_exchangedb_postgres.c | 3 + src/include/taler_crypto_lib.h | 65 +++++++++++++++ src/include/taler_exchangedb_plugin.h | 92 ++++++++++++++++++++++ src/include/taler_kyclogic_lib.h | 6 +- src/kyclogic/kyclogic_api.c | 3 + src/util/exchange_signatures.c | 57 ++++++++++++++ src/util/wallet_signatures.c | 86 ++++++++++++++++++++ 11 files changed, 334 insertions(+), 18 deletions(-) diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index da6967395..3d87a2a58 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -128,6 +128,8 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_aml-decisions-get.c \ taler-exchange-httpd_batch-deposit.c taler-exchange-httpd_batch-deposit.h \ taler-exchange-httpd_batch-withdraw.c taler-exchange-httpd_batch-withdraw.h \ + taler-exchange-httpd_age-withdraw.c taler-exchange-httpd_age-withdraw.h \ + taler-exchange-httpd_age-withdraw_reveal.c taler-exchange-httpd_age-withdraw_reveal.h \ taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \ taler-exchange-httpd_config.c taler-exchange-httpd_config.h \ taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \ diff --git a/src/exchange/taler-exchange-httpd_aml-decision-get.c b/src/exchange/taler-exchange-httpd_aml-decision-get.c index 1ceaa51df..6b36fe27f 100644 --- a/src/exchange/taler-exchange-httpd_aml-decision-get.c +++ b/src/exchange/taler-exchange-httpd_aml-decision-get.c @@ -163,7 +163,7 @@ TEH_handler_aml_decision_get ( json_t *aml_history; json_t *kyc_attributes; enum GNUNET_DB_QueryStatus qs; - bool none; + bool none = false; aml_history = json_array (); GNUNET_assert (NULL != aml_history); diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h index cae371f7c..8f6804355 100644 --- a/src/exchange/taler-exchange-httpd_metrics.h +++ b/src/exchange/taler-exchange-httpd_metrics.h @@ -34,18 +34,20 @@ enum TEH_MetricTypeRequest TEH_MT_REQUEST_OTHER = 0, TEH_MT_REQUEST_DEPOSIT = 1, TEH_MT_REQUEST_WITHDRAW = 2, - TEH_MT_REQUEST_MELT = 3, - TEH_MT_REQUEST_PURSE_CREATE = 4, - TEH_MT_REQUEST_PURSE_MERGE = 5, - TEH_MT_REQUEST_RESERVE_PURSE = 6, - TEH_MT_REQUEST_PURSE_DEPOSIT = 7, - TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 8, - TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 9, - TEH_MT_REQUEST_IDEMPOTENT_MELT = 10, - TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 11, - TEH_MT_REQUEST_BATCH_DEPOSIT = 12, - TEH_MT_REQUEST_POLICY_FULFILLMENT = 13, - TEH_MT_REQUEST_COUNT = 14 /* MUST BE LAST! */ + TEH_MT_REQUEST_AGE_WITHDRAW = 3, + TEH_MT_REQUEST_MELT = 4, + TEH_MT_REQUEST_PURSE_CREATE = 5, + TEH_MT_REQUEST_PURSE_MERGE = 6, + TEH_MT_REQUEST_RESERVE_PURSE = 7, + TEH_MT_REQUEST_PURSE_DEPOSIT = 8, + TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 9, + TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 10, + TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW = 11, + TEH_MT_REQUEST_IDEMPOTENT_MELT = 12, + TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 13, + TEH_MT_REQUEST_BATCH_DEPOSIT = 14, + TEH_MT_REQUEST_POLICY_FULFILLMENT = 15, + TEH_MT_REQUEST_COUNT = 16 /* MUST BE LAST! */ }; /** @@ -55,10 +57,11 @@ enum TEH_MetricTypeSuccess { TEH_MT_SUCCESS_DEPOSIT = 0, TEH_MT_SUCCESS_WITHDRAW = 1, - TEH_MT_SUCCESS_BATCH_WITHDRAW = 2, - TEH_MT_SUCCESS_MELT = 3, - TEH_MT_SUCCESS_REFRESH_REVEAL = 4, - TEH_MT_SUCCESS_COUNT = 5 /* MUST BE LAST! */ + TEH_MT_SUCCESS_AGE_WITHDRAW = 2, + TEH_MT_SUCCESS_BATCH_WITHDRAW = 3, + TEH_MT_SUCCESS_MELT = 4, + TEH_MT_SUCCESS_REFRESH_REVEAL = 5, + TEH_MT_SUCCESS_COUNT = 6 /* MUST BE LAST! */ }; /** diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 98e9bd90d..9757aefff 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -115,6 +115,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \ pg_drain_kyc_alert.h pg_drain_kyc_alert.c \ pg_reserves_in_insert.h pg_reserves_in_insert.c \ pg_get_withdraw_info.h pg_get_withdraw_info.c \ + pg_get_age_withdraw_info.c pg_get_age_withdraw_info.h \ pg_do_batch_withdraw.h pg_do_batch_withdraw.c \ pg_get_policy_details.h pg_get_policy_details.c \ pg_persist_policy_details.h pg_persist_policy_details.c \ diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index bbf061258..2ef250775 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -116,6 +116,7 @@ #include "pg_drain_kyc_alert.h" #include "pg_reserves_in_insert.h" #include "pg_get_withdraw_info.h" +#include "pg_get_age_withdraw_info.h" #include "pg_do_batch_withdraw.h" #include "pg_get_policy_details.h" #include "pg_persist_policy_details.h" @@ -580,6 +581,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &TEH_PG_get_withdraw_info; plugin->do_batch_withdraw = &TEH_PG_do_batch_withdraw; + plugin->get_age_withdraw_info + = &TEH_PG_get_age_withdraw_info; plugin->get_policy_details = &TEH_PG_get_policy_details; plugin->persist_policy_details diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 1a3b40e4d..b6ec2ed8e 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -46,6 +46,7 @@ * fixed and part of the protocol. */ #define TALER_CNC_KAPPA 3 +#define TALER_CNC_KAPPA_MINUS_ONE_STR "2" /* ****************** Coin crypto primitives ************* */ @@ -436,6 +437,15 @@ struct TALER_AgeCommitmentPublicKeyP }; +/* + * @brief Hash to represent the commitment to n*kappa blinded keys during a age-withdrawal. + */ +struct TALER_AgeWithdrawCommitmentHashP +{ + struct GNUNET_HashCode hash; +}; + + /** * @brief Type of online public keys used by the wallet to establish a purse and the associated contract meta data. */ @@ -3700,6 +3710,42 @@ TALER_wallet_withdraw_verify ( const struct TALER_ReserveSignatureP *reserve_sig); +/** + * Sign age-withdraw request. + * + * @param h_commitment hash all n*kappa blinded coins in the commitment for the age-withdraw + * @param amount_with_fee amount to debit the reserve for + * @param max_age_group maximum age group that the withdrawn coins must be restricted to + * @param reserve_priv private key to sign with + * @param[out] reserve_sig resulting signature + */ +void +TALER_wallet_age_withdraw_sign ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + const struct TALER_Amount *amount_with_fee, + uint32_t max_age_group, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig); + +/** + * Verify an age-withdraw request. + * + * @param h_commitment hash all n*kappa blinded coins in the commitment for the age-withdraw + * @param amount_with_fee amount to debit the reserve for + * @param max_age_group maximum age group that the withdrawn coins must be restricted to + * @param reserve_pub public key of the reserve + * @param reserve_sig resulting signature + * @return #GNUNET_OK if the signature is valid + */ +enum GNUNET_GenericReturnValue +TALER_wallet_age_withdraw_verify ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + const struct TALER_Amount *amount_with_fee, + uint32_t max_age_group, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig); + + /** * Verify exchange melt confirmation. * @@ -4789,6 +4835,25 @@ TALER_exchange_online_purse_status_verify ( const struct TALER_ExchangeSignatureP *exchange_sig); +/** + * Create age-withdraw confirmation signature. + * + * @param scb function to call to create the signature + * @param awch age-withdraw commitment that identifies the n*kappa blinded coins + * @param noreveal_index gamma cut-and-choose value chosen by the exchange + * @param[out] pub where to write the exchange public key + * @param[out] sig where to write the exchange signature + * @return #TALER_EC_NONE on success + */ +enum TALER_ErrorCode +TALER_exchange_online_age_withdraw_confirmation_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + uint32_t noreveal_index, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig); + + /* ********************* offline signing ************************** */ diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index db3289ffc..2c606225d 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -1051,6 +1051,58 @@ struct TALER_EXCHANGEDB_CollectableBlindcoin }; +/** + * @brief Information we keep for an age-withdraw commitment + * to reproduce the /age-withdraw operation if neede, and to have proof + * that a reserve was drained by this amount. + */ +struct TALER_EXCHANGEDB_AgeWithdrawCommitment +{ + /** + * Total amount (with fee) committed to withdraw + */ + struct TALER_Amount amount_with_fee; + + /** + * Maximum age group that the coins are restricted to. + */ + uint32_t max_age_group; + + /** + * The hash of the commitment of all n*kappa coins + */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment; + + /** + * Index (smaller #TALER_CNC_KAPPA) which the exchange has chosen to not have + * revealed during cut and choose. This value applies to all n coins in the + * commitment. + */ + uint32_t noreveal_index; + + /** + * Public key of the reserve that was drained. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Signature confirming the age withdrawal, matching @e reserve_pub, @e + * maximum_age_group and @e h_commitment and @e total_amount_with_fee. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * The exchange's signature of the response. + */ + struct TALER_ExchangeSignatureP sig; + + /** + * Timestamp of the request beeing made + */ + struct GNUNET_TIME_Timestamp timestamp; +}; + + /** * Information the exchange records about a recoup request * in a reserve history. @@ -3607,6 +3659,46 @@ struct TALER_EXCHANGEDB_Plugin bool *conflict, bool *nonce_reuse); + /** + * Locate the response for a age-withdraw request under a hash that uniquely + * identifies the age-withdraw operation. Used to ensure idempotency of the + * request. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param reserve_pub public key of the reserve for which the age-withdraw request is made + * @param ach hash that uniquely identifies the age-withdraw operation + * @param[out] awc corresponding details of the previous age-withdraw request if an entry was found + * @return statement execution status + */ + enum GNUNET_DB_QueryStatus + (*get_age_withdraw_info)( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_AgeWithdrawCommitmentHashP *ach, + struct TALER_EXCHANGEDB_AgeWithdrawCommitment *awc); + + /** + * Perform an age-withdraw operation, checking for sufficient balance + * and possibly persisting the withdrawal details. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param commitment corresponding commitment for the age-withdraw + * @param now current time (rounded) + * @param[out] found set to true if the reserve was found + * @param[out] balance_ok set to true if the balance was sufficient + * @param[out] ruuid set to the reserve's UUID (reserves table row) + * @return query execution status + */ + enum GNUNET_DB_QueryStatus + (*do_age_withdraw)( + void *cls, + const struct TALER_EXCHANGEDB_AgeWithdrawCommitment *commitment, + struct GNUNET_TIME_Timestamp now, + bool *found, + bool *balance_ok, + uint64_t *ruuid); + + /** * Retrieve the details to a policy given by its hash_code * diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h index 4b15b447a..44cc16e5b 100644 --- a/src/include/taler_kyclogic_lib.h +++ b/src/include/taler_kyclogic_lib.h @@ -73,8 +73,12 @@ enum TALER_KYCLOGIC_KycTriggerEvent /** * Reserve is being closed by force. */ - TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE = 4 + TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE = 4, + /** + * Customer withdraws coins via age-withdraw. + */ + TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW = 5, }; diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c index 4c465ad10..0ef1295ed 100644 --- a/src/kyclogic/kyclogic_api.c +++ b/src/kyclogic/kyclogic_api.c @@ -186,6 +186,7 @@ TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s, enum TALER_KYCLOGIC_KycTriggerEvent out; } map [] = { { "withdraw", TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW }, + { "age-withdraw", TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW }, { "deposit", TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT }, { "merge", TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE }, { "balance", TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE }, @@ -214,6 +215,8 @@ TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger) { case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW: return "withdraw"; + case TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW: + return "age-withdraw"; case TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT: return "deposit"; case TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE: diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c index c2a841839..d8bf716c7 100644 --- a/src/util/exchange_signatures.c +++ b/src/util/exchange_signatures.c @@ -359,6 +359,63 @@ TALER_exchange_online_melt_confirmation_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format of the block signed by the Exchange in response to a + * successful "/reserves/$RESERVE_PUB/age-withdraw" request. Hereby the + * exchange affirms that the commitment along with the maximum age group and + * the amount were accepted. This also commits the exchange to a particular + * index to not be revealed during the reveal. + */ +struct TALER_AgeWithdrawConfirmationPS +{ + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW. Signed by a + * `struct TALER_ExchangePublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Commitment made in the /reserves/$RESERVE_PUB/age-withdraw. + */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment GNUNET_PACKED; + + /** + * Index that the client will not have to reveal, in NBO. + * Must be smaller than #TALER_CNC_KAPPA. + */ + uint32_t noreveal_index GNUNET_PACKED; + +}; + +GNUNET_NETWORK_STRUCT_END + +enum TALER_ErrorCode +TALER_exchange_online_age_withdraw_confirmation_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + uint32_t noreveal_index, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + + struct TALER_AgeWithdrawConfirmationPS confirm = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW), + .purpose.size = htonl (sizeof (confirm)), + .h_commitment = *h_commitment, + .noreveal_index = htonl (noreveal_index) + }; + + return scb (&confirm.purpose, + pub, + sig); +} + + +/* TODO:oec: add signature for age-withdraw, age-reveal */ + + GNUNET_NETWORK_STRUCT_BEGIN /** diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c index e655c3e5e..221865e73 100644 --- a/src/util/wallet_signatures.c +++ b/src/util/wallet_signatures.c @@ -604,6 +604,92 @@ TALER_wallet_withdraw_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used for to generate the signature on a request to + * age-withdraw from a reserve. + */ +struct TALER_AgeWithdrawRequestPS +{ + + /** + * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW. + * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the commitment of n*kappa coins + */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment GNUNET_PACKED; + + /** + * Value of the coin being exchanged (matching the denomination key) + * plus the transaction fee. We include this in what is being + * signed so that we can verify a reserve's remaining total balance + * without needing to access the respective denomination key + * information each time. + */ + struct TALER_AmountNBO amount_with_fee; + + /** + * Maximum age group that the coins are going to be restricted to. + */ + uint32_t max_age_group; +}; + + +GNUNET_NETWORK_STRUCT_END + +void +TALER_wallet_age_withdraw_sign ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + const struct TALER_Amount *amount_with_fee, + uint32_t max_age_group, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_AgeWithdrawRequestPS req = { + .purpose.size = htonl (sizeof (req)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW), + .h_commitment = *h_commitment, + .max_age_group = max_age_group + }; + + TALER_amount_hton (&req.amount_with_fee, + amount_with_fee); + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &req, + &reserve_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_age_withdraw_verify ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + const struct TALER_Amount *amount_with_fee, + uint32_t max_age_group, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_AgeWithdrawRequestPS awsrd = { + .purpose.size = htonl (sizeof (awsrd)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW), + .h_commitment = *h_commitment, + .max_age_group = max_age_group + }; + + TALER_amount_hton (&awsrd.amount_with_fee, + amount_with_fee); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW, + &awsrd, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + GNUNET_NETWORK_STRUCT_BEGIN -- cgit v1.2.3