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.