diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 1902eb298..7bd1b3248 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -1513,6 +1513,11 @@ enum TALER_EXCHANGE_ReserveTransactionType */ TALER_EXCHANGE_RTT_WITHDRAWAL, + /** + * Age-Withdrawal from the reserve. + */ + TALER_EXCHANGE_RTT_AGEWITHDRAWAL, + /** * /recoup operation. */ @@ -1608,6 +1613,28 @@ struct TALER_EXCHANGE_ReserveHistoryEntry struct TALER_Amount fee; } withdraw; + /** + * Information about withdraw operation. + * @e type is #TALER_EXCHANGE_RTT_AGEWITHDRAWAL. + */ + struct + { + /** + * Signature authorizing the withdrawal for outgoing transaction. + */ + json_t *out_authorization_sig; + + /** + * Maximum age commited + */ + uint8_t max_age; + + /** + * Fee that was charged for the withdrawal. + */ + struct TALER_Amount fee; + } age_withdraw; + /** * Information provided if the reserve was filled via /recoup. * @e type is #TALER_EXCHANGE_RTT_RECOUP. @@ -2670,13 +2697,59 @@ struct TALER_EXCHANGE_AgeWithdrawCoinInput * The master secret from which we derive all other relevant values for * the coin: private key, nonces (if applicable) and age restriction */ - const struct TALER_PlanchetMasterSecretP secrets[TALER_CNC_KAPPA]; + struct TALER_PlanchetMasterSecretP secrets[TALER_CNC_KAPPA]; /** * The denomination of the coin. Must support age restriction, i.e * its .keys.age_mask MUST not be 0 */ - const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + struct TALER_EXCHANGE_DenomPublicKey *denom_pub; +}; + + +/** + * All the details about a coin that are generated during age-withdrawal and + * that may be needed for future operations on the coin. + */ +struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails +{ + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Hash of the public key of the coin. + */ + struct TALER_CoinPubHashP h_coin_pub; + + /** + * Value used to blind the key for the signature. + * Needed for recoup operations. + */ + union TALER_DenominationBlindingKeyP blinding_key; + + /** + * The age commitment, proof for the coin, derived from the + * Master secret and maximum age in the originating request + */ + struct TALER_AgeCommitmentProof age_commitment_proof; + + /** + * The hash of the age commitment + */ + struct TALER_AgeCommitmentHash h_age_commitment; + + /** + * Values contributed from the exchange during the + * withdraw protocol. + */ + struct TALER_ExchangeWithdrawValues alg_values; + + /** + * The planchet constructed + */ + struct TALER_PlanchetDetail planchet; }; /** @@ -2705,39 +2778,38 @@ struct TALER_EXCHANGE_AgeWithdrawResponse struct { /** - * Index that should not be revealed during the age-withdraw reveal phase. - * The struct TALER_PlanchetMasterSecretP * from the request - * with this index are the ones to keep. + * Index that should not be revealed during the age-withdraw reveal + * phase. */ uint8_t noreveal_index; /** - * The commitment of the call to /age-withdraw + * The commitment of the age-withdraw request, needed for the + * subsequent call to /age-withdraw/$ACH/reveal */ struct TALER_AgeWithdrawCommitmentHashP h_commitment; /** - * The algorithm specific values (for CS) need for the coins that were - * retrieved from /csr-withdraw. + * The number of elements in @e coins, each referring to + * TALER_CNC_KAPPA elements */ - struct TALER_ExchangeWithdrawValues *alg_values; + size_t num_coins; /** - * Number of elements in @e alg_values, same as number coin candidates.from - * the request. + * The computed details of the non-revealed @e num_coins coins to keep. */ - size_t num_alg_values; + const struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails *coin_details; /** - * Signature of the exchange over the origina TALER_AgeWithdrawRequestPS + * The array of blinded hashes of the non-revealed + * @e num_coins coins, needed for the reveal step; */ - struct TALER_ExchangeSignatureP exchange_sig; + const struct TALER_BlindedCoinHashP *blinded_coin_hs; /** - * Key used by the exchange for @e exchange_sig + * Key used by the exchange to sign the response. */ struct TALER_ExchangePublicKeyP exchange_pub; - } ok; } details; }; @@ -2809,7 +2881,6 @@ struct TALER_EXCHANGE_AgeWithdrawBlindedInput * Blinded Planchets */ struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA]; - }; /** @@ -2840,19 +2911,16 @@ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse uint8_t noreveal_index; /** - * The commitment of the call to /age-withdraw + * The commitment of the call to age-withdraw, needed for the subsequent + * call to /age-withdraw/$ACH/reveal. */ struct TALER_AgeWithdrawCommitmentHashP h_commitment; /** - * Signature of the exchange over the origina TALER_AgeWithdrawRequestPS - */ - struct TALER_ExchangeSignatureP exchange_sig; - - /** - * Key used by the exchange for @e exchange_sig + * Key used by the exchange to sign the response. */ struct TALER_ExchangePublicKeyP exchange_pub; + } ok; } details; @@ -2959,17 +3027,17 @@ struct TALER_EXCHANGE_AgeWithdrawRevealResponse struct { /** - * Number of coins returned. + * Number of signatures returned. */ - unsigned int num_coins; + unsigned int num_sigs; /** - * Array of @e num_coins values about the coins obtained via the reveal - * operation. The array give these coins in the same order (and should - * have the same length) in which the original age-withdraw request - * specified the respective denomination keys. + * Array of @e num_coins blinded denomination signatures, giving each + * coin its value and validity. The array give these coins in the same + * order (and should have the same length) in which the original + * age-withdraw request specified the respective denomination keys. */ - const struct TALER_EXCHANGE_RevealedCoinInfo *revealed_coins; + const struct TALER_BlindedDenominationSignature *blinded_denom_sigs; } ok; } details; @@ -2994,10 +3062,8 @@ typedef void * @param exchange_url The base url of the exchange * @param num_coins The number of elements in @e coin_inputs and @e alg_values * @param coin_inputs The input for the coins to withdraw, same as in the previous call to /age-withdraw - * @param alg_values The algorithm specific parameters per coin, from the result to the previous call to /age-withdraw * @param noreveal_index The index into each of the kappa coin candidates, that should not be revealed to the exchange * @param h_commitment The commmitment from the previous call to /age-withdraw - * @param max_age maximum age, as used in the to /age-withdraw * @param res_cb A callback for the result, maybe NULL * @param res_cb_cls A closure for @e res_cb, maybe NULL * @return a handle for this request; NULL if the argument was invalid. @@ -3010,10 +3076,8 @@ TALER_EXCHANGE_age_withdraw_reveal ( size_t num_coins, const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[static num_coins], - const struct TALER_ExchangeWithdrawValues alg_values[static num_coins], uint8_t noreveal_index, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, - uint8_t max_age, TALER_EXCHANGE_AgeWithdrawRevealCallback res_cb, void *res_cb_cls); @@ -3034,7 +3098,8 @@ TALER_EXCHANGE_age_withdraw_reveal_cancel ( /** * Information needed to melt (partially spent) coins to obtain fresh coins * that are unlinkable to the original coin(s). Note that melting more than - * one coin in a single request will make those coins linkable, so we only melt one coin at a time. + * one coin in a single request will make those coins linkable, so we only melt + * one coin at a time. */ struct TALER_EXCHANGE_RefreshData { diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 3cd0b145f..c99f7c3b3 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -2715,8 +2715,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, op (refund_deadline, const struct GNUNET_TIME_Timestamp) \ op (exchange_pub, const struct TALER_ExchangePublicKeyP) \ op (exchange_sig, const struct TALER_ExchangeSignatureP) \ - op (blinding_key, const union TALER_DenominationBlindingKeyP) - + op (blinding_key, const union TALER_DenominationBlindingKeyP) \ + op (h_blinded_coin, const struct TALER_BlindedCoinHashP) TALER_TESTING_SIMPLE_TRAITS (TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT) diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c index b5da232ed..c68fe67d2 100644 --- a/src/lib/exchange_api_age_withdraw.c +++ b/src/lib/exchange_api_age_withdraw.c @@ -47,36 +47,9 @@ struct CoinCandidate struct TALER_PlanchetMasterSecretP secret; /** - * Age commitment for the coin candidates, calculated from the @e ps and a - * given maximum age + * The details derived form the master secrets */ - struct TALER_AgeCommitmentProof age_commitment_proof; - - /** - * Age commitment for the coin. - */ - struct TALER_AgeCommitmentHash h_age_commitment; - - /** - * blinding secret - */ - union TALER_DenominationBlindingKeyP blinding_key; - - /** - * Private key of the coin we are withdrawing. - */ - struct TALER_CoinSpendPrivateKeyP coin_priv; - - /** - * Values of the @cipher selected - */ - struct TALER_ExchangeWithdrawValues alg_values; - - /** - * Hash of the public key of the coin we are signing. - */ - struct TALER_CoinPubHashP h_coin_pub; - + struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details; /** * Blinded hash of the coin @@ -340,11 +313,12 @@ reserve_age_withdraw_ok ( .hr.http_status = MHD_HTTP_OK, .details.ok.h_commitment = awbh->h_commitment }; + struct TALER_ExchangeSignatureP exchange_sig; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_uint8 ("noreaveal_index", &response.details.ok.noreveal_index), GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &response.details.ok.exchange_sig), + &exchange_sig), GNUNET_JSON_spec_fixed_auto ("exchange_pub", &response.details.ok.exchange_pub) }; @@ -363,7 +337,7 @@ reserve_age_withdraw_ok ( &awbh->h_commitment, response.details.ok.noreveal_index, &response.details.ok.exchange_pub, - &response.details.ok.exchange_sig)) + &exchange_sig)) { GNUNET_break_op (0); return GNUNET_SYSERR; @@ -785,21 +759,26 @@ copy_results ( { struct TALER_EXCHANGE_AgeWithdrawHandle *awh = cls; uint8_t idx = awbr->details.ok.noreveal_index; - struct TALER_ExchangeWithdrawValues alg_values[awh->num_coins]; + struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details[awh->num_coins]; + struct TALER_BlindedCoinHashP blinded_coin_hs[awh->num_coins]; struct TALER_EXCHANGE_AgeWithdrawResponse resp = { .hr = awbr->hr, .details = { .ok = { .noreveal_index = awbr->details.ok.noreveal_index, .h_commitment = awbr->details.ok.h_commitment, .exchange_pub = awbr->details.ok.exchange_pub, - .exchange_sig = awbr->details.ok.exchange_sig, - .num_alg_values = awh->num_coins, - .alg_values = alg_values}, + .num_coins = awh->num_coins, + .coin_details = details, + .blinded_coin_hs = blinded_coin_hs}, }, }; for (size_t n = 0; n< awh->num_coins; n++) - alg_values[n] = awh->coin_data[n].coin_candidates[idx].alg_values; + { + details[n] = awh->coin_data[n].coin_candidates[idx].details; + details[n].planchet = awh->coin_data[n].planchet_details[idx]; + blinded_coin_hs[n] = awh->coin_data[n].coin_candidates[idx].blinded_coin_h; + } awh->callback (awh->callback_cls, &resp); @@ -915,23 +894,23 @@ csr_withdraw_done ( { bool success = false; /* Complete the initialization of the coin with CS denomination */ - can->alg_values = csrr->details.ok.alg_values; + can->details.alg_values = csrr->details.ok.alg_values; TALER_planchet_setup_coin_priv (&can->secret, - &can->alg_values, - &can->coin_priv); + &can->details.alg_values, + &can->details.coin_priv); TALER_planchet_blinding_secret_create (&can->secret, - &can->alg_values, - &can->blinding_key); + &can->details.alg_values, + &can->details.blinding_key); /* This initializes the 2nd half of the can->planchet_detail.blinded_planchet! */ do { if (GNUNET_OK != TALER_planchet_prepare (&csr->denom_pub->key, - &can->alg_values, - &can->blinding_key, - &can->coin_priv, - &can->h_age_commitment, - &can->h_coin_pub, + &can->details.alg_values, + &can->details.blinding_key, + &can->details.coin_priv, + &can->details.h_age_commitment, + &can->details.h_coin_pub, planchet)) { GNUNET_break (0); @@ -1029,29 +1008,29 @@ prepare_coins ( &can->secret, &input->denom_pub->key.age_mask, awh->max_age, - &can->age_commitment_proof)); + &can->details.age_commitment_proof)); - TALER_age_commitment_hash (&can->age_commitment_proof.commitment, - &can->h_age_commitment); + TALER_age_commitment_hash (&can->details.age_commitment_proof.commitment, + &can->details.h_age_commitment); switch (input->denom_pub->key.cipher) { case TALER_DENOMINATION_RSA: { - can->alg_values.cipher = TALER_DENOMINATION_RSA; + can->details.alg_values.cipher = TALER_DENOMINATION_RSA; TALER_planchet_setup_coin_priv (&can->secret, - &can->alg_values, - &can->coin_priv); + &can->details.alg_values, + &can->details.coin_priv); TALER_planchet_blinding_secret_create (&can->secret, - &can->alg_values, - &can->blinding_key); + &can->details.alg_values, + &can->details.blinding_key); FAIL_IF (GNUNET_OK != TALER_planchet_prepare (&cd->denom_pub.key, - &can->alg_values, - &can->blinding_key, - &can->coin_priv, - &can->h_age_commitment, - &can->h_coin_pub, + &can->details.alg_values, + &can->details.blinding_key, + &can->details.coin_priv, + &can->details.h_age_commitment, + &can->details.h_coin_pub, planchet)); FAIL_IF (GNUNET_OK != TALER_coin_ev_hash (&planchet->blinded_planchet, diff --git a/src/lib/exchange_api_age_withdraw_reveal.c b/src/lib/exchange_api_age_withdraw_reveal.c index fcb551a9e..75707a4e4 100644 --- a/src/lib/exchange_api_age_withdraw_reveal.c +++ b/src/lib/exchange_api_age_withdraw_reveal.c @@ -47,19 +47,12 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle /* The age-withdraw commitment */ struct TALER_AgeWithdrawCommitmentHashP h_commitment; - /* The maximum age */ - uint8_t max_age; - /* Number of coins */ size_t num_coins; /* The @e num_coins * kappa coin secrets from the age-withdraw commitment */ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coins_input; - /* The @e num_coins algorithm- and coin-specific parameters from the - * previous call to /age-withdraw. */ - const struct TALER_ExchangeWithdrawValues *alg_values; - /* The url for the reveal request */ const char *request_url; @@ -81,82 +74,6 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle }; -static enum GNUNET_GenericReturnValue -reveal_coin ( - const struct TALER_PlanchetMasterSecretP *secret, - const struct TALER_DenominationPublicKey *denom_pub, - const struct TALER_ExchangeWithdrawValues *alg_values, - const struct TALER_BlindedDenominationSignature *blind_sig, - uint8_t max_age, - struct TALER_EXCHANGE_RevealedCoinInfo *revealed_coin) -{ - enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; - -#define BREAK_ON_FAILURE(call) \ - do { \ - if (GNUNET_OK != (call)) { GNUNET_break (0); break; } \ - } while(0) - - do { - struct TALER_CoinPubHashP h_coin_pub; - struct TALER_PlanchetDetail planchet_detail; - const struct TALER_AgeCommitmentHash *hac = NULL; - struct TALER_FreshCoin fresh_coin; - - TALER_planchet_setup_coin_priv (secret, - alg_values, - &revealed_coin->coin_priv); - - TALER_planchet_blinding_secret_create (secret, - alg_values, - &revealed_coin->bks); - - revealed_coin->age_commitment_proof = NULL; - if (0 < max_age) - { - BREAK_ON_FAILURE ( - TALER_age_restriction_from_secret ( - secret, - &denom_pub->age_mask, - max_age, - revealed_coin->age_commitment_proof)); - - TALER_age_commitment_hash ( - &revealed_coin->age_commitment_proof->commitment, - &revealed_coin->h_age_commitment); - - hac = &revealed_coin->h_age_commitment; - } - - BREAK_ON_FAILURE ( - TALER_planchet_prepare (denom_pub, - alg_values, - &revealed_coin->bks, - &revealed_coin->coin_priv, - hac, - &h_coin_pub, - &planchet_detail)); - - BREAK_ON_FAILURE ( - TALER_planchet_to_coin (denom_pub, - blind_sig, - &revealed_coin->bks, - &revealed_coin->coin_priv, - &revealed_coin->h_age_commitment, - &h_coin_pub, - alg_values, - &fresh_coin)); - - /* success */ - revealed_coin->sig = fresh_coin.sig; - ret = GNUNET_OK; - } while(0); - - return ret; -#undef BREAK_ON_FAILURE -} - - /** * We got a 200 OK response for the /age-withdraw/$ACH/reveal operation. * Extract the signed blindedcoins and return it to the caller. @@ -197,16 +114,14 @@ age_withdraw_reveal_ok ( } { - struct TALER_EXCHANGE_RevealedCoinInfo revealed_coins[awrh->num_coins]; + struct TALER_BlindedDenominationSignature denom_sigs[awrh->num_coins]; /* Reconstruct the coins and unblind the signatures */ for (size_t n = 0; n < awrh->num_coins; n++) { - enum GNUNET_GenericReturnValue ret; - struct TALER_BlindedDenominationSignature blinded_sig; json_t *j_sig = json_array_get (j_sigs, n); struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("", &blinded_sig), + GNUNET_JSON_spec_fixed_auto ("", &denom_sigs[n]), GNUNET_JSON_spec_end () }; @@ -218,19 +133,10 @@ age_withdraw_reveal_ok ( GNUNET_break_op (0); return GNUNET_SYSERR; } - ret = reveal_coin (&awrh->coins_input[n].secrets[awrh->noreveal_index], - &awrh->coins_input[n].denom_pub->key, - &awrh->alg_values[n], - &blinded_sig, - awrh->max_age, - &revealed_coins[n]); - - if (GNUNET_OK != ret) - return ret; } - response.details.ok.num_coins = awrh->num_coins; - response.details.ok.revealed_coins = revealed_coins; + response.details.ok.num_sigs = awrh->num_coins; + response.details.ok.blinded_denom_sigs = denom_sigs; awrh->callback (awrh->callback_cls, &response); /* Make sure the callback isn't called again */ @@ -510,24 +416,19 @@ TALER_EXCHANGE_age_withdraw_reveal ( size_t num_coins, const struct TALER_EXCHANGE_AgeWithdrawCoinInput coins_input[static num_coins], - const struct TALER_ExchangeWithdrawValues alg_values[static num_coins], uint8_t noreveal_index, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, - uint8_t max_age, TALER_EXCHANGE_AgeWithdrawRevealCallback reveal_cb, void *reveal_cb_cls) { struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh = GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawRevealHandle); awrh->noreveal_index = noreveal_index; - awrh->callback = reveal_cb; - awrh->callback_cls = reveal_cb_cls; awrh->h_commitment = *h_commitment; awrh->num_coins = num_coins; awrh->coins_input = coins_input; - awrh->alg_values = alg_values; - awrh->max_age = max_age; - + awrh->callback = reveal_cb; + awrh->callback_cls = reveal_cb_cls; if (GNUNET_OK != prepare_url (exchange_url, diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 337fbabaf..08ca4b4e5 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -765,6 +765,8 @@ TALER_EXCHANGE_free_reserve_history ( break; case TALER_EXCHANGE_RTT_WITHDRAWAL: break; + case TALER_EXCHANGE_RTT_AGEWITHDRAWAL: + break; case TALER_EXCHANGE_RTT_RECOUP: break; case TALER_EXCHANGE_RTT_CLOSING: diff --git a/src/lib/notizen.md b/src/lib/notizen.md new file mode 100644 index 000000000..835fad9e2 --- /dev/null +++ b/src/lib/notizen.md @@ -0,0 +1,236 @@ +# Notes re: planchets and blinding + +## `TALER_denom_blind()` + +``` +Blind coin for blind signing with @a dk using blinding secret @a coin_bks. +``` + +- `@param[out] c_hash resulting hashed coin` +- `@param[out] blinded_planchet planchet data to initialize` + +## `TALER_planchet_prepare()` + +Prepare a planchet for withdrawal. Creates and blinds a coin. + +- calls `TALER_denom_blind()!` +- `@param[out] c_hash set to the hash of the public key of the coin (needed later)` +- `@param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() and other withdraw operations, pd->blinded_planchet.cipher will be set to cipher from @a dk` + + +## `TALER_coin_ev_hash` + +Compute the hash of a blinded coin. + +- `@param blinded_planchet blinded planchet` +- `@param denom_hash hash of the denomination publick key` +- `@param[out] bch where to write the hash, type struct TALER_BlindedCoinHashP` + +**Where is this called!?** + +``` +taler-exchange-httpd_refreshes_reveal.c +605: TALER_coin_ev_hash (&rrc->blinded_planchet, + +taler-exchange-httpd_recoup.c +290: TALER_coin_ev_hash (&blinded_planchet, + +taler-exchange-httpd_withdraw.c +581: TALER_coin_ev_hash (&wc.blinded_planchet, + +taler-exchange-httpd_age-withdraw_reveal.c +350: ret = TALER_coin_ev_hash (&detail.blinded_planchet, + +taler-exchange-httpd_recoup-refresh.c +284: TALER_coin_ev_hash (&blinded_planchet, + +taler-exchange-httpd_age-withdraw.c +279: ret = TALER_coin_ev_hash (&awc->coin_evs[c], +884: TALER_coin_ev_hash (&awc->coin_evs[i], + +taler-exchange-httpd_batch-withdraw.c +832: TALER_coin_ev_hash (&pc->blinded_planchet, +``` + + +## `TALER_coin_pub_hash` + +Compute the hash of a coin. + +- `@param coin_pub public key of the coin` +- `@param age_commitment_hash hash of the age commitment vector. NULL, if no age commitment was set` +- `@param[out] coin_h where to write the hash` + +**Where is this called!?** + +### In `lib/crypto.c`, function `TALER_test_coin_valid`. + +``` +Check if a coin is valid; that is, whether the denomination key +exists, is not expired, and the signature is correct. + +@param coin_public_info the coin public info to check for validity +@param denom_pub denomination key, must match @a coin_public_info's `denom_pub_hash` +@return #GNUNET_YES if the coin is valid, + #GNUNET_NO if it is invalid + #GNUNET_SYSERR if an internal error occurred +``` + +It then calls `TALER_denom_pub_verify` on the result of `TALER_coin_pub_hash` and the signature + + +### In `util/denom.c`, function `TALER_denom_blind` + +## `TALER_EXCHANGE_batch_withdraw` vs `TALER_EXCHANGE_batch_withdraw2` + +### `TALER_EXCHANGE_batch_withdraw` + +``` +/** + * Withdraw multiple coins from the exchange using a /reserves/$RESERVE_PUB/batch-withdraw + * request. This API is typically used by a wallet to withdraw many coins from a + * reserve. + * + * Note that to ensure that no money is lost in case of hardware + * failures, the caller must have committed (most of) the arguments to + * disk before calling, and be ready to repeat the request with the + * same arguments in case of failures. + * + * @param curl_ctx The curl context to use + * @param exchange_url The base-URL of the exchange + * @param keys The /keys material from the exchange + * @param reserve_priv private key of the reserve to withdraw from + * @param wci_length number of entries in @a wcis + * @param wcis inputs that determine the planchets + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for @a res_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_BatchWithdrawHandle * +TALER_EXCHANGE_batch_withdraw ( + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePrivateKeyP *reserve_priv, + unsigned int wci_length, + const struct TALER_EXCHANGE_WithdrawCoinInput wcis[static wci_length], + TALER_EXCHANGE_BatchWithdrawCallback res_cb, + void *res_cb_cls); +``` + +### `TALER_EXCHANGE_batch_withdraw2` + +``` +/** + * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/batch-withdraw + * request. This API is typically used by a merchant to withdraw a tip + * where the blinding factor is unknown to the merchant. + * + * Note that to ensure that no money is lost in case of hardware + * failures, the caller must have committed (most of) the arguments to + * disk before calling, and be ready to repeat the request with the + * same arguments in case of failures. + * + * @param curl_ctx The curl context to use + * @param exchange_url The base-URL of the exchange + * @param keys The /keys material from the exchange + * @param pds array of planchet details of the planchet to withdraw + * @param pds_length number of entries in the @a pds array + * @param reserve_priv private key of the reserve to withdraw from + * @param res_cb the callback to call when the final result for this request is available + * @param res_cb_cls closure for @a res_cb + * @return NULL + * if the inputs are invalid (i.e. denomination key not with this exchange). + * In this case, the callback is not called. + */ +struct TALER_EXCHANGE_BatchWithdraw2Handle * +TALER_EXCHANGE_batch_withdraw2 ( + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePrivateKeyP *reserve_priv, + unsigned int pds_length, + const struct TALER_PlanchetDetail pds[static pds_length], + TALER_EXCHANGE_BatchWithdraw2Callback res_cb, + void *res_cb_cls); +``` + +### Differences + +| batch_withdraw | batch_withdraw2 | +|------------------------------------|------------------------| +| `TALER_EXCHANGE_WithdrawCoinInput` | `TALER_PlanchetDetail` | + + +``` +struct TALER_EXCHANGE_WithdrawCoinInput +{ + /** + * Denomination of the coin. + */ + const struct TALER_EXCHANGE_DenomPublicKey *pk; + + /** + * Master key material for the coin. + */ + const struct TALER_PlanchetMasterSecretP *ps; + + /** + * Age commitment for the coin. + */ + const struct TALER_AgeCommitmentHash *ach; + +}; +``` + + +``` +struct TALER_PlanchetDetail +{ + /** + * Hash of the denomination public key. + */ + struct TALER_DenominationHashP denom_pub_hash; + + /** + * The blinded planchet + */ + struct TALER_BlindedPlanchet { + /** + * Type of the sign blinded message + */ + enum TALER_DenominationCipher cipher; + + /** + * Details, depending on @e cipher. + */ + union + { + /** + * If we use #TALER_DENOMINATION_CS in @a cipher. + */ + struct TALER_BlindedCsPlanchet cs_blinded_planchet; + + /** + * If we use #TALER_DENOMINATION_RSA in @a cipher. + */ + struct TALER_BlindedRsaPlanchet rsa_blinded_planchet; + + } details; + } blinded_planchet; +}; + +``` + + + +## TODOs + +### Update documentation + +- [x] batch-withdraw needs error code for AgeRestrictionRequired +- [x] withdraw needs error code for AgeRestrictionRequired + + diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index c659f0ac6..068d94590 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -40,6 +40,7 @@ libtalertesting_la_LDFLAGS = \ -version-info 0:0:0 \ -no-undefined libtalertesting_la_SOURCES = \ + testing_api_cmd_age_withdraw.c \ testing_api_cmd_auditor_add_denom_sig.c \ testing_api_cmd_auditor_add.c \ testing_api_cmd_auditor_del.c \ diff --git a/src/testing/testing_api_cmd_age_withdraw.c b/src/testing/testing_api_cmd_age_withdraw.c index 56e65a990..edf292970 100644 --- a/src/testing/testing_api_cmd_age_withdraw.c +++ b/src/testing/testing_api_cmd_age_withdraw.c @@ -23,20 +23,76 @@ */ #include "platform.h" +#include "taler_exchange_service.h" #include "taler_json_lib.h" +#include #include #include #include "taler_signatures.h" #include "taler_extensions.h" #include "taler_testing_lib.h" +/* + * The output state of coin + */ +struct CoinOutputState +{ + + /** + * The calculated details during "age-withdraw", for the selected coin. + */ + struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details; + + /** + * The (wanted) value of the coin, MUST be the same as input.denom_pub.value; + */ + struct TALER_Amount amount; + + /** + * Reserve history entry that corresponds to this coin. + * Will be of type #TALER_EXCHANGE_RTT_AGEWITHDRAWAL. + */ + struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history; +}; + /** * State for a "age withdraw" CMD: */ struct AgeWithdrawState { - /* + + /** + * Interpreter state (during command) + */ + struct TALER_TESTING_Interpreter *is; + + /** + * The age-withdraw handle + */ + struct TALER_EXCHANGE_AgeWithdrawHandle *handle; + + /** + * Exchange base URL. Only used as offered trait. + */ + char *exchange_url; + + /** + * URI of the reserve we are withdrawing from. + */ + char *reserve_payto_uri; + + /** + * Private key of the reserve we are withdrawing from. + */ + struct TALER_ReservePrivateKeyP reserve_priv; + + /** + * Public key of the reserve we are withdrawing from. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** * Which reserve should we withdraw from? */ const char *reserve_reference; @@ -46,6 +102,11 @@ struct AgeWithdrawState */ unsigned int expected_response_code; + /** + * Age mask + */ + struct TALER_AgeMask mask; + /** * The maximum age we commit to */ @@ -55,8 +116,338 @@ struct AgeWithdrawState * Number of coins to withdraw */ size_t num_coins; + + /** + * The @e num_coins input that is provided to the + * `TALER_EXCHANGE_age_withdraw` API. + * Each contains kappa secrets, from which we will have + * to disclose kappa-1 in a subsequent age-withdraw-reveal operation. + */ + struct TALER_EXCHANGE_AgeWithdrawCoinInput *coin_inputs; + + /** + * The output state of @e num_coins coins, calculated during the + * "age-withdraw" operation. + */ + struct CoinOutputState *coin_outputs; + + /** + * The index returned by the exchange for the "age-withdraw" operation, + * of the kappa coin candidates that we do not disclose and keep. + */ + uint8_t noreveal_index; + + /** + * The blinded hashes of the non-revealed (to keep) @e num_coins coins. + */ + const struct TALER_BlindedCoinHashP *blinded_coin_hs; + + /** + * The hash of the commitment, needed for the reveal step. + */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment; + + /** + * Set to the KYC requirement payto hash *if* the exchange replied with a + * request for KYC. + */ + struct TALER_PaytoHashP h_payto; + + /** + * Set to the KYC requirement row *if* the exchange replied with + * a request for KYC. + */ + uint64_t requirement_row; + }; +/** + * Callback for the "age-withdraw" ooperation; It checks that the response + * code is expected and store the exchange signature in the state. + * + * @param cls Closure of type `struct AgeWithdrawState *` + * @param awr Repsonse details + */ +static void +age_withdraw_cb ( + void *cls, + const struct TALER_EXCHANGE_AgeWithdrawResponse *response) +{ + struct AgeWithdrawState *aws = cls; + struct TALER_TESTING_Interpreter *is = aws->is; + + aws->handle = NULL; + if (aws->expected_response_code != response->hr.http_status) + { + TALER_TESTING_unexpected_status_with_body (is, + response->hr.http_status, + aws->expected_response_code, + response->hr.reply); + return; + } + + switch (response->hr.http_status) + { + case MHD_HTTP_OK: + aws->noreveal_index = response->details.ok.noreveal_index; + aws->h_commitment = response->details.ok.h_commitment; + + GNUNET_assert (aws->num_coins == response->details.ok.num_coins); + for (size_t n = 0; n < aws->num_coins; n++) + { + aws->coin_outputs[n].details = response->details.ok.coin_details[n]; + TALER_age_commitment_proof_deep_copy ( + &response->details.ok.coin_details[n].age_commitment_proof, + &aws->coin_outputs[n].details.age_commitment_proof); + } + aws->blinded_coin_hs = response->details.ok.blinded_coin_hs; + break; + case MHD_HTTP_FORBIDDEN: + case MHD_HTTP_NOT_FOUND: + case MHD_HTTP_GONE: + /* nothing to check */ + break; + case MHD_HTTP_CONFLICT: + /* TODO[oec]: Add this to the response-type and handle it here */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Age withdraw test command does not YET support status code %u\n", + response->hr.http_status); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + /* TODO[oec]: Add this to response-type and handle it here */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Age withdraw test command does not YET support status code %u\n", + response->hr.http_status); + break; + default: + /* Unsupported status code (by test harness) */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Age withdraw test command does not support status code %u\n", + response->hr.http_status); + GNUNET_break (0); + break; + } + + /* We are done with this command, pick the next one */ + TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the command for age-withdraw. + */ +static void +age_withdraw_run ( + void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct AgeWithdrawState *aws = cls; + struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is); + const struct TALER_ReservePrivateKeyP *rp; + const struct TALER_TESTING_Command *create_reserve; + const struct TALER_EXCHANGE_DenomPublicKey *dpk; + + aws->is = is; + + /* Prepare the reserve related data */ + create_reserve + = TALER_TESTING_interpreter_lookup_command ( + is, + aws->reserve_reference); + + if (NULL == create_reserve) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (GNUNET_OK != + TALER_TESTING_get_trait_reserve_priv (create_reserve, + &rp)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + if (NULL == aws->exchange_url) + aws->exchange_url + = GNUNET_strdup (TALER_TESTING_get_exchange_url (is)); + aws->reserve_priv = *rp; + GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv, + &aws->reserve_pub.eddsa_pub); + aws->reserve_payto_uri + = TALER_reserve_make_payto (aws->exchange_url, + &aws->reserve_pub); + + aws->coin_inputs = GNUNET_new_array ( + aws->num_coins, + struct TALER_EXCHANGE_AgeWithdrawCoinInput); + + for (unsigned int i = 0; inum_coins; i++) + { + struct TALER_EXCHANGE_AgeWithdrawCoinInput *input = &aws->coin_inputs[i]; + struct CoinOutputState *cos = &aws->coin_outputs[i]; + + /* randomly create the secrets for the kappa coin-candidates */ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &input->secrets, + sizeof(input->secrets)); + /* Find denomination */ + dpk = TALER_TESTING_find_pk (keys, + &cos->amount, + true); /* _always_ use denominations with age-striction */ + if (NULL == dpk) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to determine denomination key at %s\n", + (NULL != cmd) ? cmd->label : ""); + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + /* We copy the denomination key, as re-querying /keys + * would free the old one. */ + input->denom_pub = TALER_EXCHANGE_copy_denomination_key (dpk); + cos->reserve_history.type = TALER_EXCHANGE_RTT_AGEWITHDRAWAL; + GNUNET_assert (0 <= + TALER_amount_add (&cos->reserve_history.amount, + &cos->amount, + &input->denom_pub->fees.withdraw)); + cos->reserve_history.details.withdraw.fee = input->denom_pub->fees.withdraw; + } + + /* Execute the age-withdraw protocol */ + aws->handle = + TALER_EXCHANGE_age_withdraw ( + TALER_TESTING_interpreter_get_context (is), + keys, + TALER_TESTING_get_exchange_url (is), + rp, + aws->num_coins, + aws->coin_inputs, + aws->max_age, + &age_withdraw_cb, + aws); + + if (NULL == aws->handle) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } +} + + +/** + * Free the state of a "age withdraw" CMD, and possibly cancel a + * pending operation thereof + * + * @param cls Closure of type `struct AgeWithdrawState` + * @param cmd The command beeing freed. + */ +static void +age_withdraw_cleanup ( + void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct AgeWithdrawState *aws = cls; + + if (NULL != aws->handle) + { + TALER_TESTING_command_incomplete (aws->is, + cmd->label); + TALER_EXCHANGE_age_withdraw_cancel (aws->handle); + aws->handle = NULL; + } + + for (size_t n = 0; n < aws->num_coins; n++) + { + struct TALER_EXCHANGE_AgeWithdrawCoinInput *in = &aws->coin_inputs[n]; + struct CoinOutputState *out = &aws->coin_outputs[n]; + + if (NULL != in->denom_pub) + { + TALER_EXCHANGE_destroy_denomination_key (in->denom_pub); + in->denom_pub = NULL; + } + TALER_age_commitment_proof_free (&out->details.age_commitment_proof); + } + GNUNET_free (aws->coin_inputs); + GNUNET_free (aws->coin_outputs); + GNUNET_free (aws->exchange_url); + GNUNET_free (aws->reserve_payto_uri); + GNUNET_free (aws); +} + + +/** + * Offer internal data of a "age withdraw" CMD state to other commands. + * + * @param cls Closure of type `struct AgeWithdrawState` + * @param[out] ret result (could be anything) + * @param trait name of the trait + * @param idx index number of the object to offer. + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +age_withdraw_traits ( + void *cls, + const void **ret, + const char *trait, + unsigned int idx) +{ + struct AgeWithdrawState *aws = cls; + uint8_t k = aws->noreveal_index; + struct TALER_EXCHANGE_AgeWithdrawCoinInput *in = &aws->coin_inputs[idx]; + struct CoinOutputState *out = &aws->coin_outputs[idx]; + struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails *details = + &aws->coin_outputs[idx].details; + struct TALER_TESTING_Trait traits[] = { + /* history entry MUST be first due to response code logic below! */ + TALER_TESTING_make_trait_reserve_history (idx, + &out->reserve_history), + TALER_TESTING_make_trait_denom_pub (idx, + in->denom_pub), + TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv), + TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub), + TALER_TESTING_make_trait_amounts (idx, + &out->amount), + /* TODO[oec]: add legal requirement to response and handle it here, as well + TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row), + TALER_TESTING_make_trait_h_payto (&aws->h_payto), + */ + TALER_TESTING_make_trait_h_blinded_coin (idx, + &aws->blinded_coin_hs[idx]), + TALER_TESTING_make_trait_payto_uri (aws->reserve_payto_uri), + TALER_TESTING_make_trait_exchange_url (aws->exchange_url), + TALER_TESTING_make_trait_coin_priv (idx, + &details->coin_priv), + TALER_TESTING_make_trait_planchet_secrets (idx, + &in->secrets[k]), + TALER_TESTING_make_trait_blinding_key (idx, + &details->blinding_key), + TALER_TESTING_make_trait_exchange_wd_value (idx, + &details->alg_values), + TALER_TESTING_make_trait_age_commitment_proof ( + idx, + &details->age_commitment_proof), + TALER_TESTING_make_trait_h_age_commitment ( + idx, + &details->h_age_commitment), + }; + + if (idx >= aws->num_coins) + return GNUNET_NO; + + return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK) + ? &traits[0] /* we have reserve history */ + : &traits[1], /* skip reserve history */ + ret, + trait, + idx); +} + struct TALER_TESTING_Command TALER_TESTING_cmd_age_withdraw (const char *label, @@ -71,60 +462,27 @@ TALER_TESTING_cmd_age_withdraw (const char *label, va_list ap; aws = GNUNET_new (struct AgeWithdrawState); - aws->max_age = max_age; aws->reserve_reference = reserve_reference; aws->expected_response_code = expected_response_code; + aws->mask = TALER_extensions_get_age_restriction_mask (); + aws->max_age = TALER_get_lowest_age (&aws->mask, max_age); cnt = 1; va_start (ap, amount); while (NULL != (va_arg (ap, const char *))) cnt++; aws->num_coins = cnt; - aws->coins = GNUNET_new_array (cnt, - struct CoinState); + aws->coin_outputs = GNUNET_new_array (cnt, + struct CoinOutputState); va_end (ap); va_start (ap, amount); - for (unsigned int i = 0; inum_coins; i++) + + for (unsigned int i = 0; inum_coins; i++) { - struct CoinState *cs = &ws->coins[i]; - - if (0 < age) - { - struct TALER_AgeCommitmentProof *acp; - struct TALER_AgeCommitmentHash *hac; - struct GNUNET_HashCode seed; - struct TALER_AgeMask mask; - - acp = GNUNET_new (struct TALER_AgeCommitmentProof); - hac = GNUNET_new (struct TALER_AgeCommitmentHash); - mask = TALER_extensions_get_age_restriction_mask (); - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, - &seed, - sizeof(seed)); - - if (GNUNET_OK != - TALER_age_restriction_commit ( - &mask, - age, - &seed, - acp)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to generate age commitment for age %d at %s\n", - age, - label); - GNUNET_assert (0); - } - - TALER_age_commitment_hash (&acp->commitment, - hac); - cs->age_commitment_proof = acp; - cs->h_age_commitment = hac; - } - + struct CoinOutputState *out = &aws->coin_outputs[i]; if (GNUNET_OK != TALER_string_to_amount (amount, - &cs->amount)) + &out->amount)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to parse amount `%s' at %s\n", @@ -132,19 +490,20 @@ TALER_TESTING_cmd_age_withdraw (const char *label, label); GNUNET_assert (0); } - /* move on to next vararg! */ - amount = va_arg (ap, const char *); } + + /* move on to next vararg! */ + amount = va_arg (ap, const char *); GNUNET_assert (NULL == amount); va_end (ap); { struct TALER_TESTING_Command cmd = { - .cls = ws, + .cls = aws, .label = label, .run = &age_withdraw_run, .cleanup = &age_withdraw_cleanup, - .traits = &age_withdraw_traits + .traits = &age_withdraw_traits, }; return cmd; @@ -152,4 +511,237 @@ TALER_TESTING_cmd_age_withdraw (const char *label, } +/** + * The state for the age-withdraw-reveal operation + */ +struct AgeWithdrawRevealState +{ + /** + * The reference to the CMD resembling the previous call to age-withdraw + */ + const char *age_withdraw_reference; + + /** + * The state to the previous age-withdraw command + */ + const struct AgeWithdrawState *aws; + + /** + * The expected response code from the call to the + * age-withdraw-reveal operation + */ + unsigned int expected_response_code; + + /** + * Interpreter state (during command) + */ + struct TALER_TESTING_Interpreter *is; + + /** + * The handle to the reveal-operation + */ + struct TALER_EXCHANGE_AgeWithdrawRevealHandle *handle; + + + /** + * Number of coins, extracted form the age withdraw command + */ + size_t num_coins; + + /** + * The signatures of the @e num_coins coins returned + */ + struct TALER_DenominationSignature *denom_sigs; + +}; + +/* + * Callback for the reveal response + * + * @param cls Closure of type `struct AgeWithdrawRevealState` + * @param awr The response + */ +static void +age_withdraw_reveal_cb ( + void *cls, + const struct TALER_EXCHANGE_AgeWithdrawRevealResponse *response) +{ + struct AgeWithdrawRevealState *awrs = cls; + struct TALER_TESTING_Interpreter *is = awrs->is; + + awrs->handle = NULL; + if (awrs->expected_response_code != response->hr.http_status) + { + TALER_TESTING_unexpected_status_with_body (is, + response->hr.http_status, + awrs->expected_response_code, + response->hr.reply); + return; + } + switch (response->hr.http_status) + { + case MHD_HTTP_OK: + { + const struct AgeWithdrawState *aws = awrs->aws; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got age-withdraw reveal success!\n"); + GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs); + for (size_t n = 0; n < awrs->num_coins; n++) + TALER_denom_sig_unblind (&awrs->denom_sigs[n], + &response->details.ok.blinded_denom_sigs[n], + &aws->coin_outputs[n].details.blinding_key, + &aws->coin_outputs[n].details.h_coin_pub, + &aws->coin_outputs[n].details.alg_values, + &aws->coin_inputs[n].denom_pub->key); + } + break; + case MHD_HTTP_NOT_FOUND: + case MHD_HTTP_FORBIDDEN: + /* nothing to check */ + break; + /* TODO[oec]: handle more cases !? */ + default: + /* Unsupported status code (by test harness) */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Age withdraw reveal test command does not support status code %u\n", + response->hr.http_status); + GNUNET_break (0); + break; + } + + /* We are done with this command, pick the next one */ + TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the command for age-withdraw-reveal + */ +static void +age_withdraw_reveal_run ( + void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct AgeWithdrawRevealState *awrs = cls; + const struct TALER_TESTING_Command *age_withdraw_cmd; + const struct AgeWithdrawState *aws; + + (void) cmd; + awrs->is = is; + + /* + * Get the command and state for the previous call to "age witdraw" + */ + age_withdraw_cmd = + TALER_TESTING_interpreter_get_command (is, + awrs->age_withdraw_reference); + if (NULL == age_withdraw_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + } + GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run); + aws = age_withdraw_cmd->cls; + awrs->aws = aws; + awrs->num_coins = aws->num_coins; + + awrs->handle = + TALER_EXCHANGE_age_withdraw_reveal ( + TALER_TESTING_interpreter_get_context (is), + TALER_TESTING_get_exchange_url (is), + aws->num_coins, + aws->coin_inputs, + aws->noreveal_index, + &aws->h_commitment, + age_withdraw_reveal_cb, + awrs); +} + + +/** + * Free the state of a "age-withdraw-reveal" CMD, and possibly + * cancel a pending operation thereof + * + * @param cls Closure of type `struct AgeWithdrawRevealState` + * @param cmd The command being freed. + */ +static void +age_withdraw_reveal_cleanup ( + void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct AgeWithdrawRevealState *awrs = cls; + + if (NULL != awrs->handle) + { + TALER_TESTING_command_incomplete (awrs->is, + cmd->label); + TALER_EXCHANGE_age_withdraw_reveal_cancel (awrs->handle); + awrs->handle = NULL; + } + GNUNET_free (awrs->denom_sigs); + awrs->denom_sigs = NULL; + GNUNET_free (awrs); +} + + +/** + * Offer internal data of a "age withdraw reveal" CMD state to other commands. + * + * @param cls Closure of they `struct AgeWithdrawRevealState` + * @param[out] ret result (could be anything) + * @param trait name of the trait + * @param idx index number of the object to offer. + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +age_withdraw_reveal_traits ( + void *cls, + const void **ret, + const char *trait, + unsigned int idx) +{ + struct AgeWithdrawRevealState *awrs = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_denom_sig (idx, + &awrs->denom_sigs[idx]), + /* FIXME: shall we provide the traits from the previous + * call to "age withdraw" as well? */ + }; + + if (idx >= awrs->num_coins) + return GNUNET_NO; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + idx); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_age_withdraw_reveal ( + const char *label, + const char *age_withdraw_reference, + unsigned int expected_response_code) +{ + struct AgeWithdrawRevealState *awrs = + GNUNET_new (struct AgeWithdrawRevealState); + + awrs->age_withdraw_reference = age_withdraw_reference; + awrs->expected_response_code = expected_response_code; + + struct TALER_TESTING_Command cmd = { + .cls = awrs, + .label = label, + .run = age_withdraw_reveal_run, + .cleanup = age_withdraw_reveal_cleanup, + .traits = age_withdraw_reveal_traits, + }; + + return cmd; +} + + /* end of testing_api_cmd_age_withdraw.c */ diff --git a/src/testing/testing_api_cmd_batch_withdraw.c b/src/testing/testing_api_cmd_batch_withdraw.c index da43a9aa9..41c74c3e2 100644 --- a/src/testing/testing_api_cmd_batch_withdraw.c +++ b/src/testing/testing_api_cmd_batch_withdraw.c @@ -23,6 +23,7 @@ * @author Marcello Stanisci */ #include "platform.h" +#include "taler_exchange_service.h" #include "taler_json_lib.h" #include #include @@ -217,7 +218,7 @@ reserve_batch_withdraw_cb (void *cls, /* nothing to check */ break; case MHD_HTTP_CONFLICT: - /* nothing to check */ + /* TODO[oec]: Check if age-requirement is the reason */ break; case MHD_HTTP_GONE: /* theoretically could check that the key was actually */ @@ -250,6 +251,7 @@ batch_withdraw_run (void *cls, struct TALER_TESTING_Interpreter *is) { struct BatchWithdrawState *ws = cls; + const struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is); const struct TALER_ReservePrivateKeyP *rp; const struct TALER_TESTING_Command *create_reserve; const struct TALER_EXCHANGE_DenomPublicKey *dpk; @@ -292,7 +294,7 @@ batch_withdraw_run (void *cls, struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i]; TALER_planchet_master_setup_random (&cs->ps); - dpk = TALER_TESTING_find_pk (TALER_TESTING_get_keys (is), + dpk = TALER_TESTING_find_pk (keys, &cs->amount, ws->age > 0); if (NULL == dpk) @@ -321,7 +323,7 @@ batch_withdraw_run (void *cls, ws->wsh = TALER_EXCHANGE_batch_withdraw ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), - TALER_TESTING_get_keys (is), + keys, rp, ws->num_coins, wcis, diff --git a/src/testing/testing_api_cmd_common.c b/src/testing/testing_api_cmd_common.c index 91138f361..2c29f4ec2 100644 --- a/src/testing/testing_api_cmd_common.c +++ b/src/testing/testing_api_cmd_common.c @@ -59,6 +59,20 @@ TALER_TESTING_history_entry_cmp ( that should be good enough. */ return 0; return 1; + case TALER_EXCHANGE_RTT_AGEWITHDRAWAL: + /* testing_api_cmd_age_withdraw doesn't set the out_authorization_sig, + so we cannot test for it here. but if the amount matches, + that should be good enough. */ + if ( (0 == + TALER_amount_cmp (&h1->amount, + &h2->amount)) && + (0 == + TALER_amount_cmp (&h1->details.age_withdraw.fee, + &h2->details.age_withdraw.fee)) && + (h1->details.age_withdraw.max_age == + h2->details.age_withdraw.max_age)) + return 0; + return 1; case TALER_EXCHANGE_RTT_RECOUP: /* exchange_sig, exchange_pub and timestamp are NOT available from the original recoup response, hence here NOT check(able/ed) */ diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c index 2a8e3d0b7..781028bf1 100644 --- a/src/testing/testing_api_loop.c +++ b/src/testing/testing_api_loop.c @@ -31,6 +31,9 @@ #include "taler_testing_lib.h" +/** + * The interpreter and its state + */ struct TALER_TESTING_Interpreter {