taler-docs

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

commit 4f219f7a878b23d1b38c52cfff783b220911400d
parent d42b291f6e2f460de80b03d2e544d5635a864f80
Author: Özgür Kesim <oec@codeblau.de>
Date:   Mon, 14 Sep 2026 19:00:55 +0200

exchange API: one batch signature per recoup response

Replace the per-coin confirmations in the /recoup-withdraw and
/recoup-refresh responses by a single exchange signature over the
whole response: new signed structs TALER_RecoupWithdrawBatchConfirmationPS
and TALER_RecoupRefreshBatchConfirmationPS bind the timestamp, the
credited reserve resp. old coin, the operation's commitment, the total
amount and a hash over the (coin_pub, amount) pairs of all recouped
coins.  The response lists the recouped coins with their amounts next
to exchange_sig and exchange_pub.

The per-coin history entries keep their existing per-coin confirmation
purposes, which the exchange creates on the fly when serving a history.

Diffstat:
Mcore/api-common.rst | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/exchange/post-recoup-refresh.rst | 43++++++++++++++++++++++++-------------------
Mcore/exchange/post-recoup-withdraw.rst | 53++++++++++++++++++++++++++++++++++-------------------
3 files changed, 110 insertions(+), 38 deletions(-)

diff --git a/core/api-common.rst b/core/api-common.rst @@ -1907,6 +1907,58 @@ within the union GNUNET_CRYPTO_BlindingSecretP coin_blind; }; +.. _TALER_RecoupWithdrawBatchConfirmationPS: +.. sourcecode:: c + + /** + * Signed by the exchange in the response to /recoup-withdraw, + * affirming that it credited the reserve for all recouped coins + * of one request. + */ + struct TALER_RecoupWithdrawBatchConfirmationPS { + /** + * purpose.purpose = TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_WITHDRAW_BATCH + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_TIME_TimestampNBO timestamp; + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_HashBlindedPlanchetsP planchets_h; + struct TALER_AmountNBO total_amount; + /** + * SHA-512 hash over the concatenation, in the order of the + * ``recoups`` array of the response, of the coin public key + * (32 bytes) followed by the recouped amount (as TALER_AmountNBO) + * of every recouped coin. + */ + struct GNUNET_HashCode h_recoups; + }; + +.. _TALER_RecoupRefreshBatchConfirmationPS: +.. sourcecode:: c + + /** + * Signed by the exchange in the response to /recoup-refresh, + * affirming that it credited the old coin for all recouped coins + * of one request. + */ + struct TALER_RecoupRefreshBatchConfirmationPS { + /** + * purpose.purpose = TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH_BATCH + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct GNUNET_TIME_TimestampNBO timestamp; + struct TALER_CoinSpendPublicKeyP old_coin_pub; + struct TALER_RefreshCommitmentP rc; + struct TALER_AmountNBO total_amount; + /** + * SHA-512 hash over the concatenation, in the order of the + * ``recoups`` array of the response, of the coin public key + * (32 bytes) followed by the recouped amount (as TALER_AmountNBO) + * of every recouped coin. + */ + struct GNUNET_HashCode h_recoups; + }; + .. _TALER_RecoupRefreshConfirmationPS: .. sourcecode:: c diff --git a/core/exchange/post-recoup-refresh.rst b/core/exchange/post-recoup-refresh.rst @@ -39,9 +39,10 @@ :http:statuscode:`200 OK`: The request was successful and the response is a `RecoupRefreshResponse`. Repeating exactly the same request - yields the same response, including the same timestamps and - signatures, so if the network goes down before the client can commit the - result to disk, the recoup is not lost. + yields the same response (the signature may differ if the exchange + has rotated its signing key in the meantime), so if the network goes + down before the client can commit the result to disk, the recoup is + not lost. :http:statuscode:`400 Bad Request`: The request body is malformed or a parameter is invalid. This response comes with a standard `ErrorDetail` response. @@ -164,29 +165,33 @@ // Public key of the old coin that was credited. old_coin_pub: CoinPublicKey; - // One confirmation per `RecoupedCoin` in the request, - // in the same order. - recoups: RecoupRefreshConfirmation[]; - } - - .. ts:def:: RecoupRefreshConfirmation + // The refresh commitment of the refresh operation, as in the request. + rc: HashCode; - interface RecoupRefreshConfirmation { - // Public key of the coin that was recouped. - coin_pub: CoinPublicKey; + // Time when the exchange accepted the recoup. All coins + // of one request share this timestamp. + timestamp: Timestamp; - // Amount credited to the old coin for this coin, that is - // the coin's residual value at the time of the recoup. - amount: Amount; + // Total amount credited to the old coin, that is the sum + // of the ``amount`` values in ``recoups``. + total_amount: Amount; - // Time when the exchange accepted the recoup of this coin. - timestamp: Timestamp; + // One entry per `RecoupedCoin` in the request, in the same order. + recoups: RecoupedCoinResult[]; - // Signature over `TALER_RecoupRefreshConfirmationPS` with purpose - // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH``. + // Signature over `TALER_RecoupRefreshBatchConfirmationPS` with + // purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH_BATCH``, + // affirming the recoup of all coins in ``recoups``. exchange_sig: EddsaSignature; // Public key used to create ``exchange_sig``. // Should match one of the exchange's signing keys from ``/keys``. exchange_pub: EddsaPublicKey; } + + The coin histories report each recouped coin individually, see + `CoinRecoupRefreshTransaction` for the recouped coin and + `CoinRecoupRefreshReceiverTransaction` for the old coin. Those entries + carry a signature per coin over :ref:`TALER_RecoupRefreshConfirmationPS <TALER_RecoupRefreshConfirmationPS>`, + which the exchange creates when a history is requested; the batch + signature above is only part of the response to this request. diff --git a/core/exchange/post-recoup-withdraw.rst b/core/exchange/post-recoup-withdraw.rst @@ -35,9 +35,10 @@ :http:statuscode:`200 OK`: The request was successful and the response is a `RecoupWithdrawResponse`. Repeating exactly the same request - yields the same response, including the same timestamps and - signatures, so if the network goes down before the client can commit the - result to disk, the recoup is not lost. + yields the same response (the signature may differ if the exchange + has rotated its signing key in the meantime), so if the network goes + down before the client can commit the result to disk, the recoup is + not lost. :http:statuscode:`400 Bad Request`: The request body is malformed or a parameter is invalid. This response comes with a standard `ErrorDetail` response. @@ -242,29 +243,43 @@ // Public key of the reserve that was credited. reserve_pub: EddsaPublicKey; - // One confirmation per `RecoupedCoin` in the request, - // in the same order. - recoups: RecoupWithdrawConfirmation[]; - } - - .. ts:def:: RecoupWithdrawConfirmation + // The commitment of the withdraw operation, as in the request. + planchets_h: HashCode; - interface RecoupWithdrawConfirmation { - // Public key of the coin that was recouped. - coin_pub: CoinPublicKey; + // Time when the exchange accepted the recoup. All coins + // of one request share this timestamp. + timestamp: Timestamp; - // Amount credited to the reserve for this coin, that is - // the coin's residual value at the time of the recoup. - amount: Amount; + // Total amount credited to the reserve, that is the sum + // of the ``amount`` values in ``recoups``. + total_amount: Amount; - // Time when the exchange accepted the recoup of this coin. - timestamp: Timestamp; + // One entry per `RecoupedCoin` in the request, in the same order. + recoups: RecoupedCoinResult[]; - // Signature over `TALER_RecoupConfirmationPS` with purpose - // ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP``. + // Signature over `TALER_RecoupWithdrawBatchConfirmationPS` with + // purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_WITHDRAW_BATCH``, + // affirming the recoup of all coins in ``recoups``. exchange_sig: EddsaSignature; // Public key used to create ``exchange_sig``. // Should match one of the exchange's signing keys from ``/keys``. exchange_pub: EddsaPublicKey; } + + .. ts:def:: RecoupedCoinResult + + interface RecoupedCoinResult { + // Public key of the coin that was recouped. + coin_pub: CoinPublicKey; + + // Amount credited for this coin, that is the coin's + // residual value at the time of the recoup. + amount: Amount; + } + + The coin and reserve histories report each recouped coin individually, + see `CoinRecoupWithdrawTransaction` and `ReserveRecoupTransaction`. + Those entries carry a signature per coin over :ref:`TALER_RecoupConfirmationPS <TALER_RecoupConfirmationPS>`, + which the exchange creates when a history is requested; the batch + signature above is only part of the response to this request.