taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

post-recoup-withdraw.rst (13246B)


      1 .. http:post:: /recoup-withdraw
      2 
      3   Demand that coins of revoked denominations, which originated from
      4   one particular call to :http:post:`/withdraw </withdraw>`, be paid
      5   back to the reserve they were withdrawn from.
      6 
      7   All coins in one request MUST originate from the same withdraw operation.
      8   The client identifies that operation by the public key of the reserve and
      9   the commitment ``planchets_h`` of the original withdraw request.  For each
     10   coin that is to be recouped, the client discloses the secret material that
     11   allows the exchange to recompute the coin's blinded envelope.  For every
     12   other coin of the same operation, the client only provides the hash of its
     13   blinded envelope.  From these values the exchange recomputes the hash over
     14   the batch of coins it signed in the original operation and compares it with
     15   the commitment it stored.  This proves that each disclosed coin was part of
     16   that withdraw operation without the exchange having to store individual
     17   envelopes.
     18 
     19   The residual value of each disclosed coin, that is its denomination's value
     20   minus what has already been spent, is credited to the reserve.  The
     21   withdraw fee paid in the original operation is **not** recouped.
     22 
     23   The exchange only accepts this request for coins of denominations that
     24   are listed in the ``recoup`` field of :http:get:`/keys </keys>`.
     25 
     26   This endpoint was introduced in protocol **vRECOUP** and replaces the
     27   former ``/coins/$COIN_PUB/recoup`` endpoint.
     28 
     29   **Request:**
     30 
     31   The request body must be a `RecoupWithdrawRequest` object.
     32 
     33   **Response:**
     34 
     35   :http:statuscode:`200 OK`:
     36     The request was successful and the response is a
     37     `RecoupWithdrawResponse`.  Repeating exactly the same request
     38     yields the same response (the signature may differ if the exchange
     39     has rotated its signing key in the meantime), so if the network goes
     40     down before the client can commit the result to disk, the recoup is
     41     not lost.
     42   :http:statuscode:`400 Bad Request`:
     43     The request body is malformed or a parameter is invalid.
     44     This response comes with a standard `ErrorDetail` response.
     45     Possible error codes include ``TALER_EC_GENERIC_PARAMETER_MALFORMED``
     46     (also used if ``coin_data`` does not contain at least one disclosed
     47     coin) and ``TALER_EC_EXCHANGE_RECOUP_WITHDRAW_BATCH_SIZE_MISMATCH``
     48     (the number of entries in ``coin_data`` differs from the number of
     49     coins the exchange signed in the original withdraw operation).
     50   :http:statuscode:`403 Forbidden`:
     51     A signature is invalid.
     52     This response comes with a standard `ErrorDetail` response.
     53     Possible error codes include
     54     ``TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID`` (a ``coin_sig``)
     55     or ``TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID``
     56     (a ``denom_sig``).
     57   :http:statuscode:`404 Not found`:
     58     One of the following reasons occurred:
     59 
     60     1. A denomination key is unknown.  The response is a
     61        `DenominationUnknownMessage` with error code
     62        ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN``.
     63     2. The exchange has no record of a withdraw operation for the given
     64        ``reserve_pub`` and ``planchets_h``.  This response comes with a
     65        standard `ErrorDetail` response with error code
     66        ``TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND``.
     67   :http:statuscode:`409 Conflict`:
     68     One of the following reasons occurred:
     69 
     70     1. The hash the exchange recomputed over the ``coin_data`` array does
     71        not match the commitment of the withdraw operation, so at least one
     72        disclosed coin was not part of it.  This response comes with a
     73        standard `ErrorDetail` response with error code
     74        ``TALER_EC_EXCHANGE_RECOUP_WITHDRAW_COMMITMENT_MISMATCH``.
     75     2. The denomination of a disclosed coin differs from the denomination
     76        recorded for that position of the withdraw operation.  This
     77        response comes with a standard `ErrorDetail` response with error code
     78        ``TALER_EC_EXCHANGE_RECOUP_WITHDRAW_DENOMINATION_MISMATCH``.
     79     3. A disclosed coin has no residual value left.  The response is a
     80        `DepositDoubleSpendError` with error code
     81        ``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS``.
     82     4. The same coin public key was previously used with a different
     83        denomination or age commitment.  The response is a
     84        `CoinDenominationConflictError` with error code
     85        ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``
     86        or a `CoinAgeCommitmentConflictError` with error code
     87        ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH``.
     88 
     89     In all of these cases nothing is credited, not even for the other
     90     coins in the request.
     91   :http:statuscode:`410 Gone`:
     92     The denomination of a disclosed coin is not eligible for recoup.
     93     The response is a `DenominationGoneMessage` with error code
     94     ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED`` (past the deposit
     95     expiration) or ``TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE`` (the
     96     denomination was not revoked).
     97   :http:statuscode:`412 Precondition Failed`:
     98     The denomination of a disclosed coin is not yet valid.
     99     The response is a `DenominationGoneMessage` with error code
    100     ``TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE``.
    101   :http:statuscode:`413 Request entity too large`:
    102     The uploaded body is to long, it exceeds the size limit.
    103     Returned with an error code of
    104     ``TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT``.
    105   :http:statuscode:`500 Internal Server Error`:
    106     The exchange encountered an internal error.
    107     This response comes with a standard `ErrorDetail` response.
    108     Possible error codes include
    109     ``TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED``,
    110     ``TALER_EC_GENERIC_DB_FETCH_FAILED``,
    111     ``TALER_EC_GENERIC_DB_COMMIT_FAILED``, or
    112     ``TALER_EC_GENERIC_DB_INVARIANT_FAILURE``.
    113   :http:statuscode:`502 Bad gateway`:
    114     The exchange could not reach its signing helper to create
    115     the confirmation signatures.  Returned with an error code of
    116     ``TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE``.
    117     Wallets should retry the request (with some delay) at a later time.
    118   :http:statuscode:`503 Service Unavailable`:
    119     The exchange currently has no signing keys available.
    120     This response comes with a standard `ErrorDetail` response with
    121     a code of ``TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING``.
    122 
    123   **Details:**
    124 
    125   .. ts:def:: RecoupWithdrawRequest
    126 
    127     interface RecoupWithdrawRequest {
    128       // Public key of the reserve from which the coins were withdrawn.
    129       // The recouped value is credited to this reserve.
    130       reserve_pub: EddsaPublicKey;
    131 
    132       // The commitment of the original withdraw request, as
    133       // signed in the ``h_planchets`` field of `TALER_WithdrawRequestPS`.
    134       // Together with ``reserve_pub``, this identifies the
    135       // withdraw operation.
    136       planchets_h: HashCode;
    137 
    138       // Exactly one entry per coin the exchange signed in the
    139       // withdraw operation, in the order of the original request.
    140       // If the withdraw request had ``max_age`` set, these are the
    141       // ``n`` coins of the batch at the ``noreveal_index`` returned
    142       // by the exchange, not all ``n*kappa`` candidates.
    143       // At least one entry MUST be a `RecoupedCoin`.
    144       coin_data: RecoupCoinData[];
    145     }
    146 
    147   .. ts:def:: RecoupCoinData
    148 
    149     // Union discriminated by the ``type`` field.
    150     type RecoupCoinData =
    151        | NonRecoupedCoin
    152        | RecoupedCoin;
    153 
    154   .. ts:def:: NonRecoupedCoin
    155 
    156     // A coin of the original operation that is not to be recouped
    157     // in this request.  The client only provides the hash of its
    158     // blinded envelope, which is all the exchange needs to
    159     // recompute the commitment.
    160     interface NonRecoupedCoin {
    161       type: "hash";
    162 
    163       // Hash of the coin's blinded envelope,
    164       // see `BlindedCoinEnvelopeHash`.
    165       h_coin_ev: BlindedCoinEnvelopeHash;
    166     }
    167 
    168   .. ts:def:: BlindedCoinEnvelopeHash
    169 
    170     // SHA-512 hash over the hash of the denomination public key followed
    171     // by the canonical serialization of the blinded envelope (the cipher
    172     // identifier in network byte order followed by the blinded message).
    173     // This is the value computed by ``TALER_coin_ev_hash`` in
    174     // ``libtalerutil``.  The hash the exchange compares against a
    175     // withdraw or refresh commitment is the SHA-512 hash over the
    176     // concatenation of these values for all coins of a batch, in order
    177     // (``TALER_wallet_blinded_planchets_hash`` in ``libtalerutil``).
    178     type BlindedCoinEnvelopeHash = HashCode;
    179 
    180   .. ts:def:: RecoupedCoin
    181 
    182     // A coin of the original operation that is to be recouped.
    183     // The client discloses the secrets from which the exchange can
    184     // recompute the coin's blinded envelope, proves ownership of the
    185     // coin and authorizes the recoup.
    186     interface RecoupedCoin {
    187       type: "recoup";
    188 
    189       // The coin's public key.
    190       coin_pub: CoinPublicKey;
    191 
    192       // Hash of the public key of the coin's denomination.
    193       // It MUST be listed in the ``recoup`` field of ``/keys``
    194       // and MUST match the denomination at this position of the
    195       // original withdraw request.
    196       denom_pub_h: HashCode;
    197 
    198       // Unblinded signature of the denomination key over the coin.
    199       denom_sig: DenominationSignature;
    200 
    201       // The blinding secret that was used to blind the coin
    202       // in the original withdraw request.
    203       coin_blinding_secret: DenominationBlindingKeySecret;
    204 
    205       // The hash of the coin's age commitment.  MUST be present
    206       // if and only if the denomination supports age restriction.
    207       h_age_commitment?: AgeCommitmentHash;
    208 
    209       // Signature over `TALER_RecoupRequestPS` with purpose
    210       // ``TALER_SIGNATURE_WALLET_COIN_RECOUP``, created with the
    211       // coin's private key.
    212       coin_sig: EddsaSignature;
    213     }
    214 
    215   Note that for denominations of cipher type Clause-Schnorr the client
    216   does not need to provide the session nonce or the R-value pair: the
    217   exchange derives the nonce from the ``blinding_seed`` of the original
    218   withdraw request and the coin's position in ``coin_data`` (exactly as the
    219   wallet does), and it stored the R-values it returned in
    220   :http:post:`/blinding-prepare </blinding-prepare>`.
    221 
    222   The exchange processes the request as follows:
    223 
    224   1. It looks up the withdraw operation under ``reserve_pub`` and
    225      ``planchets_h``, which yields the number ``n`` of signed coins, their
    226      denominations and, where applicable, the blinding seed, the R-values and
    227      the ``noreveal_index``.  If ``coin_data`` does not have ``n`` entries,
    228      the request is rejected.
    229   2. For each `RecoupedCoin` at position ``i``, it checks that the
    230      denomination is eligible for recoup and equals the one recorded at
    231      position ``i``, verifies ``denom_sig`` and ``coin_sig``, recomputes the
    232      blinded envelope from ``coin_pub``, ``coin_blinding_secret``,
    233      ``h_age_commitment`` and the stored cipher-specific values, and hashes
    234      it into a `BlindedCoinEnvelopeHash`.  For each `NonRecoupedCoin`
    235      it uses the ``h_coin_ev`` provided.
    236   3. It computes the hash over the resulting ``n`` values and compares it
    237      to the hash of the batch it signed: ``planchets_h`` itself if the
    238      withdraw request had no ``max_age``, and otherwise the hash of the batch
    239      at the ``noreveal_index``.
    240   4. In one database transaction, it credits the residual value of every
    241      disclosed coin to the reserve and records the recoup.  If any coin
    242      cannot be recouped, the whole request fails and nothing is credited.
    243 
    244   .. ts:def:: RecoupWithdrawResponse
    245 
    246     interface RecoupWithdrawResponse {
    247       // Public key of the reserve that was credited.
    248       reserve_pub: EddsaPublicKey;
    249 
    250       // The commitment of the withdraw operation, as in the request.
    251       planchets_h: HashCode;
    252 
    253       // Time when the exchange accepted the recoup.  All coins
    254       // of one request share this timestamp.
    255       timestamp: Timestamp;
    256 
    257       // Total amount credited to the reserve, that is the sum
    258       // of the ``amount`` values in ``recoups``.
    259       total_amount: Amount;
    260 
    261       // One entry per `RecoupedCoin` in the request, in the same order.
    262       recoups: RecoupedCoinResult[];
    263 
    264       // Signature over `TALER_RecoupWithdrawBatchConfirmationPS` with
    265       // purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_WITHDRAW_BATCH``,
    266       // affirming the recoup of all coins in ``recoups``.
    267       exchange_sig: EddsaSignature;
    268 
    269       // Public key used to create ``exchange_sig``.
    270       // Should match one of the exchange's signing keys from ``/keys``.
    271       exchange_pub: EddsaPublicKey;
    272     }
    273 
    274   .. ts:def:: RecoupedCoinResult
    275 
    276     interface RecoupedCoinResult {
    277       // Public key of the coin that was recouped.
    278       coin_pub: CoinPublicKey;
    279 
    280       // Amount credited for this coin, that is the coin's
    281       // residual value at the time of the recoup.
    282       amount: Amount;
    283     }
    284 
    285   The coin and reserve histories report each recouped coin individually,
    286   see `CoinRecoupWithdrawTransaction` and `ReserveRecoupTransaction`.
    287   Those entries carry a signature per coin over :ref:`TALER_RecoupConfirmationPS <TALER_RecoupConfirmationPS>`,
    288   which the exchange creates when a history is requested; the batch
    289   signature above is only part of the response to this request.