gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] branch master updated (2f8e3d31 -> db9fb04e)


From: gnunet
Subject: [taler-exchange] branch master updated (2f8e3d31 -> db9fb04e)
Date: Thu, 20 Jul 2023 18:16:02 +0200

This is an automated email from the git hooks/post-receive script.

oec pushed a change to branch master
in repository exchange.

    from 2f8e3d31 create dir
     new 2eb3ff1f [age-withdraw] simplify lib-API
     new 8706a36c -first steps towards testing age-withdraw
     new c424cf99 Merge branch 'master' into age-withdraw
     new 430bb062 [age-withdraw] Added testing commands for age-withdraw and 
-reveal
     new db9fb04e Merge branch 'age-withdraw', after implementing 
testing-commands

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/include/taler_exchange_service.h         | 137 ++++--
 src/include/taler_testing_lib.h              |   4 +-
 src/lib/exchange_api_age_withdraw.c          |  97 ++--
 src/lib/exchange_api_age_withdraw_reveal.c   | 111 +----
 src/lib/exchange_api_common.c                |   2 +
 src/lib/notizen.md                           | 236 +++++++++
 src/testing/Makefile.am                      |   1 +
 src/testing/testing_api_cmd_age_withdraw.c   | 684 +++++++++++++++++++++++++--
 src/testing/testing_api_cmd_batch_withdraw.c |   8 +-
 src/testing/testing_api_cmd_common.c         |  14 +
 src/testing/testing_api_loop.c               |   3 +
 11 files changed, 1046 insertions(+), 251 deletions(-)
 create mode 100644 src/lib/notizen.md

diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index 1902eb29..7bd1b324 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 3cd0b145..c99f7c3b 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 b5da232e..c68fe67d 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 fcb551a9..75707a4e 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 337fbaba..08ca4b4e 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 00000000..835fad9e
--- /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 c659f0ac..068d9459 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 56e65a99..edf29297 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 <gnunet/gnunet_common.h>
 #include <microhttpd.h>
 #include <gnunet/gnunet_curl_lib.h>
 #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; i<aws->num_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 : "<retried command>");
+      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; i<ws->num_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;
-    }
 
+  for (unsigned int i = 0; i<aws->num_coins; i++)
+  {
+    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 da43a9aa..41c74c3e 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 <microhttpd.h>
 #include <gnunet/gnunet_curl_lib.h>
@@ -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 91138f36..2c29f4ec 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 2a8e3d0b..781028bf 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
 {
 

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]