exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit c6f7bd46fe73236dd3464cc6d87a5bce92c1ef16
parent cb60a5695d330de0feea2fb4800ca853a8ffe0ed
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 24 Sep 2023 19:03:30 +0200

combine deposit confirmation signatures into one big signature

Diffstat:
Msrc/auditor/taler-auditor-httpd_deposit-confirmation.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/auditor/taler-helper-auditor-deposits.c | 29++++++++++++++++-------------
Msrc/auditordb/auditor-0001.sql | 7++++---
Msrc/auditordb/pg_get_deposit_confirmations.c | 45++++++++++++++++++++++++++++++++++-----------
Msrc/auditordb/pg_insert_deposit_confirmation.c | 18++++++++++++------
Msrc/auditordb/plugin_auditordb_postgres.c | 2+-
Msrc/exchange/taler-exchange-httpd_batch-deposit.c | 82++++++++++++++++++++++++-------------------------------------------------------
Msrc/include/taler_auditor_service.h | 18+++++++++++-------
Msrc/include/taler_auditordb_plugin.h | 21+++++++++++++++------
Msrc/include/taler_crypto_lib.h | 20++++++++++++--------
Msrc/include/taler_exchange_service.h | 9++-------
Msrc/include/taler_testing_lib.h | 17++++++++---------
Msrc/lib/auditor_api_deposit_confirmation.c | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/lib/exchange_api_batch_deposit.c | 118++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/testing/test_auditor_api.c | 2+-
Msrc/testing/testing_api_cmd_auditor_deposit_confirmation.c | 48+++++++++++++++++++++++++++++++-----------------
Msrc/testing/testing_api_cmd_batch_deposit.c | 31++++++++++++++-----------------
Msrc/testing/testing_api_cmd_deposit.c | 15++++++++++-----
Msrc/util/exchange_signatures.c | 48++++++++++++++++++++++++++++++++----------------
19 files changed, 432 insertions(+), 295 deletions(-)

diff --git a/src/auditor/taler-auditor-httpd_deposit-confirmation.c b/src/auditor/taler-auditor-httpd_deposit-confirmation.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -115,6 +115,11 @@ verify_and_execute_deposit_confirmation ( .end = GNUNET_TIME_timestamp_hton (es->ep_end), .signkey_pub = es->exchange_pub }; + const struct TALER_CoinSpendSignatureP *coin_sigps[ + GNUNET_NZL (dc->num_coins)]; + + for (unsigned int i = 0; i<dc->num_coins; i++) + coin_sigps[i] = &dc->coin_sigs[i]; if (GNUNET_TIME_absolute_is_future (es->ep_start.abs_time) || GNUNET_TIME_absolute_is_past (es->ep_expire.abs_time) ) @@ -231,8 +236,9 @@ verify_and_execute_deposit_confirmation ( dc->exchange_timestamp, dc->wire_deadline, dc->refund_deadline, - &dc->amount_without_fee, - &dc->coin_pub, + &dc->total_without_fee, + dc->num_coins, + coin_sigps, &dc->merchant, &dc->exchange_pub, &dc->exchange_sig)) @@ -265,16 +271,19 @@ verify_and_execute_deposit_confirmation ( MHD_RESULT -TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size) +TAH_DEPOSIT_CONFIRMATION_handler ( + struct TAH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) { struct TALER_AUDITORDB_DepositConfirmation dc = { .refund_deadline = GNUNET_TIME_UNIT_ZERO_TS }; struct TALER_AUDITORDB_ExchangeSigningKey es; + const json_t *jcoin_sigs; + const json_t *jcoin_pubs; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &dc.h_contract_terms), @@ -290,11 +299,13 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh, NULL), GNUNET_JSON_spec_timestamp ("wire_deadline", &dc.wire_deadline), - TALER_JSON_spec_amount ("amount_without_fee", + TALER_JSON_spec_amount ("total_without_fee", TAH_currency, - &dc.amount_without_fee), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &dc.coin_pub), + &dc.total_without_fee), + GNUNET_JSON_spec_array_const ("coin_pubs", + &jcoin_pubs), + GNUNET_JSON_spec_array_const ("coin_sigs", + &jcoin_sigs), GNUNET_JSON_spec_fixed_auto ("merchant_pub", &dc.merchant), GNUNET_JSON_spec_fixed_auto ("exchange_sig", @@ -313,13 +324,14 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh, &es.master_sig), GNUNET_JSON_spec_end () }; + unsigned int num_coins; + json_t *json; (void) rh; (void) connection_cls; (void) upload_data; (void) upload_data_size; { - json_t *json; enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_post_json (connection, @@ -335,22 +347,89 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh, res = TALER_MHD_parse_json_data (connection, json, spec); - json_decref (json); if (GNUNET_SYSERR == res) + { + json_decref (json); return MHD_NO; /* hard failure */ + } if (GNUNET_NO == res) + { + json_decref (json); return MHD_YES; /* failure */ + } } - - es.exchange_pub = dc.exchange_pub; /* used twice! */ - dc.master_public_key = es.master_public_key; + num_coins = json_array_size (jcoin_sigs); + if (num_coins != json_array_size (jcoin_pubs)) { + GNUNET_break_op (0); + json_decref (json); + return TALER_MHD_reply_with_ec ( + connection, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "coin_pubs.length != coin_sigs.length"); + } + if (0 == num_coins) + { + GNUNET_break_op (0); + json_decref (json); + return TALER_MHD_reply_with_ec ( + connection, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "coin_pubs array is empty"); + } + { + struct TALER_CoinSpendPublicKeyP coin_pubs[num_coins]; + struct TALER_CoinSpendSignatureP coin_sigs[num_coins]; MHD_RESULT res; + for (unsigned int i = 0; i<num_coins; i++) + { + json_t *jpub = json_array_get (jcoin_pubs, + i); + json_t *jsig = json_array_get (jcoin_sigs, + i); + const char *ps = json_string_value (jpub); + const char *ss = json_string_value (jsig); + + if ( (NULL == ps) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (ps, + strlen (ps), + &coin_pubs[i], + sizeof (coin_pubs[i]))) ) + { + GNUNET_break_op (0); + json_decref (json); + return TALER_MHD_reply_with_ec ( + connection, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "coin_pub[] malformed"); + } + if ( (NULL == ss) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (ss, + strlen (ss), + &coin_sigs[i], + sizeof (coin_sigs[i]))) ) + { + GNUNET_break_op (0); + json_decref (json); + return TALER_MHD_reply_with_ec ( + connection, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "coin_sig[] malformed"); + } + } + dc.num_coins = num_coins; + dc.coin_pubs = coin_pubs; + dc.coin_sigs = coin_sigs; + es.exchange_pub = dc.exchange_pub; /* used twice! */ + dc.master_public_key = es.master_public_key; res = verify_and_execute_deposit_confirmation (connection, &dc, &es); GNUNET_JSON_parse_free (spec); + json_decref (json); return res; } } diff --git a/src/auditor/taler-helper-auditor-deposits.c b/src/auditor/taler-helper-auditor-deposits.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2016-2021 Taler Systems SA + Copyright (C) 2016-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero Public License as published by the Free Software @@ -108,8 +108,10 @@ test_dc (void *cls, const struct TALER_AUDITORDB_DepositConfirmation *dc) { struct DepositConfirmationContext *dcc = cls; + bool missing = false; dcc->last_seen_coin_serial = serial_id; + for (unsigned int i = 0; i<dc->num_coins; i++) { enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Timestamp exchange_timestamp; @@ -118,20 +120,12 @@ test_dc (void *cls, qs = TALER_ARL_edb->have_deposit2 (TALER_ARL_edb->cls, &dc->h_contract_terms, &dc->h_wire, - &dc->coin_pub, + &dc->coin_pubs[i], &dc->merchant, dc->refund_deadline, &deposit_fee, &exchange_timestamp); - if (qs > 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Found deposit %s in exchange database\n", - GNUNET_h2s (&dc->h_contract_terms.hash)); - if (TALER_ARL_do_abort ()) - return GNUNET_SYSERR; - return GNUNET_OK; /* found, all good */ - } + missing |= (0 == qs); if (qs < 0) { GNUNET_break (0); /* DB error, complain */ @@ -139,6 +133,15 @@ test_dc (void *cls, return GNUNET_SYSERR; } } + if (! missing) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Found deposit %s in exchange database\n", + GNUNET_h2s (&dc->h_contract_terms.hash)); + if (TALER_ARL_do_abort ()) + return GNUNET_SYSERR; + return GNUNET_OK; /* all coins found, all good */ + } /* deposit confirmation missing! report! */ TALER_ARL_report ( report_deposit_confirmation_inconsistencies, @@ -146,7 +149,7 @@ test_dc (void *cls, TALER_JSON_pack_time_abs_human ("timestamp", dc->exchange_timestamp.abs_time), TALER_JSON_pack_amount ("amount", - &dc->amount_without_fee), + &dc->total_without_fee), GNUNET_JSON_pack_uint64 ("rowid", serial_id), GNUNET_JSON_pack_data_auto ("account", @@ -156,7 +159,7 @@ test_dc (void *cls, dcc->missed_count++; TALER_ARL_amount_add (&dcc->missed_amount, &dcc->missed_amount, - &dc->amount_without_fee); + &dc->total_without_fee); if (TALER_ARL_do_abort ()) return GNUNET_SYSERR; return GNUNET_OK; diff --git a/src/auditordb/auditor-0001.sql b/src/auditordb/auditor-0001.sql @@ -289,13 +289,14 @@ CREATE TABLE IF NOT EXISTS deposit_confirmations ,exchange_timestamp INT8 NOT NULL ,refund_deadline INT8 NOT NULL ,wire_deadline INT8 NOT NULL - ,amount_without_fee taler_amount NOT NULL - ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32) + ,total_without_fee taler_amount NOT NULL + ,coin_pubs BYTEA[] NOT NULL CHECK (CARDINALITY(coin_pubs)>0) + ,coin_sigs BYTEA[] NOT NULL CHECK (CARDINALITY(coin_sigs)=CARDINALITY(coin_pubs)) ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64) ,exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32) ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) - ,PRIMARY KEY (h_contract_terms,h_wire,coin_pub,merchant_pub,exchange_sig,exchange_pub,master_sig) + ,PRIMARY KEY (h_contract_terms,h_wire,merchant_pub,exchange_sig,exchange_pub,master_sig) ); COMMENT ON TABLE deposit_confirmations IS 'deposit confirmation sent to us by merchants; we must check that the exchange reported these properly.'; diff --git a/src/auditordb/pg_get_deposit_confirmations.c b/src/auditordb/pg_get_deposit_confirmations.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -82,6 +82,10 @@ deposit_confirmation_cb (void *cls, struct TALER_AUDITORDB_DepositConfirmation dc = { .master_public_key = *dcc->master_pub }; + struct TALER_CoinSpendPublicKeyP *coin_pubs = NULL; + struct TALER_CoinSpendSignatureP *coin_sigs = NULL; + size_t num_pubs = 0; + size_t num_sigs = 0; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_uint64 ("serial_id", &serial_id), @@ -97,10 +101,16 @@ deposit_confirmation_cb (void *cls, &dc.refund_deadline), GNUNET_PQ_result_spec_timestamp ("wire_deadline", &dc.wire_deadline), - TALER_PQ_RESULT_SPEC_AMOUNT ("amount_without_fee", - &dc.amount_without_fee), - GNUNET_PQ_result_spec_auto_from_type ("coin_pub", - &dc.coin_pub), + TALER_PQ_RESULT_SPEC_AMOUNT ("total_without_fee", + &dc.total_without_fee), + GNUNET_PQ_result_spec_auto_array_from_type (pg->conn, + "coin_pub", + &num_pubs, + coin_pubs), + GNUNET_PQ_result_spec_auto_array_from_type (pg->conn, + "coin_sigs", + &num_sigs, + coin_sigs), GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", &dc.merchant), GNUNET_PQ_result_spec_auto_from_type ("exchange_sig", @@ -111,6 +121,7 @@ deposit_confirmation_cb (void *cls, &dc.master_sig), GNUNET_PQ_result_spec_end }; + enum GNUNET_GenericReturnValue rval; if (GNUNET_OK != GNUNET_PQ_extract_result (result, @@ -121,11 +132,22 @@ deposit_confirmation_cb (void *cls, dcc->qs = GNUNET_DB_STATUS_HARD_ERROR; return; } + if (num_sigs != num_pubs) + { + GNUNET_break (0); + dcc->qs = GNUNET_DB_STATUS_HARD_ERROR; + GNUNET_PQ_cleanup_result (rs); + return; + } dcc->qs = i + 1; - if (GNUNET_OK != - dcc->cb (dcc->cb_cls, - serial_id, - &dc)) + dc.coin_pubs = coin_pubs; + dc.coin_sigs = coin_sigs; + dc.num_coins = num_sigs; + rval = dcc->cb (dcc->cb_cls, + serial_id, + &dc); + GNUNET_PQ_cleanup_result (rs); + if (GNUNET_OK != rval) break; } } @@ -163,8 +185,9 @@ TAH_PG_get_deposit_confirmations ( ",exchange_timestamp" ",wire_deadline" ",refund_deadline" - ",amount_without_fee" - ",coin_pub" + ",total_without_fee" + ",coin_pubs" + ",coin_sigs" ",merchant_pub" ",exchange_sig" ",exchange_pub" diff --git a/src/auditordb/pg_insert_deposit_confirmation.c b/src/auditordb/pg_insert_deposit_confirmation.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022, 2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -41,8 +41,13 @@ TAH_PG_insert_deposit_confirmation ( GNUNET_PQ_query_param_timestamp (&dc->wire_deadline), GNUNET_PQ_query_param_timestamp (&dc->refund_deadline), TALER_PQ_query_param_amount (pg->conn, - &dc->amount_without_fee), - GNUNET_PQ_query_param_auto_from_type (&dc->coin_pub), + &dc->total_without_fee), + GNUNET_PQ_query_param_array_auto_from_type (dc->num_coins, + dc->coin_pubs, + pg->conn), + GNUNET_PQ_query_param_array_auto_from_type (dc->num_coins, + dc->coin_sigs, + pg->conn), GNUNET_PQ_query_param_auto_from_type (&dc->merchant), GNUNET_PQ_query_param_auto_from_type (&dc->exchange_sig), GNUNET_PQ_query_param_auto_from_type (&dc->exchange_pub), @@ -60,13 +65,14 @@ TAH_PG_insert_deposit_confirmation ( ",exchange_timestamp" ",wire_deadline" ",refund_deadline" - ",amount_without_fee" - ",coin_pub" + ",total_without_fee" + ",coin_pubs" + ",coin_sigs" ",merchant_pub" ",exchange_sig" ",exchange_pub" ",master_sig" /* master_sig could be normalized... */ - ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);"); + ") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14);"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "auditor_deposit_confirmation_insert", params); diff --git a/src/auditordb/plugin_auditordb_postgres.c b/src/auditordb/plugin_auditordb_postgres.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c @@ -108,65 +108,33 @@ reply_batch_deposit_success ( const struct BatchDepositContext *dc) { const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd; - json_t *arr; + const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)]; + enum TALER_ErrorCode ec; struct TALER_ExchangePublicKeyP pub; + struct TALER_ExchangeSignatureP sig; -again: - arr = json_array (); - GNUNET_assert (NULL != arr); for (unsigned int i = 0; i<bd->num_cdis; i++) + csigs[i] = &bd->cdis[i].csig; + if (TALER_EC_NONE != + (ec = TALER_exchange_online_deposit_confirmation_sign ( + &TEH_keys_exchange_sign_, + &bd->h_contract_terms, + &dc->h_wire, + NULL != dc->policy_json ? &dc->h_policy : NULL, + dc->exchange_timestamp, + bd->wire_deadline, + bd->refund_deadline, + &dc->policy_details.accumulated_total, /* excludes fees */ + bd->num_cdis, + csigs, + &dc->bd.merchant_pub, + &pub, + &sig))) { - const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi - = &bd->cdis[i]; - struct TALER_ExchangePublicKeyP pubi; - struct TALER_ExchangeSignatureP sig; - enum TALER_ErrorCode ec; - struct TALER_Amount amount_without_fee; - - GNUNET_assert (0 <= - TALER_amount_subtract (&amount_without_fee, - &cdi->amount_with_fee, - &dc->deposit_fees[i])); - if (TALER_EC_NONE != - (ec = TALER_exchange_online_deposit_confirmation_sign ( - &TEH_keys_exchange_sign_, - &bd->h_contract_terms, - &dc->h_wire, - NULL != dc->policy_json ? &dc->h_policy : NULL, - dc->exchange_timestamp, - bd->wire_deadline, - bd->refund_deadline, - &amount_without_fee, - &cdi->coin.coin_pub, - &dc->bd.merchant_pub, - &pubi, - &sig))) - { - GNUNET_break (0); - return TALER_MHD_reply_with_ec (connection, - ec, - NULL); - } - if (0 == i) - pub = pubi; - if (0 != - GNUNET_memcmp (&pub, - &pubi)) - { - /* note: in the future, maybe have batch sign API to avoid having to - handle key rollover... */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Exchange public key changed during batch deposit, trying again\n"); - json_decref (arr); - goto again; - } - GNUNET_assert ( - 0 == - json_array_append_new (arr, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ( - "exchange_sig", - &sig)))); + GNUNET_break (0); + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); } return TALER_MHD_REPLY_JSON_PACK ( connection, @@ -175,8 +143,8 @@ again: dc->exchange_timestamp), GNUNET_JSON_pack_data_auto ("exchange_pub", &pub), - GNUNET_JSON_pack_array_steal ("exchange_sigs", - arr)); + GNUNET_JSON_pack_data_auto ("exchange_sig", + &sig)); } diff --git a/src/include/taler_auditor_service.h b/src/include/taler_auditor_service.h @@ -226,11 +226,11 @@ TALER_AUDITOR_get_config (struct GNUNET_CURL_Context *ctx, /** * Cancel auditor config request. * - * @param auditor the auditor handle + * @param[in] auditor the auditor handle */ void -TALER_AUDITOR_get_config_cancel (struct - TALER_AUDITOR_GetConfigHandle *auditor); +TALER_AUDITOR_get_config_cancel ( + struct TALER_AUDITOR_GetConfigHandle *auditor); /** @@ -284,8 +284,10 @@ typedef void * @param exchange_timestamp timestamp when the contract was finalized, must not be too far in the future * @param wire_deadline date until which the exchange should wire the funds * @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline - * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant - * @param coin_pub coin’s public key + * @param total_without_fee the amount confirmed to be wired by the exchange to the merchant + * @param num_coins number of coins involved in the batch deposit + * @param coin_pubs array of the coin’s public keys + * @param coin_sigs array of the original deposit signatures of the coins in the batch * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT * @param exchange_pub the public key of the exchange that matches @a exchange_sig @@ -309,8 +311,10 @@ TALER_AUDITOR_deposit_confirmation ( struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendPublicKeyP *coin_pubs[static num_coins], + const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins], const struct TALER_MerchantPublicKeyP *merchant_pub, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct TALER_ExchangeSignatureP *exchange_sig, diff --git a/src/include/taler_auditordb_plugin.h b/src/include/taler_auditordb_plugin.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -421,14 +421,23 @@ struct TALER_AUDITORDB_DepositConfirmation * Amount to be deposited, excluding fee. Calculated from the * amount with fee and the fee from the deposit request. */ - struct TALER_Amount amount_without_fee; + struct TALER_Amount total_without_fee; /** - * The coin's public key. This is the value that must have been - * signed (blindly) by the Exchange. The deposit request is to be - * signed by the corresponding private key (using EdDSA). + * Length of the @e coin_pubs and @e coin_sigs arrays. */ - struct TALER_CoinSpendPublicKeyP coin_pub; + unsigned int num_coins; + + /** + * Array of the coin public keys involved in the + * batch deposit operation. + */ + const struct TALER_CoinSpendPublicKeyP *coin_pubs; + + /** + * Array of coin deposit signatures from the deposit operation. + */ + const struct TALER_CoinSpendSignatureP *coin_sigs; /** * The Merchant's public key. Allows the merchant to later refund diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h @@ -4045,8 +4045,9 @@ typedef enum TALER_ErrorCode * @param exchange_timestamp timestamp when the contract was finalized, must not be too far off * @param wire_deadline date until which the exchange should wire the funds * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline - * @param amount_without_fee the amount to be deposited after fees - * @param coin_pub public key of the deposited coin + * @param total_without_fee the total amount to be deposited after fees over all coins + * @param num_coins length of @a coin_sigs array + * @param coin_sigs signatures of the deposited coins * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) * @param[out] pub where to write the public key * @param[out] sig where to write the signature @@ -4061,8 +4062,9 @@ TALER_exchange_online_deposit_confirmation_sign ( struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins], const struct TALER_MerchantPublicKeyP *merchant_pub, struct TALER_ExchangePublicKeyP *pub, struct TALER_ExchangeSignatureP *sig); @@ -4077,8 +4079,9 @@ TALER_exchange_online_deposit_confirmation_sign ( * @param exchange_timestamp timestamp when the contract was finalized, must not be too far off * @param wire_deadline date until which the exchange should wire the funds * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline - * @param amount_without_fee the amount to be deposited after fees - * @param coin_pub public key of the deposited coin + * @param total_without_fee the total amount to be deposited after fees over all coins + * @param num_coins length of @a coin_sigs array + * @param coin_sigs signatures of the deposited coins * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) * @param pub where to write the public key * @param sig where to write the signature @@ -4092,8 +4095,9 @@ TALER_exchange_online_deposit_confirmation_verify ( struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins], const struct TALER_MerchantPublicKeyP *merchant_pub, const struct TALER_ExchangePublicKeyP *pub, const struct TALER_ExchangeSignatureP *sig); diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h @@ -1143,9 +1143,9 @@ struct TALER_EXCHANGE_BatchDepositResult struct GNUNET_TIME_Timestamp deposit_timestamp; /** - * Array of signatures provided by the exchange + * Deposit confirmation signature provided by the exchange */ - const struct TALER_ExchangeSignatureP *exchange_sigs; + const struct TALER_ExchangeSignatureP *exchange_sig; /** * exchange key used to sign @a exchange_sig. @@ -1158,11 +1158,6 @@ struct TALER_EXCHANGE_BatchDepositResult */ const char *transaction_base_url; - /** - * Length of the @e exchange_sigs array. - */ - unsigned int num_signatures; - } ok; /** diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h @@ -811,20 +811,18 @@ TALER_TESTING_cmd_exec_auditor_dbinit (const char *label, * @param label command label. * @param deposit_reference reference to any operation that can * provide a coin. - * @param coin_index if @a deposit_reference offers an array of - * coins, this parameter selects which one in that array. - * This value is currently ignored, as only one-coin - * deposits are implemented. + * @param num_coins number of coins expected in the batch deposit * @param amount_without_fee deposited amount without the fee * @param expected_response_code expected HTTP response code. * @return the command. */ struct TALER_TESTING_Command -TALER_TESTING_cmd_deposit_confirmation (const char *label, - const char *deposit_reference, - unsigned int coin_index, - const char *amount_without_fee, - unsigned int expected_response_code); +TALER_TESTING_cmd_deposit_confirmation ( + const char *label, + const char *deposit_reference, + unsigned int num_coins, + const char *amount_without_fee, + unsigned int expected_response_code); /** @@ -2729,6 +2727,7 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, op (exchange_wd_value, const struct TALER_ExchangeWithdrawValues) \ op (coin_priv, const struct TALER_CoinSpendPrivateKeyP) \ op (coin_pub, const struct TALER_CoinSpendPublicKeyP) \ + op (coin_sig, const struct TALER_CoinSpendSignatureP) \ op (absolute_time, const struct GNUNET_TIME_Absolute) \ op (timestamp, const struct GNUNET_TIME_Timestamp) \ op (wire_deadline, const struct GNUNET_TIME_Timestamp) \ diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c @@ -167,22 +167,25 @@ handle_deposit_confirmation_finished (void *cls, * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not */ static enum GNUNET_GenericReturnValue -verify_signatures (const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_ExtensionPolicyHashP *h_policy, - const struct TALER_PrivateContractHashP *h_contract_terms, - struct GNUNET_TIME_Timestamp exchange_timestamp, - struct GNUNET_TIME_Timestamp wire_deadline, - struct GNUNET_TIME_Timestamp refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_MerchantPublicKeyP *merchant_pub, - const struct TALER_ExchangePublicKeyP *exchange_pub, - const struct TALER_ExchangeSignatureP *exchange_sig, - const struct TALER_MasterPublicKeyP *master_pub, - struct GNUNET_TIME_Timestamp ep_start, - struct GNUNET_TIME_Timestamp ep_expire, - struct GNUNET_TIME_Timestamp ep_end, - const struct TALER_MasterSignatureP *master_sig) +verify_signatures ( + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_ExtensionPolicyHashP *h_policy, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct GNUNET_TIME_Timestamp exchange_timestamp, + struct GNUNET_TIME_Timestamp wire_deadline, + struct GNUNET_TIME_Timestamp refund_deadline, + const struct TALER_Amount *amount_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendSignatureP *coin_sigs[ + static num_coins], + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_MasterPublicKeyP *master_pub, + struct GNUNET_TIME_Timestamp ep_start, + struct GNUNET_TIME_Timestamp ep_expire, + struct GNUNET_TIME_Timestamp ep_end, + const struct TALER_MasterSignatureP *master_sig) { if (GNUNET_OK != TALER_exchange_online_deposit_confirmation_verify ( @@ -193,7 +196,8 @@ verify_signatures (const struct TALER_MerchantWireHashP *h_wire, wire_deadline, refund_deadline, amount_without_fee, - coin_pub, + num_coins, + coin_sigs, merchant_pub, exchange_pub, exchange_sig)) @@ -241,8 +245,12 @@ TALER_AUDITOR_deposit_confirmation ( struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendPublicKeyP *coin_pubs[ + static num_coins], + const struct TALER_CoinSpendSignatureP *coin_sigs[ + static num_coins], const struct TALER_MerchantPublicKeyP *merchant_pub, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct TALER_ExchangeSignatureP *exchange_sig, @@ -257,7 +265,14 @@ TALER_AUDITOR_deposit_confirmation ( struct TALER_AUDITOR_DepositConfirmationHandle *dh; json_t *deposit_confirmation_obj; CURL *eh; + json_t *jcoin_sigs; + json_t *jcoin_pubs; + if (0 == num_coins) + { + GNUNET_break (0); + return NULL; + } if (GNUNET_OK != verify_signatures (h_wire, h_policy, @@ -265,8 +280,9 @@ TALER_AUDITOR_deposit_confirmation ( exchange_timestamp, wire_deadline, refund_deadline, - amount_without_fee, - coin_pub, + total_without_fee, + num_coins, + coin_sigs, merchant_pub, exchange_pub, exchange_sig, @@ -279,7 +295,21 @@ TALER_AUDITOR_deposit_confirmation ( GNUNET_break_op (0); return NULL; } - + jcoin_sigs = json_array (); + GNUNET_assert (NULL != jcoin_sigs); + jcoin_pubs = json_array (); + GNUNET_assert (NULL != jcoin_pubs); + for (unsigned int i = 0; i<num_coins; i++) + { + GNUNET_assert (0 == + json_array_append_new (jcoin_sigs, + GNUNET_JSON_from_data_auto ( + coin_sigs[i]))); + GNUNET_assert (0 == + json_array_append_new (jcoin_pubs, + GNUNET_JSON_from_data_auto ( + coin_pubs[i]))); + } deposit_confirmation_obj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("h_wire", @@ -295,10 +325,12 @@ TALER_AUDITOR_deposit_confirmation ( refund_deadline)), GNUNET_JSON_pack_timestamp ("wire_deadline", wire_deadline), - TALER_JSON_pack_amount ("amount_without_fee", - amount_without_fee), - GNUNET_JSON_pack_data_auto ("coin_pub", - coin_pub), + TALER_JSON_pack_amount ("total_without_fee", + total_without_fee), + GNUNET_JSON_pack_array_steal ("coin_pubs", + jcoin_pubs), + GNUNET_JSON_pack_array_steal ("coin_sigs", + jcoin_sigs), GNUNET_JSON_pack_data_auto ("merchant_pub", merchant_pub), GNUNET_JSON_pack_data_auto ("exchange_sig", diff --git a/src/lib/exchange_api_batch_deposit.c b/src/lib/exchange_api_batch_deposit.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA + Copyright (C) 2014-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -146,9 +146,9 @@ struct TALER_EXCHANGE_BatchDepositHandle struct GNUNET_TIME_Timestamp exchange_timestamp; /** - * Exchange signatures, set for #auditor_cb. + * Exchange signature, set for #auditor_cb. */ - struct TALER_ExchangeSignatureP *exchange_sigs; + struct TALER_ExchangeSignatureP exchange_sig; /** * Head of DLL of interactions with this auditor. @@ -171,6 +171,11 @@ struct TALER_EXCHANGE_BatchDepositHandle struct TALER_ExchangePublicKeyP exchange_pub; /** + * Total amount deposited without fees as calculated by us. + */ + struct TALER_Amount total_without_fee; + + /** * Response object to free at the end. */ json_t *response; @@ -251,9 +256,20 @@ auditor_cb (void *cls, struct TALER_EXCHANGE_BatchDepositHandle *dh = cls; const struct TALER_EXCHANGE_SigningPublicKey *spk; struct TEAH_AuditorInteractionEntry *aie; - struct TALER_Amount amount_without_fee; const struct TALER_EXCHANGE_DenomPublicKey *dki; unsigned int coin; + const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL ( + dh->num_cdds)]; + const struct TALER_CoinSpendPublicKeyP *cpubs[GNUNET_NZL ( + dh->num_cdds)]; + + for (unsigned int i = 0; i<dh->num_cdds; i++) + { + const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &dh->cdds[i]; + + csigs[i] = &cdd->coin_sig; + cpubs[i] = &cdd->coin_pub; + } if (0 != GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, @@ -278,10 +294,6 @@ auditor_cb (void *cls, GNUNET_break_op (0); return; } - GNUNET_assert (0 <= - TALER_amount_subtract (&amount_without_fee, - &dh->cdds[coin].amount, - &dki->fees.deposit)); aie = GNUNET_new (struct TEAH_AuditorInteractionEntry); aie->dh = dh; aie->auditor_url = auditor_url; @@ -294,11 +306,13 @@ auditor_cb (void *cls, dh->exchange_timestamp, dh->dcd.wire_deadline, dh->dcd.refund_deadline, - &amount_without_fee, - &dh->cdds[coin].coin_pub, + &dh->total_without_fee, + dh->num_cdds, + cpubs, + csigs, &dh->dcd.merchant_pub, &dh->exchange_pub, - &dh->exchange_sigs[coin], + &dh->exchange_sig, &dh->keys->master_pub, spk->valid_from, spk->valid_until, @@ -340,12 +354,9 @@ handle_deposit_finished (void *cls, break; case MHD_HTTP_OK: { - const json_t *sigs; - json_t *sig; - unsigned int idx; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("exchange_sigs", - &sigs), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &dh->exchange_sig), GNUNET_JSON_spec_fixed_auto ("exchange_pub", &dh->exchange_pub), GNUNET_JSON_spec_mark_optional ( @@ -367,15 +378,6 @@ handle_deposit_finished (void *cls, dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - if (json_array_size (sigs) != dh->num_cdds) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - dh->exchange_sigs = GNUNET_new_array (dh->num_cdds, - struct TALER_ExchangeSignatureP); if (GNUNET_OK != TALER_EXCHANGE_test_signing_key (dh->keys, &dh->exchange_pub)) @@ -385,35 +387,12 @@ handle_deposit_finished (void *cls, dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; break; } - json_array_foreach (sigs, idx, sig) { - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &dh->exchange_sigs[idx]), - GNUNET_JSON_spec_end () - }; - struct TALER_Amount amount_without_fee; - const struct TALER_EXCHANGE_DenomPublicKey *dki; - - if (GNUNET_OK != - GNUNET_JSON_parse (sig, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - dki = TALER_EXCHANGE_get_denomination_key_by_hash (dh->keys, - &dh->cdds[idx]. - h_denom_pub); - GNUNET_assert (NULL != dki); - GNUNET_assert (0 <= - TALER_amount_subtract (&amount_without_fee, - &dh->cdds[idx].amount, - &dki->fees.deposit)); + const struct TALER_CoinSpendSignatureP *csigs[ + GNUNET_NZL (dh->num_cdds)]; + for (unsigned int i = 0; i<dh->num_cdds; i++) + csigs[i] = &dh->cdds[i].coin_sig; if (GNUNET_OK != TALER_exchange_online_deposit_confirmation_verify ( &dh->dcd.h_contract_terms, @@ -422,11 +401,12 @@ handle_deposit_finished (void *cls, dh->exchange_timestamp, dh->dcd.wire_deadline, dh->dcd.refund_deadline, - &amount_without_fee, - &dh->cdds[idx].coin_pub, + &dh->total_without_fee, + dh->num_cdds, + csigs, &dh->dcd.merchant_pub, &dh->exchange_pub, - &dh->exchange_sigs[idx])) + &dh->exchange_sig)) { GNUNET_break_op (0); dr->hr.http_status = 0; @@ -438,10 +418,9 @@ handle_deposit_finished (void *cls, &auditor_cb, dh); } - dr->details.ok.exchange_sigs = dh->exchange_sigs; + dr->details.ok.exchange_sig = &dh->exchange_sig; dr->details.ok.exchange_pub = &dh->exchange_pub; dr->details.ok.deposit_timestamp = dh->exchange_timestamp; - dr->details.ok.num_signatures = dh->num_cdds; break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the exchange is buggy @@ -531,9 +510,13 @@ TALER_EXCHANGE_batch_deposit ( json_t *deposit_obj; json_t *deposits; CURL *eh; - struct TALER_Amount amount_without_fee; const struct GNUNET_HashCode *wallet_data_hashp; + if (0 == num_cdds) + { + GNUNET_break (0); + return NULL; + } if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline, >, dcd->wire_deadline)) @@ -547,8 +530,7 @@ TALER_EXCHANGE_batch_deposit ( dh->cb = cb; dh->cb_cls = cb_cls; dh->cdds = GNUNET_memdup (cdds, - num_cdds - * sizeof (*cdds)); + num_cdds * sizeof (*cdds)); dh->num_cdds = num_cdds; dh->dcd = *dcd; if (NULL != dcd->policy_details) @@ -559,11 +541,15 @@ TALER_EXCHANGE_batch_deposit ( &dh->h_wire); deposits = json_array (); GNUNET_assert (NULL != deposits); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (cdds[0].amount.currency, + &dh->total_without_fee)); for (unsigned int i = 0; i<num_cdds; i++) { const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i]; const struct TALER_EXCHANGE_DenomPublicKey *dki; const struct TALER_AgeCommitmentHash *h_age_commitmentp; + struct TALER_Amount amount_without_fee; dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys, &cdd->h_denom_pub); @@ -580,17 +566,14 @@ TALER_EXCHANGE_batch_deposit ( { *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT; GNUNET_break_op (0); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Amount: %s\n", - TALER_amount2s (&cdd->amount)); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Fee: %s\n", - TALER_amount2s (&dki->fees.deposit)); GNUNET_free (dh->cdds); GNUNET_free (dh); return NULL; } - + GNUNET_assert (0 <= + TALER_amount_add (&dh->total_without_fee, + &dh->total_without_fee, + &amount_without_fee)); if (GNUNET_OK != TALER_EXCHANGE_verify_deposit_signature_ (dcd, &dh->h_policy, @@ -737,7 +720,6 @@ TALER_EXCHANGE_batch_deposit_cancel ( TALER_EXCHANGE_keys_decref (deposit->keys); GNUNET_free (deposit->url); GNUNET_free (deposit->cdds); - GNUNET_free (deposit->exchange_sigs); TALER_curl_easy_post_finished (&deposit->post_ctx); json_decref (deposit->response); GNUNET_free (deposit); diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c @@ -635,7 +635,7 @@ run (void *cls, MHD_HTTP_OK), TALER_TESTING_cmd_deposit_confirmation ("deposit-confirmation", "massive-deposit-10", - 0, + 1, "EUR:0.99", MHD_HTTP_OK), CMD_RUN_AUDITOR ("massive-auditor"), diff --git a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c @@ -59,9 +59,9 @@ struct DepositConfirmationState const char *amount_without_fee; /** - * Which coin of the @e deposit_reference should we confirm. + * How many coins were there in the @e deposit_reference? */ - unsigned int coin_index; + unsigned int num_coins; /** * DepositConfirmation handle while operation is running. @@ -201,14 +201,15 @@ deposit_confirmation_run (void *cls, struct GNUNET_TIME_Timestamp refund_deadline = GNUNET_TIME_UNIT_ZERO_TS; struct TALER_Amount amount_without_fee; - struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_CoinSpendPublicKeyP coin_pubs[dcs->num_coins]; + const struct TALER_CoinSpendPublicKeyP *coin_pubps[dcs->num_coins]; + const struct TALER_CoinSpendSignatureP *coin_sigps[dcs->num_coins]; const struct TALER_MerchantPrivateKeyP *merchant_priv; struct TALER_MerchantPublicKeyP merchant_pub; const struct TALER_ExchangePublicKeyP *exchange_pub; const struct TALER_ExchangeSignatureP *exchange_sig; const json_t *wire_details; const json_t *contract_terms; - const struct TALER_CoinSpendPrivateKeyP *coin_priv; const struct TALER_EXCHANGE_Keys *keys; const struct TALER_EXCHANGE_SigningPublicKey *spk; const char *auditor_url; @@ -249,19 +250,19 @@ deposit_confirmation_run (void *cls, GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_exchange_pub (deposit_cmd, - dcs->coin_index, + 0, &exchange_pub)); GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_exchange_sig (deposit_cmd, - dcs->coin_index, + 0, &exchange_sig)); GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_timestamp (deposit_cmd, - dcs->coin_index, + 0, &exchange_timestamp)); GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_wire_deadline (deposit_cmd, - dcs->coin_index, + 0, &wire_deadline)); GNUNET_assert (NULL != exchange_timestamp); keys = TALER_TESTING_get_keys (is); @@ -283,12 +284,23 @@ deposit_confirmation_run (void *cls, GNUNET_assert (GNUNET_OK == TALER_JSON_merchant_wire_signature_hash (wire_details, &h_wire)); - GNUNET_assert (GNUNET_OK == - TALER_TESTING_get_trait_coin_priv (deposit_cmd, - dcs->coin_index, - &coin_priv)); - GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, - &coin_pub.eddsa_pub); + + for (unsigned int i = 0; i<dcs->num_coins; i++) + { + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_coin_priv (deposit_cmd, + i, + &coin_priv)); + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pubs[i].eddsa_pub); + coin_pubps[i] = &coin_pubs[i]; + GNUNET_assert (GNUNET_OK == + TALER_TESTING_get_trait_coin_sig (deposit_cmd, + i, + &coin_sigps[i])); + } GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_merchant_priv (deposit_cmd, &merchant_priv)); @@ -331,7 +343,9 @@ deposit_confirmation_run (void *cls, *wire_deadline, refund_deadline, &amount_without_fee, - &coin_pub, + dcs->num_coins, + coin_pubps, + coin_sigps, &merchant_pub, exchange_pub, exchange_sig, @@ -385,7 +399,7 @@ deposit_confirmation_cleanup (void *cls, struct TALER_TESTING_Command TALER_TESTING_cmd_deposit_confirmation (const char *label, const char *deposit_reference, - unsigned int coin_index, + unsigned int num_coins, const char *amount_without_fee, unsigned int expected_response_code) { @@ -393,7 +407,7 @@ TALER_TESTING_cmd_deposit_confirmation (const char *label, dcs = GNUNET_new (struct DepositConfirmationState); dcs->deposit_reference = deposit_reference; - dcs->coin_index = coin_index; + dcs->num_coins = num_coins; dcs->amount_without_fee = amount_without_fee; dcs->expected_response_code = expected_response_code; diff --git a/src/testing/testing_api_cmd_batch_deposit.c b/src/testing/testing_api_cmd_batch_deposit.c @@ -59,6 +59,11 @@ struct Coin struct TALER_Amount deposit_fee; /** + * Our coin signature. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** * Reference to any command that is able to provide a coin, * possibly using $LABEL#$INDEX notation. */ @@ -151,10 +156,9 @@ struct BatchDepositState struct GNUNET_SCHEDULER_Task *retry_task; /** - * Array of @e num_coins signatures from the exchange on the - * deposit confirmation. + * Deposit confirmation signature from the exchange. */ - struct TALER_ExchangeSignatureP *exchange_sigs; + struct TALER_ExchangeSignatureP exchange_sig; /** * Reference to previous deposit operation. @@ -205,19 +209,10 @@ batch_deposit_cb (void *cls, } if (MHD_HTTP_OK == dr->hr.http_status) { - if (ds->num_coins != dr->details.ok.num_signatures) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (ds->is); - return; - } ds->deposit_succeeded = GNUNET_YES; ds->exchange_timestamp = dr->details.ok.deposit_timestamp; ds->exchange_pub = *dr->details.ok.exchange_pub; - ds->exchange_sigs = GNUNET_memdup (dr->details.ok.exchange_sigs, - dr->details.ok.num_signatures - * sizeof (struct - TALER_ExchangeSignatureP)); + ds->exchange_sig = *dr->details.ok.exchange_sig; } TALER_TESTING_interpreter_next (ds->is); } @@ -373,6 +368,7 @@ batch_deposit_run (void *cls, ds->refund_deadline, coin_priv, &cdd->coin_sig); + coin->coin_sig = cdd->coin_sig; } GNUNET_assert (NULL == ds->dh); @@ -439,7 +435,6 @@ batch_deposit_cleanup (void *cls, for (unsigned int i = 0; i<ds->num_coins; i++) GNUNET_free (ds->coins[i].coin_reference); GNUNET_free (ds->coins); - GNUNET_free (ds->exchange_sigs); json_decref (ds->wire_details); json_decref (ds->contract_terms); GNUNET_free (ds); @@ -495,10 +490,10 @@ batch_deposit_traits (void *cls, struct TALER_TESTING_Trait traits[] = { /* First two traits are only available if ds->traits is #GNUNET_YES */ - TALER_TESTING_make_trait_exchange_pub (index, + TALER_TESTING_make_trait_exchange_pub (0, &ds->exchange_pub), - TALER_TESTING_make_trait_exchange_sig (index, - &ds->exchange_sigs[index]), + TALER_TESTING_make_trait_exchange_sig (0, + &ds->exchange_sig), /* These traits are always available */ TALER_TESTING_make_trait_wire_details (ds->wire_details), TALER_TESTING_make_trait_contract_terms (ds->contract_terms), @@ -507,6 +502,8 @@ batch_deposit_traits (void *cls, age_commitment_proof), TALER_TESTING_make_trait_coin_priv (index, coin_spent_priv), + TALER_TESTING_make_trait_coin_sig (index, + &coin->coin_sig), TALER_TESTING_make_trait_deposit_amount (index, &coin->amount), TALER_TESTING_make_trait_deposit_fee_amount (index, diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c @@ -69,6 +69,11 @@ struct DepositState unsigned int coin_index; /** + * Our coin signature. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** * Wire details of who is depositing -- this would be merchant * wire details in a normal scenario. */ @@ -258,11 +263,10 @@ deposit_cb (void *cls, } if (MHD_HTTP_OK == dr->hr.http_status) { - GNUNET_assert (1 == dr->details.ok.num_signatures); ds->deposit_succeeded = GNUNET_YES; ds->exchange_timestamp = dr->details.ok.deposit_timestamp; ds->exchange_pub = *dr->details.ok.exchange_pub; - ds->exchange_sig = dr->details.ok.exchange_sigs[0]; + ds->exchange_sig = *dr->details.ok.exchange_sig; } TALER_TESTING_interpreter_next (ds->is); } @@ -287,7 +291,6 @@ deposit_run (void *cls, const struct TALER_AgeCommitmentHash *phac; const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; const struct TALER_DenominationSignature *denom_pub_sig; - struct TALER_CoinSpendSignatureP coin_sig; struct TALER_MerchantPublicKeyP merchant_pub; struct TALER_PrivateContractHashP h_contract_terms; enum TALER_ErrorCode ec; @@ -449,14 +452,14 @@ deposit_run (void *cls, &merchant_pub, ds->refund_deadline, coin_priv, - &coin_sig); + &ds->coin_sig); } GNUNET_assert (NULL == ds->dh); { struct TALER_EXCHANGE_CoinDepositDetail cdd = { .amount = ds->amount, .coin_pub = coin_pub, - .coin_sig = coin_sig, + .coin_sig = ds->coin_sig, .denom_sig = *denom_pub_sig, .h_denom_pub = denom_pub->h_key, .h_age_commitment = {{{0}}}, @@ -595,6 +598,8 @@ deposit_traits (void *cls, /* These traits are always available */ TALER_TESTING_make_trait_coin_priv (0, coin_spent_priv), + TALER_TESTING_make_trait_coin_sig (0, + &ds->coin_sig), TALER_TESTING_make_trait_age_commitment_proof (0, age_commitment_proof), TALER_TESTING_make_trait_h_age_commitment (0, diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021, 2022 Taler Systems SA + Copyright (C) 2021-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -78,12 +78,12 @@ struct TALER_DepositConfirmationPS * Amount to be deposited, excluding fee. Calculated from the * amount with fee and the fee from the deposit request. */ - struct TALER_AmountNBO amount_without_fee; + struct TALER_AmountNBO total_without_fee; /** - * The public key of the coin that was deposited. + * Hash over all of the coin signatures. */ - struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode h_coin_sigs; /** * The Merchant's public key. Allows the merchant to later refund @@ -105,8 +105,9 @@ TALER_exchange_online_deposit_confirmation_sign ( struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins], const struct TALER_MerchantPublicKeyP *merchant_pub, struct TALER_ExchangePublicKeyP *pub, struct TALER_ExchangeSignatureP *sig) @@ -119,14 +120,21 @@ TALER_exchange_online_deposit_confirmation_sign ( .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp), .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline), .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline), - .coin_pub = *coin_pub, .merchant_pub = *merchant_pub }; - + struct GNUNET_HashContext *hc; + + hc = GNUNET_CRYPTO_hash_context_start (); + for (unsigned int i = 0; i<num_coins; i++) + GNUNET_CRYPTO_hash_context_read (hc, + coin_sigs[i], + sizeof (*coin_sigs[i])); + GNUNET_CRYPTO_hash_context_finish (hc, + &dcs.h_coin_sigs); if (NULL != h_policy) dcs.h_policy = *h_policy; - TALER_amount_hton (&dcs.amount_without_fee, - amount_without_fee); + TALER_amount_hton (&dcs.total_without_fee, + total_without_fee); return scb (&dcs.purpose, pub, sig); @@ -141,8 +149,9 @@ TALER_exchange_online_deposit_confirmation_verify ( struct GNUNET_TIME_Timestamp exchange_timestamp, struct GNUNET_TIME_Timestamp wire_deadline, struct GNUNET_TIME_Timestamp refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins], const struct TALER_MerchantPublicKeyP *merchant_pub, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct TALER_ExchangeSignatureP *exchange_sig) @@ -155,14 +164,21 @@ TALER_exchange_online_deposit_confirmation_verify ( .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp), .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline), .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline), - .coin_pub = *coin_pub, .merchant_pub = *merchant_pub }; - + struct GNUNET_HashContext *hc; + + hc = GNUNET_CRYPTO_hash_context_start (); + for (unsigned int i = 0; i<num_coins; i++) + GNUNET_CRYPTO_hash_context_read (hc, + coin_sigs[i], + sizeof (*coin_sigs[i])); + GNUNET_CRYPTO_hash_context_finish (hc, + &dcs.h_coin_sigs); if (NULL != h_policy) dcs.h_policy = *h_policy; - TALER_amount_hton (&dcs.amount_without_fee, - amount_without_fee); + TALER_amount_hton (&dcs.total_without_fee, + total_without_fee); if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, &dcs,