diff options
| author | Özgür Kesim <oec-taler@kesim.org> | 2023-03-06 20:51:11 +0100 | 
|---|---|---|
| committer | Özgür Kesim <oec-taler@kesim.org> | 2023-03-06 20:51:11 +0100 | 
| commit | 20cd46f63d53f18b07d42ffdee99d2f3746bc6e5 (patch) | |
| tree | c6764f532cfaff37410fb3321086909df467e4b9 /src/exchange/taler-exchange-httpd_age-withdraw_reveal.c | |
| parent | e2deb89a3d070f6fa112009ce1e24652fd04ab2f (diff) | |
| parent | 2c78cb71e6ca3f67dc9ad3df014f1467f3cebee6 (diff) | |
Merge branch 'age-withdraw'
Diffstat (limited to 'src/exchange/taler-exchange-httpd_age-withdraw_reveal.c')
| -rw-r--r-- | src/exchange/taler-exchange-httpd_age-withdraw_reveal.c | 402 | 
1 files changed, 315 insertions, 87 deletions
| diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c index 65bbb432..daefca4c 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -42,7 +42,7 @@ struct AgeRevealContext    /**     * Public key of the reserve for with the age-withdraw commitment was     * originally made.  This parameter is provided by the client again -   * during the call to reveal in order to save a database-lookup . +   * during the call to reveal in order to save a database-lookup.     */    struct TALER_ReservePublicKeyP reserve_pub; @@ -52,20 +52,41 @@ struct AgeRevealContext    uint32_t num_coins;    /** -   * TODO:oec num_coins denoms +   * #num_coins hashes of the denominations from which the coins are withdrawn. +   * Those must support age restriction.     */    struct TALER_DenominationHashP *denoms_h;    /** -   * TODO:oec num_coins blinded coins +   * #num_coins denomination keys, found in the system, according to denoms_h; +   */ +  struct TEH_DenominationKey *denom_keys; + +  /** +   * #num_coins hases of blinded coins.     */    struct TALER_BlindedCoinHashP *coin_evs;    /** -   * TODO:oec num_coins*(kappa - 1) disclosed coins +   * Total sum of all denominations' values +   **/ +  struct TALER_Amount total_amount; + +  /** +   * Total sum of all denominations' fees +   */ +  struct TALER_Amount total_fee; + +  /** +   * #num_coins*(kappa - 1) disclosed coins.     */    struct GNUNET_CRYPTO_EddsaPrivateKey *disclosed_coins; +  /** +   * The data from the original age-withdraw.  Will be retrieved from +   * the DB via @a ach. +   */ +  struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment;  };  /** @@ -75,30 +96,35 @@ void  age_reveal_context_free (struct AgeRevealContext *actx)  {    GNUNET_free (actx->denoms_h); +  GNUNET_free (actx->denom_keys);    GNUNET_free (actx->coin_evs);    GNUNET_free (actx->disclosed_coins);  }  /** - * Handle a "/age-withdraw/$ACH/reveal request.  Parses the given JSON - * ... TODO:oec:description + * Parse the json body of an '/age-withdraw/$ACH/reveal' request.  It extracts + * the denomination hashes, blinded coins and disclosed coins and allocates + * memory for those.   *   * @param connection The MHD connection to handle - * @param actx The context of the operation, only partially built at call time   * @param j_denoms_h Array of hashes of the denominations for the withdrawal, in JSON format   * @param j_coin_evs The blinded envelopes in JSON format for the coins that are not revealed and will be signed on success   * @param j_disclosed_coins The n*(kappa-1) disclosed coins' private keys in JSON format, from which all other attributes (age restriction, blinding, nonce) will be derived from + * @param[out] actx The context of the operation, only partially built at call time + * @param[out] mhd_mret The result if a reply is queued for MHD + * @return true on success, false on failure, with a reply already queued for MHD.   */ -MHD_RESULT -handle_age_withdraw_reveal_json ( +static enum GNUNET_GenericReturnValue +parse_age_withdraw_reveal_json (    struct MHD_Connection *connection, -  struct AgeRevealContext *actx,    const json_t *j_denoms_h,    const json_t *j_coin_evs, -  const json_t *j_disclosed_coins) +  const json_t *j_disclosed_coins, +  struct AgeRevealContext *actx, +  MHD_RESULT *mhd_ret)  { -  MHD_RESULT mhd_ret = MHD_NO; +  enum GNUNET_GenericReturnValue result = GNUNET_SYSERR;    /* Verify JSON-structure consistency */    { @@ -116,135 +142,309 @@ handle_age_withdraw_reveal_json (        error = "denoms_h must not be empty";      else if (actx->num_coins != json_array_size (j_coin_evs))        error = "denoms_h and coins_evs must be arrays of the same size"; -    else if (actx->num_coins * (TALER_CNC_KAPPA - 1) -             != json_array_size (j_disclosed_coins)) -      error = "the size of array disclosed_coins must be " -              TALER_CNC_KAPPA_MINUS_ONE_STR " times of the size of denoms_h";      else if (actx->num_coins > TALER_MAX_FRESH_COINS)        /** -       * FIXME?: If the user had commited to more than the maximum coins allowed, -       * the reserve has been charged, but now the user can not withdraw any money -       * from it. How can the user get their money back? +       * The wallet had committed to more than the maximum coins allowed, the +       * reserve has been charged, but now the user can not withdraw any money +       * from it.  Note that the user can't get their money back in this case!         **/        error = "maximum number of coins that can be withdrawn has been exceeded"; +    else if (actx->num_coins * (TALER_CNC_KAPPA - 1) +             != json_array_size (j_disclosed_coins)) +      error = "the size of array disclosed_coins must be " +              TALER_CNC_KAPPA_MINUS_ONE_STR " times the size of denoms_h";      if (NULL != error) -      return TALER_MHD_reply_with_error (connection, -                                         MHD_HTTP_BAD_REQUEST, -                                         TALER_EC_GENERIC_PARAMETER_MALFORMED, -                                         error); +    { +      *mhd_ret = TALER_MHD_reply_with_error (connection, +                                             MHD_HTTP_BAD_REQUEST, +                                             TALER_EC_GENERIC_PARAMETER_MALFORMED, +                                             error); +      return GNUNET_SYSERR; +    }    } -  /* Parse denomination keys */ +  /* Continue parsing the parts */    { -    unsigned int idx; -    json_t *jh; +    unsigned int idx = 0; +    json_t *value = NULL; +    /* Parse denomination keys */      actx->denoms_h = GNUNET_new_array (actx->num_coins,                                         struct TALER_DenominationHashP); -    json_array_foreach (j_denoms_h, idx, jh) { +    json_array_foreach (j_denoms_h, idx, value) {        struct GNUNET_JSON_Specification spec[] = {          GNUNET_JSON_spec_fixed_auto (NULL, &actx->denoms_h[idx]),          GNUNET_JSON_spec_end ()        };        if (GNUNET_OK != -          GNUNET_JSON_parse (jh, spec, NULL, NULL)) +          GNUNET_JSON_parse (value, spec, NULL, NULL))        {          char msg[256] = {0};          GNUNET_snprintf (msg,                           sizeof(msg),                           "couldn't parse entry no. %d in array denoms_h",                           idx + 1); -        mhd_ret = TALER_MHD_reply_with_error (connection, -                                              MHD_HTTP_BAD_REQUEST, -                                              TALER_EC_GENERIC_PARAMETER_MALFORMED, -                                              msg); +        *mhd_ret = TALER_MHD_reply_with_error (connection, +                                               MHD_HTTP_BAD_REQUEST, +                                               TALER_EC_GENERIC_PARAMETER_MALFORMED, +                                               msg);          goto EXIT;        } -      }; -  } - -  /* Parse blinded envelopes */ -  { -    unsigned int idx; -    json_t *ce; +    /* Parse blinded envelopes */      actx->coin_evs = GNUNET_new_array (actx->num_coins,                                         struct TALER_BlindedCoinHashP); -    json_array_foreach (j_coin_evs, idx, ce) { +    json_array_foreach (j_coin_evs, idx, value) {        struct GNUNET_JSON_Specification spec[] = {          GNUNET_JSON_spec_fixed_auto (NULL, &actx->coin_evs[idx]),          GNUNET_JSON_spec_end ()        };        if (GNUNET_OK != -          GNUNET_JSON_parse (ce, spec, NULL, NULL)) +          GNUNET_JSON_parse (value, spec, NULL, NULL))        {          char msg[256] = {0};          GNUNET_snprintf (msg,                           sizeof(msg),                           "couldn't parse entry no. %d in array coin_evs",                           idx + 1); -        mhd_ret = TALER_MHD_reply_with_error (connection, -                                              MHD_HTTP_BAD_REQUEST, -                                              TALER_EC_GENERIC_PARAMETER_MALFORMED, -                                              msg); +        *mhd_ret = TALER_MHD_reply_with_error (connection, +                                               MHD_HTTP_BAD_REQUEST, +                                               TALER_EC_GENERIC_PARAMETER_MALFORMED, +                                               msg);          goto EXIT;        }      }; -  } - -  /* Parse diclosed keys */ -  { -    unsigned int idx; -    json_t *dc; +    /* Parse diclosed keys */      actx->disclosed_coins = GNUNET_new_array ( -      actx->num_coins * (TALER_CNC_KAPPA), +      actx->num_coins * (TALER_CNC_KAPPA - 1),        struct GNUNET_CRYPTO_EddsaPrivateKey); -    json_array_foreach (j_coin_evs, idx, dc) { +    json_array_foreach (j_disclosed_coins, idx, value) {        struct GNUNET_JSON_Specification spec[] = {          GNUNET_JSON_spec_fixed_auto (NULL, &actx->disclosed_coins[idx]),          GNUNET_JSON_spec_end ()        };        if (GNUNET_OK != -          GNUNET_JSON_parse (dc, spec, NULL, NULL)) +          GNUNET_JSON_parse (value, spec, NULL, NULL))        {          char msg[256] = {0};          GNUNET_snprintf (msg,                           sizeof(msg),                           "couldn't parse entry no. %d in array disclosed_coins",                           idx + 1); -        mhd_ret = TALER_MHD_reply_with_error (connection, -                                              MHD_HTTP_BAD_REQUEST, -                                              TALER_EC_GENERIC_PARAMETER_MALFORMED, -                                              msg); +        *mhd_ret = TALER_MHD_reply_with_error (connection, +                                               MHD_HTTP_BAD_REQUEST, +                                               TALER_EC_GENERIC_PARAMETER_MALFORMED, +                                               msg);          goto EXIT;        }      }; -    } -  /* TODO:oec: find commitment */ -  /* TODO:oec: check validity of denoms */ -  /* TODO:oec: check amount total against denoms */ -  /* TODO:oec: compute the disclosed blinded coins */ -  /* TODO:oec: generate h_commitment_comp */ -  /* TODO:oec: compare h_commitment_comp against h_commitment */ -  /* TODO:oec: sign the coins */ -  /* TODO:oec: send response */ +  result = GNUNET_OK; +  *mhd_ret = MHD_YES; -  /* TODO */  EXIT: -  age_reveal_context_free (actx); -  return mhd_ret; +  return result; +} + + +/** + * Check if the request belongs to an existing age-withdraw request. + * If so, sets the age_withdraw object with the request data. + * Otherwise, it queues an appropriate MHD response. + * + * @param connection The HTTP connection to the client + * @param h_commitment Original commitment value sent with the age-withdraw request + * @param reserve_pub Reserve public key used in the original age-withdraw request + * @param[out] commitment Data from the original age-withdraw request + * @param[out] result In the error cases, a response will be queued with MHD and this will be the result. + * @return GNUNET_OK if the withdraw request has been found, + *   GNUNET_SYSERROR if we did not find the request in the DB + */ +static enum GNUNET_GenericReturnValue +retrieve_original_commitment ( +  struct MHD_Connection *connection, +  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, +  const struct TALER_ReservePublicKeyP *reserve_pub, +  struct TALER_EXCHANGEDB_AgeWithdrawCommitment *commitment, +  MHD_RESULT *result) +{ +  enum GNUNET_DB_QueryStatus qs; + +  qs = TEH_plugin->get_age_withdraw_info (TEH_plugin->cls, +                                          reserve_pub, +                                          h_commitment, +                                          commitment); +  switch (qs) +  { +  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: +    return GNUNET_OK; /* Only happy case */ + +  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: +    *result = TALER_MHD_reply_with_error (connection, +                                          MHD_HTTP_NOT_FOUND, +                                          TALER_EC_EXCHANGE_AGE_WITHDRAW_COMMITMENT_UNKNOWN, +                                          NULL); +    break; + +  case GNUNET_DB_STATUS_HARD_ERROR: +    *result = TALER_MHD_reply_with_error (connection, +                                          MHD_HTTP_INTERNAL_SERVER_ERROR, +                                          TALER_EC_GENERIC_DB_FETCH_FAILED, +                                          "get_age_withdraw_info"); +    break; + +  case GNUNET_DB_STATUS_SOFT_ERROR: +  /* FIXME: Do we queue a result in this case or retry? */ +  default: +    GNUNET_break (0);       /* should be impossible */ +    *result = TALER_MHD_reply_with_error (connection, +                                          MHD_HTTP_INTERNAL_SERVER_ERROR, +                                          TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, +                                          NULL); +  } + +  return GNUNET_SYSERR; +} + + +/** + * Check if the given array of hashes of denomination_keys a) belong + * to valid denominations and b) those are marked as age restricted. + * + * @param connection The HTTP connection to the client + * @param len The lengths of the array @a denoms_h + * @param denoms_h array of hashes of denomination public keys + * @param[out] dks On success, will be filled with the denomination keys.  Caller must deallocate. + * @param amount_with_fee The commited amount including fees + * @param[out] total_sum On success, will contain the total sum of all denominations + * @param[out] total_fee On success, will contain the total sum of all fees + * @param[out] result In the error cases, a response will be queued with MHD and this will be the result. + * @return GNUNET_OK if the denominations are valid and support age-restriction + *   GNUNET_SYSERR otherwise + */ +static enum GNUNET_GenericReturnValue +all_denominations_valid ( +  struct MHD_Connection *connection, +  uint32_t len, +  const struct TALER_DenominationHashP *denoms_h, +  struct TEH_DenominationKey **dks, +  const struct TALER_Amount *amount_with_fee, +  struct TALER_Amount *total_amount, +  struct TALER_Amount *total_fee, +  MHD_RESULT *result) +{ +  struct TEH_KeyStateHandle *ksh; + +  GNUNET_assert (*dks == NULL); + +  ksh = TEH_keys_get_state (); +  if (NULL == ksh) +  { +    *result = TALER_MHD_reply_with_error (connection, +                                          MHD_HTTP_INTERNAL_SERVER_ERROR, +                                          TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, +                                          NULL); +    return GNUNET_SYSERR; +  } + +  *dks = GNUNET_new_array (len, struct TEH_DenominationKey); +  TALER_amount_set_zero (TEH_currency, total_amount); +  TALER_amount_set_zero (TEH_currency, total_fee); + +  for (uint32_t i = 0; i < len; i++) +  { +    dks[i] = TEH_keys_denomination_by_hash2 ( +      ksh, +      &denoms_h[i], +      connection, +      result); + +    /* Does the denomination exist? */ +    if (NULL == dks[i]) +    { +      GNUNET_assert (result != NULL); +      /* Note: a HTTP-response has been queued and result has been set by +       * TEH_keys_denominations_by_hash2 */ +      return GNUNET_SYSERR; +    } + +    /* Does the denomation support age restriction ? */ +    if (0 == dks[i]->denom_pub.age_mask.bits) +    { +      char msg[256] = {0}; +      GNUNET_snprintf (msg, +                       sizeof(msg), +                       "denomination key no. %d does not support age restriction", +                       i + 1); + +      *result = TALER_MHD_reply_with_error (connection, +                                            MHD_HTTP_BAD_REQUEST, +                                            TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN, +                                            msg); +      return GNUNET_SYSERR; +    } + +    /* Accumulate the values */ +    if (0 > TALER_amount_add ( +          total_amount, +          total_amount, +          &dks[i]->meta.value)) +    { +      GNUNET_break (0); +      *result = TALER_MHD_reply_with_error (connection, +                                            MHD_HTTP_INTERNAL_SERVER_ERROR, +                                            TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW, +                                            "amount"); +      return GNUNET_SYSERR; +    } + +    /* Accumulate the withdraw fees */ +    if (0 > TALER_amount_add ( +          total_fee, +          total_fee, +          &dks[i]->meta.fees.withdraw)) +    { +      GNUNET_break (0); +      *result = TALER_MHD_reply_with_error (connection, +                                            MHD_HTTP_INTERNAL_SERVER_ERROR, +                                            TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW, +                                            "fee"); +      return GNUNET_SYSERR; +    } +  } + +  /* Compare the commited amount against the totals */ +  { +    struct TALER_Amount sum; +    TALER_amount_set_zero (TEH_currency, &sum); + +    GNUNET_assert (0 < TALER_amount_add ( +                     &sum, +                     total_amount, +                     total_fee)); + +    if (0 != TALER_amount_cmp (&sum, amount_with_fee)) +    { +      GNUNET_break (0); +      *result = TALER_MHD_reply_with_ec (connection, +                                         TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_INCORRECT, +                                         NULL); +      return GNUNET_SYSERR; +    } +  } + +  return GNUNET_OK;  } @@ -254,6 +454,8 @@ TEH_handler_age_withdraw_reveal (    const struct TALER_AgeWithdrawCommitmentHashP *ach,    const json_t *root)  { +  MHD_RESULT result = MHD_NO; +  enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;    struct AgeRevealContext actx = {0};    json_t *j_denoms_h;    json_t *j_coin_evs; @@ -270,33 +472,59 @@ TEH_handler_age_withdraw_reveal (    /* Parse JSON body*/    { -    enum GNUNET_GenericReturnValue res; - -    res = TALER_MHD_parse_json_data (rc->connection, +    ret = TALER_MHD_parse_json_data (rc->connection,                                       root,                                       spec); -    if (GNUNET_OK != res) +    if (GNUNET_OK != ret)      {        GNUNET_break_op (0); -      return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; +      return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;      }    } -  /* handle reveal request */ -  { -    MHD_RESULT res; - -    res = handle_age_withdraw_reveal_json (rc->connection, -                                           &actx, -                                           j_denoms_h, -                                           j_coin_evs, -                                           j_disclosed_coins); +  do { +    /* Extract denominations, blinded and disclosed coins */ +    if (GNUNET_OK != parse_age_withdraw_reveal_json (rc->connection, +                                                     j_denoms_h, +                                                     j_coin_evs, +                                                     j_disclosed_coins, +                                                     &actx, +                                                     &result)) +      break; + +    /* Find original commitment */ +    if (GNUNET_OK != retrieve_original_commitment (rc->connection, +                                                   &actx.ach, +                                                   &actx.reserve_pub, +                                                   &actx.commitment, +                                                   &result)) +      break; + +    /* Ensure validity of denoms and the sum of amounts and fees */ +    if (GNUNET_OK != all_denominations_valid ( +          rc->connection, +          actx.num_coins, +          actx.denoms_h, +          &actx.denom_keys, +          &actx.commitment.amount_with_fee, +          &actx.total_amount, +          &actx.total_fee, +          &result)) +      break; + + +  } while(0); -    GNUNET_JSON_parse_free (spec); -    return res; -  } +  /* TODO:oec: compute the disclosed blinded coins */ +  /* TODO:oec: generate h_commitment_comp */ +  /* TODO:oec: compare h_commitment_comp against h_commitment */ +  /* TODO:oec: sign the coins */ +  /* TODO:oec: send response */ +  age_reveal_context_free (&actx); +  GNUNET_JSON_parse_free (spec); +  return result;  } | 
