taler-docs

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

commit 90448a05fae6b27db2825d3d77902a821b61a512
parent 6b6ccd40ba6a197d4172a0fd5c19fcb32c1a3a8e
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 18 Sep 2023 14:46:35 +0200

update exchange REST API: remove paid history API, authenticate GET coin history API, add start offset and ETag specs for both histories

Diffstat:
Mcore/api-exchange.rst | 1493+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 775 insertions(+), 718 deletions(-)

diff --git a/core/api-exchange.rst b/core/api-exchange.rst @@ -1636,7 +1636,7 @@ exchange. .. http:get:: /reserves/$RESERVE_PUB - Request information about a reserve. + Request summary information about a reserve. **Request:** @@ -1667,970 +1667,906 @@ exchange. } -.. http:post:: /reserves/$RESERVE_PUB/status +Withdraw +~~~~~~~~ - Request information about a reserve or an account. +.. http:post:: /csr-withdraw - **Request:** + Obtain exchange-side input values in preparation for a + withdraw step for certain denomination cipher types, + specifically at this point for Clause-Schnorr blind + signatures. - The request body must be a `ReserveStatusRequest` object. + **Request:** The request body must be a `WithdrawPrepareRequest` object. **Response:** :http:statuscode:`200 OK`: - The exchange responds with a `ReserveStatus` object; the reserve was known to the exchange. - :http:statuscode:`403 Forbidden`: - The *TALER_SIGNATURE_RESERVE_STATUS_REQUEST* signature is invalid. - This response comes with a standard `ErrorDetail` response. Alternatively, the provided timestamp is not close to the current time. + The request was successful, and the response is a `WithdrawPrepareResponse`. Note that repeating exactly the same request + will again yield the same response (assuming none of the denomination is expired). :http:statuscode:`404 Not found`: - The reserve key does not belong to a reserve known to the exchange. + The denomination key is not known to the exchange. + :http:statuscode:`410 Gone`: + The requested denomination key is not yet or no longer valid. + It either before the validity start, past the expiration or was revoked. The response is a + `DenominationExpiredMessage`. Clients must evaluate + the error code provided to understand which of the + cases this is and handle it accordingly. **Details:** - .. ts:def:: ReserveStatusRequest - - interface ReserveStatusRequest { - // Signature of purpose - // ``TALER_SIGNATURE_RESERVE_STATUS_REQUEST`` over - // a `TALER_ReserveStatusRequestSignaturePS`. - reserve_sig: EddsaSignature; - - // Time when the client made the request. - // Timestamp must be reasonably close to the time of - // the exchange, otherwise the exchange may reject - // the request. - request_timestamp: Timestamp; - } + .. ts:def:: WithdrawPrepareRequest - .. ts:def:: ReserveStatus + interface WithdrawPrepareRequest { - interface ReserveStatus { - // Balance left in the reserve. - balance: Amount; + // Nonce to be used by the exchange to derive + // its private inputs from. Must not have ever + // been used before. + nonce: CSNonce; - // If set, gives the maximum age group that the client is required to set - // during withdrawal. - maximum_age_group: number; + // Hash of the public key of the denomination the + // request relates to. + denom_pub_hash: HashCode; - // Transaction history for this reserve. - // May be partial (!). - history: TransactionHistoryItem[]; } - Objects in the transaction history have the following format: + .. ts:def:: WithdrawPrepareResponse - .. ts:def:: TransactionHistoryItem + type WithdrawPrepareResponse = + | ExchangeWithdrawValue; - // Union discriminated by the "type" field. - type TransactionHistoryItem = - | AccountSetupTransaction - | ReserveHistoryTransaction - | ReserveWithdrawTransaction - | ReserveAgeWithdrawTransaction - | ReserveCreditTransaction - | ReserveClosingTransaction - | ReserveOpenRequestTransaction - | ReserveCloseRequestTransaction - | PurseMergeTransaction; + .. ts:def:: ExchangeWithdrawValue - .. ts:def:: AccountSetupTransaction + type ExchangeWithdrawValue = + | ExchangeRsaWithdrawValue + | ExchangeCsWithdrawValue; - interface AccountSetupTransaction { - type: "SETUP"; + .. ts:def:: ExchangeRsaWithdrawValue - // KYC fee agreed to by the reserve owner. - kyc_fee: Amount; + interface ExchangeRsaWithdrawValue { + cipher: "RSA"; + } - // Time when the KYC was triggered. - kyc_timestamp: Timestamp; + .. ts:def:: ExchangeCsWithdrawValue - // Hash of the wire details of the account. - // Note that this hash is unsalted and potentially - // private (as it could be inverted), hence access - // to this endpoint must be authorized using the - // private key of the reserve. - h_wire: HashCode; + interface ExchangeCsWithdrawValue { + cipher: "CS"; - // Signature created with the reserve's private key. - // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_SETUP_REQUEST`` over - // a ``TALER_AccountSetupRequestSignaturePS``. - reserve_sig: EddsaSignature; + // CSR R0 value + r_pub_0: CsRPublic; + // CSR R1 value + r_pub_1: CsRPublic; } - .. ts:def:: ReserveHistoryTransaction - - interface ReserveHistoryTransaction { - type: "HISTORY"; - // Fee agreed to by the reserve owner. - amount: Amount; +.. http:post:: /reserves/$RESERVE_PUB/withdraw - // Time when the request was made. - request_timestamp: Timestamp; + Withdraw a coin of the specified denomination. Note that the client should + commit all of the request details, including the private key of the coin and + the blinding factor, to disk *before* issuing this request, so that it can + recover the information if necessary in case of transient failures, like + power outage, network outage, etc. - // Signature created with the reserve's private key. - // Must be of purpose ``TALER_SIGNATURE_RESERVE_HISTORY_REQUEST`` over - // a `TALER_ReserveHistoryRequestSignaturePS`. - reserve_sig: EddsaSignature; + **Request:** The request body must be a `WithdrawRequest` object. - } + **Response:** - .. ts:def:: ReserveWithdrawTransaction + :http:statuscode:`200 OK`: + The request was successful, and the response is a `WithdrawResponse`. Note that repeating exactly the same request + will again yield the same response, so if the network goes down during the + transaction or before the client can commit the coin signature to disk, the + coin is not lost. + :http:statuscode:`403 Forbidden`: + The signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The denomination key or the reserve are not known to the exchange. If the + denomination key is unknown, this suggests a bug in the wallet as the + wallet should have used current denomination keys from ``/keys``. + In this case, the response will be a `DenominationUnknownMessage`. + If the reserve is unknown, the wallet should not report a hard error yet, but + instead simply wait for up to a day, as the wire transaction might simply + not yet have completed and might be known to the exchange in the near future. + In this case, the wallet should repeat the exact same request later again + using exactly the same blinded coin. + :http:statuscode:`409 Conflict`: + One of the following reasons occured: - interface ReserveWithdrawTransaction { - type: "WITHDRAW"; + 1. The balance of the reserve is not sufficient to withdraw the coins of the + indicated denominations. The response is `WithdrawError` object. - // Amount withdrawn. - amount: Amount; + 2. The reserve has a birthday set and requires a request to ``/age-withdraw`` instead. + The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in the call to ``/age-withdraw`` + :http:statuscode:`410 Gone`: + The requested denomination key is not yet or no longer valid. + It either before the validity start, past the expiration or was revoked. The response is a + `DenominationExpiredMessage`. Clients must evaluate + the error code provided to understand which of the + cases this is and handle it accordingly. + :http:statuscode:`451 Unavailable for Legal Reasons`: + This reserve has received funds from a purse or the amount withdrawn + exceeds another legal threshold and thus the reserve must + be upgraded to an account (with KYC) before the withdraw can + complete. Note that this response does NOT affirm that the + withdraw will ultimately complete with the requested amount. + The user should be redirected to the provided location to perform + the required KYC checks to open the account before withdrawing. + Afterwards, the request should be repeated. + The response will be an `KycNeededRedirect` object. - // Hash of the denomination public key of the coin. - h_denom_pub: HashCode; + Implementation note: internally, we need to + distinguish between upgrading the reserve to an + account (due to P2P payment) and identifying the + owner of the origin bank account (due to exceeding + the withdraw amount threshold), as we need to create + a different payto://-URI for the KYC check depending + on the case. - // Hash of the blinded coin to be signed. - h_coin_envelope: HashCode; - // Signature over a `TALER_WithdrawRequestPS` - // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW`` - // created with the reserve's private key. - reserve_sig: EddsaSignature; + **Details:** - // Fee that is charged for withdraw. - withdraw_fee: Amount; - } + .. ts:def:: DenominationExpiredMessage - .. ts:def:: ReserveAgeWithdrawTransaction + interface DenominationExpiredMessage { - interface ReserveAgeWithdrawTransaction { - type: "AGEWITHDRAW"; + // Taler error code. Note that beyond + // expiration this message format is also + // used if the key is not yet valid, or + // has been revoked. + code: number; - // Total Amount withdrawn. - amount: Amount; + // Signature by the exchange over a + // `TALER_DenominationExpiredAffirmationPS`. + // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED``. + exchange_sig: EddsaSignature; - // Commitment of all ``n*kappa`` blinded coins. - h_commitment: HashCode; + // Public key of the exchange used to create + // the 'exchange_sig. + exchange_pub: EddsaPublicKey; - // Signature over a `TALER_AgeWithdrawRequestPS` - // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW`` - // created with the reserve's private key. - reserve_sig: EddsaSignature; + // Hash of the denomination public key that is unknown. + h_denom_pub: HashCode; - // Fee that is charged for withdraw. - withdraw_fee: Amount; - } + // When was the signature created. + timestamp: Timestamp; + // What kind of operation was requested that now + // failed? + oper: string; + } - .. ts:def:: ReserveCreditTransaction - interface ReserveCreditTransaction { - type: "CREDIT"; + .. ts:def:: WithdrawRequest - // Amount deposited. - amount: Amount; + interface WithdrawRequest { + // Hash of a denomination public key (RSA), specifying the type of coin the client + // would like the exchange to create. + denom_pub_hash: HashCode; - // Sender account ``payto://`` URL. - sender_account_url: string; + // Coin's blinded public key, should be (blindly) signed by the exchange's + // denomination private key. + coin_ev: CoinEnvelope; - // Opaque identifier internal to the exchange that - // uniquely identifies the wire transfer that credited the reserve. - wire_reference: Integer; + // Signature of `TALER_WithdrawRequestPS` created with + // the `reserves's private key <reserve-priv>` + // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW``. + reserve_sig: EddsaSignature; - // Timestamp of the incoming wire transfer. - timestamp: Timestamp; } + .. ts:def:: WithdrawResponse - .. ts:def:: ReserveClosingTransaction + interface WithdrawResponse { + // The blinded signature over the 'coin_ev', affirms the coin's + // validity after unblinding. + ev_sig: BlindedDenominationSignature; - interface ReserveClosingTransaction { - type: "CLOSING"; + } - // Closing balance. - amount: Amount; + .. ts:def:: BlindedDenominationSignature - // Closing fee charged by the exchange. - closing_fee: Amount; + type BlindedDenominationSignature = + | RsaBlindedDenominationSignature + | CSBlindedDenominationSignature; - // Wire transfer subject. - wtid: Base32; + .. ts:def:: RsaBlindedDenominationSignature - // ``payto://`` URI of the wire account into which the funds were returned to. - receiver_account_details: string; + interface RsaBlindedDenominationSignature { + cipher: "RSA"; - // This is a signature over a - // struct `TALER_ReserveCloseConfirmationPS` with purpose - // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``. - exchange_sig: EddsaSignature; - - // Public key used to create 'exchange_sig'. - exchange_pub: EddsaPublicKey; - - // Time when the reserve was closed. - timestamp: Timestamp; + // (blinded) RSA signature + blinded_rsa_signature: BlindedRsaSignature; } + .. ts:def:: CSBlindedDenominationSignature - .. ts:def:: ReserveOpenRequestTransaction - - interface ReserveOpenRequestTransaction { - type: "OPEN"; - - // Open fee paid from the reserve. - open_fee: Amount; + interface CSBlindedDenominationSignature { + type: "CS"; - // This is a signature over - // a struct `TALER_ReserveOpenPS` with purpose - // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN``. - reserve_sig: EddsaSignature; + // Signer chosen bit value, 0 or 1, used + // in Clause Blind Schnorr to make the + // ROS problem harder. + b: Integer; - // Timestamp of the open request. - request_timestamp: Timestamp; + // Blinded scalar calculated from c_b. + s: Cs25519Scalar; - // Requested expiration. - requested_expiration: Timestamp; + } - // Requested number of free open purses. - requested_min_purses: Integer; + .. ts:def:: KycNeededRedirect - } + interface KycNeededRedirect { - .. ts:def:: ReserveCloseRequestTransaction + // Numeric `error code <error-codes>` unique to the condition. + // Should always be ``TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED``. + code: number; - interface ReserveCloseRequestTransaction { - type: "CLOSE"; + // Human-readable description of the error, i.e. "missing parameter", "commitment violation", ... + // Should give a human-readable hint about the error's nature. Optional, may change without notice! + hint?: string; - // This is a signature over - // a struct `TALER_ReserveClosePS` with purpose - // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE``. - reserve_sig: EddsaSignature; + // Hash of the payto:// account URI that identifies + // the account which is being KYCed. + h_payto: PaytoHash; - // Target account ``payto://``, optional. - h_payto?: PaytoHash; + // Legitimization target that the merchant should + // use to check for its KYC status using + // the ``/kyc-check/$REQUIREMENT_ROW/...`` endpoint. + requirement_row: Integer; - // Timestamp of the close request. - request_timestamp: Timestamp; } - .. ts:def:: ReserveCreditTransaction - - interface ReserveCreditTransaction { - type: "CREDIT"; + .. ts:def:: WithdrawError - // Amount deposited. - amount: Amount; + interface WithdrawError { + // Text describing the error. + hint: string; - // Sender account ``payto://`` URL. - sender_account_url: string; + // Detailed error code. + code: Integer; - // Opaque identifier internal to the exchange that - // uniquely identifies the wire transfer that credited the reserve. - wire_reference: Integer; + // Amount left in the reserve. + balance: Amount; - // Timestamp of the incoming wire transfer. - timestamp: Timestamp; + // History of the reserve's activity, in the same format + // as returned by ``/reserve/$RID/history``. + history: TransactionHistoryItem[] } - .. ts:def:: PurseMergeTransaction - - interface PurseMergeTransaction { - type: "MERGE"; - - // SHA-512 hash of the contact of the purse. - h_contract_terms: HashCode; - // EdDSA public key used to approve merges of this purse. - merge_pub: EddsaPublicKey; - // Minimum age required for all coins deposited into the purse. - min_age: Integer; - // Number that identifies who created the purse - // and how it was paid for. - flags: Integer; +Batch Withdraw +~~~~~~~~~~~~~~ - // Purse public key. - purse_pub: EddsaPublicKey; - // EdDSA signature of the account/reserve affirming the merge - // over a `TALER_AccountMergeSignaturePS`. - // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_MERGE`` - reserve_sig: EddsaSignature; +.. http:post:: /reserves/$RESERVE_PUB/batch-withdraw - // Client-side timestamp of when the merge request was made. - merge_timestamp: Timestamp; + Withdraw multiple coins from the same reserve. Note that the client should + commit all of the request details, including the private key of the coins and + the blinding factors, to disk *before* issuing this request, so that it can + recover the information if necessary in case of transient failures, like + power outage, network outage, etc. - // Indicative time by which the purse should expire - // if it has not been merged into an account. At this - // point, all of the deposits made should be - // auto-refunded. - purse_expiration: Timestamp; + **Request:** The request body must be a `BatchWithdrawRequest` object. - // Purse fee the reserve owner paid for the purse creation. - purse_fee: Amount; + **Response:** - // Total amount merged into the reserve. - // (excludes fees). - amount: Amount; + :http:statuscode:`200 OK`: + The request was successful, and the response is a `BatchWithdrawResponse`. + Note that repeating exactly the same request will again yield the same + response, so if the network goes down during the transaction or before the + client can commit the coin signature to disk, the coin is not lost. + :http:statuscode:`403 Forbidden`: + A signature is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + A denomination key or the reserve are not known to the exchange. If the + denomination key is unknown, this suggests a bug in the wallet as the + wallet should have used current denomination keys from ``/keys``. + In this case, the response will be a `DenominationUnknownMessage`. + If the reserve is unknown, the wallet should not report a hard error yet, but + instead simply wait for up to a day, as the wire transaction might simply + not yet have completed and might be known to the exchange in the near future. + In this case, the wallet should repeat the exact same request later again + using exactly the same blinded coin. + :http:statuscode:`409 Conflict`: + One of the following reasons occured: - // True if the purse was actually merged. - // If false, only the purse_fee has an impact - // on the reserve balance! - merged: boolean; - } + 1. The balance of the reserve is not sufficient to withdraw the coins of the + indicated denominations. The response is `WithdrawError` object. + 2. The reserve has a birthday set and requires a request to ``/age-withdraw`` instead. + The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in the call to ``/age-withdraw`` + :http:statuscode:`410 Gone`: + A requested denomination key is not yet or no longer valid. + It either before the validity start, past the expiration or was revoked. + The response is a `DenominationExpiredMessage`. Clients must evaluate the + error code provided to understand which of the cases this is and handle it + accordingly. + :http:statuscode:`451 Unavailable for Legal Reasons`: + This reserve has received funds from a purse or the amount withdrawn + exceeds another legal threshold and thus the reserve must + be upgraded to an account (with KYC) before the withdraw can + complete. Note that this response does NOT affirm that the + withdraw will ultimately complete with the requested amount. + The user should be redirected to the provided location to perform + the required KYC checks to open the account before withdrawing. + Afterwards, the request should be repeated. + The response will be an `KycNeededRedirect` object. -.. http:post:: /reserves/$RESERVE_PUB/history + Implementation note: internally, we need to + distinguish between upgrading the reserve to an + account (due to P2P payment) and identifying the + owner of the origin bank account (due to exceeding + the withdraw amount threshold), as we need to create + a different payto://-URI for the KYC check depending + on the case. - Request information about the full history of - a reserve or an account. - **Request:** + **Details:** - The request body must be a `ReserveHistoryRequest` object. + .. ts:def:: BatchWithdrawRequest - **Response:** + interface BatchWithdrawRequest { + // Array of requests for the individual coins to withdraw. + planchets: WithdrawRequest[]; - :http:statuscode:`200 OK`: - The exchange responds with a `ReserveStatus` object; the reserve was known to the exchange. - :http:statuscode:`403 Forbidden`: - The *TALER_SIGNATURE_RESERVE_HISTORY_REQUEST* is invalid. - This response comes with a standard `ErrorDetail` response. Alternatively, the provided timestamp is not close to the current time. - :http:statuscode:`404 Not found`: - The reserve key does not belong to a reserve known to the exchange. - :http:statuscode:`412 Precondition failed`: - The balance in the reserve is insufficient to pay for the history request. - This response comes with a standard `ErrorDetail` response. + } - **Details:** - .. ts:def:: ReserveHistoryRequest + .. ts:def:: BatchWithdrawResponse - interface ReserveHistoryRequest { - // Signature of type - // ``TALER_SIGNATURE_RESERVE_HISTORY_REQUEST`` - // over a `TALER_ReserveHistoryRequestSignaturePS`. - reserve_sig: EddsaSignature; + interface BatchWithdrawResponse { + // Array of blinded signatures, in the same order as was + // given in the request. + ev_sigs: WithdrawResponse[]; - // Time when the client made the request. - // Timestamp must be reasonably close to the time of - // the exchange, otherwise the exchange may reject - // the request. - request_timestamp: Timestamp; } +Withdraw with Age Restriction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. _delete-reserve: - -.. http:DELETE:: /reserves/$RESERVE_PUB +If the reserve was marked with a maximum age group, the client has to perform a +cut&choose protocol with the exchange. It first calls +``/reserves/$RESERVE_PUB/age-withdraw`` and commits to ``n*kappa`` coins. On +success, the exchange answers this request with an noreveal-index. The client +then has to call ``/age-withdraw/$ACH/reveal`` to reveal all ``n*(kappa - 1)`` +coins along with their age commitments to proof that they were appropriate. +If so, the exchange will blindly sign ``n`` undisclosed coins from the request. - Forcefully closes a reserve. - The request header must contain an *Account-Request-Signature*. - Note: this endpoint is not currently implemented! - **Request:** +.. http:POST:: /reserves/$RESERVE_PUB/age-withdraw - *Account-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$ACCOUNT_PRIV``, affirming its authorization to delete the account. The purpose used MUST be ``TALER_SIGNATURE_RESERVE_CLOSE``. + Withdraw multiple coins *with age restriction* from the same reserve. + Note that the client should commit all of the request details, including the + private key of the coins and the blinding factors, to disk *before* issuing + this request, so that it can recover the information if necessary in case of + transient failures, like power outage, network outage, etc. - :query force=BOOLEAN: *Optional.* If set to 'true' specified, the exchange - will delete the account even if there is a balance remaining. + **Request:** The request body must be a `AgeWithdrawRequest` object. **Response:** :http:statuscode:`200 OK`: - The operation succeeded, the exchange provides details - about the account deletion. - The response will include a `ReserveClosedResponse` object. + The request was successful, and the response is a `AgeWithdrawResponse`. + Note that repeating exactly the same request will again yield the same + response, so if the network goes down during the transaction or before the + client can commit the coin signature to disk, the coin is not lost. :http:statuscode:`403 Forbidden`: - The *Account-Request-Signature* is invalid. + A signature is invalid. This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - The account is unknown to the exchange. :http:statuscode:`409 Conflict`: - The account is still has digital cash in it, the associated - wire method is ``void`` and the *force* option was not provided. - This response comes with a standard `ErrorDetail` response. - - **Details:** - - .. ts:def:: ReserveClosedResponse + One of two reasons occured: - interface ReserveClosedResponse { + 1. The balance of the reserve is not sufficient to withdraw the coins of the + given amount. The response is a `WithdrawError` object. - // Final balance of the account. - closing_amount: Amount; - - // Current time of the exchange, used as part of - // what the exchange signs over. - close_time: Timestamp; - - // Hash of the wire account into which the remaining - // balance will be transferred. Note: may be the - // hash over ``payto://void/`, in which case the - // balance is forfeit to the profit of the exchange. - h_wire: HashCode; - - // This is a signature over a - // struct ``TALER_AccountDeleteConfirmationPS`` with purpose - // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``. - exchange_sig: EddsaSignature; - - } - - - -Withdraw -~~~~~~~~ - -.. http:post:: /csr-withdraw - - Obtain exchange-side input values in preparation for a - withdraw step for certain denomination cipher types, - specifically at this point for Clause-Schnorr blind - signatures. - - **Request:** The request body must be a `WithdrawPrepareRequest` object. - - **Response:** - - :http:statuscode:`200 OK`: - The request was successful, and the response is a `WithdrawPrepareResponse`. Note that repeating exactly the same request - will again yield the same response (assuming none of the denomination is expired). - :http:statuscode:`404 Not found`: - The denomination key is not known to the exchange. + 2. The provided value for ``max_age`` is higher than the allowed value according to the reserve's birthday. + The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in a call to ``/age-withdraw`` :http:statuscode:`410 Gone`: - The requested denomination key is not yet or no longer valid. - It either before the validity start, past the expiration or was revoked. The response is a - `DenominationExpiredMessage`. Clients must evaluate - the error code provided to understand which of the - cases this is and handle it accordingly. - - **Details:** + A requested denomination key is not yet or no longer valid. + It either before the validity start, past the expiration or was revoked. + The response is a `DenominationExpiredMessage`. Clients must evaluate the + error code provided to understand which of the cases this is and handle it + accordingly. + :http:statuscode:`451 Unavailable for Legal Reasons`: + This reserve has received funds from a purse or the amount withdrawn + exceeds another legal threshold and thus the reserve must + be upgraded to an account (with KYC) before the withdraw can + complete. Note that this response does NOT affirm that the + withdraw will ultimately complete with the requested amount. + The user should be redirected to the provided location to perform + the required KYC checks to open the account before withdrawing. + Afterwards, the request should be repeated. + The response will be an `KycNeededRedirect` object. - .. ts:def:: WithdrawPrepareRequest + .. ts:def:: AgeWithdrawRequest - interface WithdrawPrepareRequest { + interface AgeWithdrawRequest { + // Array of ``n`` hash codes of denomination public keys to order. + // These denominations MUST support age restriction as defined in the + // output to /keys. + // The sum of all denomination's values and fees MUST be at most the + // balance of the reserve. The balance of the reserve will be + // immediatley reduced by that amount. + denoms_h: HashCode[]; - // Nonce to be used by the exchange to derive - // its private inputs from. Must not have ever - // been used before. - nonce: CSNonce; + // ``n`` arrays of ``kappa`` entries with blinded coin envelopes. Each + // (toplevel) entry represents ``kappa`` canditates for a particular + // coin. The exchange will respond with an index ``gamma``, which is + // the index that shall remain undisclosed during the reveal phase. + // The SHA512 hash $ACH over the blinded coin envelopes is the commitment + // that is later used as the key to the reveal-URL. + blinded_coins_evs: CoinEnvelope[][]; - // Hash of the public key of the denomination the - // request relates to. - denom_pub_hash: HashCode; + // The maximum age to commit to. MUST be the same as the maximum + // age in the reserve. + max_age: number; + // Signature of `TALER_AgeWithdrawRequestPS` created with + // the `reserves's private key <reserve-priv>` + // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``. + reserve_sig: EddsaSignature; } - .. ts:def:: WithdrawPrepareResponse - - type WithdrawPrepareResponse = - | ExchangeWithdrawValue; - - .. ts:def:: ExchangeWithdrawValue + .. ts:def:: AgeWithdrawResponse - type ExchangeWithdrawValue = - | ExchangeRsaWithdrawValue - | ExchangeCsWithdrawValue; + interface AgeWithdrawResponse { + // index of the commitments that the client doesn't + // have to disclose + noreveal_index: Integer; - .. ts:def:: ExchangeRsaWithdrawValue + // Signature of `TALER_AgeWithdrawConfirmationPS` whereby + // the exchange confirms the ``noreveal_index``. + exchange_sig: EddsaSignature; - interface ExchangeRsaWithdrawValue { - cipher: "RSA"; + // `Public EdDSA key <sign-key-pub>` of the exchange that was used to + // generate the signature. Should match one of the exchange's signing + // keys from ``/keys``. Again given explicitly as the client might + // otherwise be confused by clock skew as to which signing key was used. + exchange_pub: EddsaPublicKey; } - .. ts:def:: ExchangeCsWithdrawValue - - interface ExchangeCsWithdrawValue { - cipher: "CS"; - - // CSR R0 value - r_pub_0: CsRPublic; - // CSR R1 value - r_pub_1: CsRPublic; - } +.. http:POST:: /age-withdraw/$ACH/reveal -.. http:post:: /reserves/$RESERVE_PUB/withdraw + The client has previously committed to multiple coins with age restriction + in a call to ``/reserve/$RESERVE_PUB/age-withdraw`` and got a + `AgeWithdrawResponse` from the exchange. By calling this + endpoint, the client has to reveal each coin and their ``kappa - 1`` + age commitments, except for the age commitments with index + ``noreveal_index``. The hash of all commitments from the former withdraw + request is given as the ``$ACH`` value in the URL to this endpoint. - Withdraw a coin of the specified denomination. Note that the client should - commit all of the request details, including the private key of the coin and - the blinding factor, to disk *before* issuing this request, so that it can - recover the information if necessary in case of transient failures, like - power outage, network outage, etc. - **Request:** The request body must be a `WithdrawRequest` object. + **Request:** The request body must be a `AgeWithdrawRevealRequest` object. **Response:** :http:statuscode:`200 OK`: - The request was successful, and the response is a `WithdrawResponse`. Note that repeating exactly the same request - will again yield the same response, so if the network goes down during the - transaction or before the client can commit the coin signature to disk, the - coin is not lost. - :http:statuscode:`403 Forbidden`: - The signature is invalid. - This response comes with a standard `ErrorDetail` response. + The request was successful, and the response is a `AgeWithdrawRevealResponse`. + Note that repeating exactly the same request will again yield the same + response, so if the network goes down during the transaction or before the + client can commit the coin signature to disk, the coin is not lost. :http:statuscode:`404 Not found`: - The denomination key or the reserve are not known to the exchange. If the - denomination key is unknown, this suggests a bug in the wallet as the - wallet should have used current denomination keys from ``/keys``. - In this case, the response will be a `DenominationUnknownMessage`. - If the reserve is unknown, the wallet should not report a hard error yet, but - instead simply wait for up to a day, as the wire transaction might simply - not yet have completed and might be known to the exchange in the near future. - In this case, the wallet should repeat the exact same request later again - using exactly the same blinded coin. + The provided commitment $ACH is unknown. :http:statuscode:`409 Conflict`: - One of the following reasons occured: + The reveal operation failed and the response is an `WithdrawError` object. + The error codes indicate one of two cases: - 1. The balance of the reserve is not sufficient to withdraw the coins of the - indicated denominations. The response is `WithdrawError` object. + 1. An age commitment for at least one of the coins did not fulfill the + required maximum age requirement of the corresponding reserve. + Error code: + ``TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE``. + 2. The computation of the hash of the commitment with provided input does + result in the value $ACH. + Error code: + ``TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH`` - 2. The reserve has a birthday set and requires a request to ``/age-withdraw`` instead. - The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in the call to ``/age-withdraw`` - :http:statuscode:`410 Gone`: - The requested denomination key is not yet or no longer valid. - It either before the validity start, past the expiration or was revoked. The response is a - `DenominationExpiredMessage`. Clients must evaluate - the error code provided to understand which of the - cases this is and handle it accordingly. - :http:statuscode:`451 Unavailable for Legal Reasons`: - This reserve has received funds from a purse or the amount withdrawn - exceeds another legal threshold and thus the reserve must - be upgraded to an account (with KYC) before the withdraw can - complete. Note that this response does NOT affirm that the - withdraw will ultimately complete with the requested amount. - The user should be redirected to the provided location to perform - the required KYC checks to open the account before withdrawing. - Afterwards, the request should be repeated. - The response will be an `KycNeededRedirect` object. - Implementation note: internally, we need to - distinguish between upgrading the reserve to an - account (due to P2P payment) and identifying the - owner of the origin bank account (due to exceeding - the withdraw amount threshold), as we need to create - a different payto://-URI for the KYC check depending - on the case. + .. ts:def:: AgeWithdrawRevealRequest + interface AgeWithdrawRevealRequest { + // Array of ``n`` of ``(kappa - 1)`` disclosed coin master secrets, from + // which the coins' private key, blinding, nonce (for Clause-Schnorr) and + // age-restriction is calculated. + // + // Given each coin's private key and age commitment, the exchange will + // calculate each coin's blinded hash value und use all those (disclosed) + // blinded hashes together with the non-disclosed envelopes ``coin_evs`` + // during the verification of the original age-withdraw-commitment. + disclosed_coin_secrets: AgeRestrictedCoinSecret[][]; + } - **Details:** + .. ts:def:: AgeRestrictedCoinSecret - .. ts:def:: DenominationExpiredMessage + // The Master key material from which the coins' private key ``coin_priv``, + // blinding ``beta`` and nonce ``nonce`` (for Clause-Schnorr) itself are + // derived as usually in wallet-core. Given a coin's master key material, + // the age commitment for the coin MUST be derived from this private key as + // follows: + // + // Let m ∈ {1,...,M} be the maximum age group as defined in the reserve + // that the wallet can commit to. + // + // For age group $AG ∈ {1,...m}, set + // seed = HDKF(coin_secret, "age-commitment", $AG) + // p[$AG] = Edx25519_generate_private(seed) + // and calculate the corresponding Edx25519PublicKey as + // q[$AG] = Edx25519_public_from_private(p[$AG]) + // + // For age groups $AG ∈ {m,...,M}, set + // f[$AG] = HDKF(coin_secret, "age-factor", $AG) + // and calculate the corresponding Edx25519PublicKey as + // q[$AG] = Edx25519_derive_public(`PublishedAgeRestrictionBaseKey`, f[$AG]) + // + type AgeRestrictedCoinSecret = string; - interface DenominationExpiredMessage { + .. ts:def:: PublishedAgeRestrictionBaseKey - // Taler error code. Note that beyond - // expiration this message format is also - // used if the key is not yet valid, or - // has been revoked. - code: number; + // The value for ``PublishedAgeRestrictionBaseKey`` is a randomly chosen + // `Edx25519PublicKey` for which the private key is not known to the clients. It is + // used during the age-withdraw protocol so that clients can proof that they + // derived all public keys to age groups higher than their allowed maximum + // from this particular value. + const PublishedAgeRestrictionBaseKey = + new Edx25519PublicKey("CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG"); - // Signature by the exchange over a - // `TALER_DenominationExpiredAffirmationPS`. - // Must have purpose ``TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED``. - exchange_sig: EddsaSignature; + .. ts:def:: AgeWithdrawRevealResponse - // Public key of the exchange used to create - // the 'exchange_sig. - exchange_pub: EddsaPublicKey; + interface AgeWithdrawRevealResponse { + // List of the exchange's blinded RSA signatures on the new coins. + ev_sigs : BlindedDenominationSignature[]; + } - // Hash of the denomination public key that is unknown. - h_denom_pub: HashCode; - // When was the signature created. - timestamp: Timestamp; +.. _reserve-history: - // What kind of operation was requested that now - // failed? - oper: string; - } +--------------- +Reserve History +--------------- +.. http:get:: /reserves/$RESERVE_PUB/history - .. ts:def:: WithdrawRequest + Request information about the full history of + a reserve or an account. - interface WithdrawRequest { - // Hash of a denomination public key (RSA), specifying the type of coin the client - // would like the exchange to create. - denom_pub_hash: HashCode; + **Request:** - // Coin's blinded public key, should be (blindly) signed by the exchange's - // denomination private key. - coin_ev: CoinEnvelope; + The GET request should come with the following HTTP headers: - // Signature of `TALER_WithdrawRequestPS` created with - // the `reserves's private key <reserve-priv>` - // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW``. - reserve_sig: EddsaSignature; + *If-None-Match*: The client MAY provide an ``If-None-Match`` header with an + Etag. In that case, the server MUST additionally respond with an ``304`` + status code in case the reserve history matches the provided Etag. - } + *Taler-Reserve-History-Signature*: The client MUST provide Base-32 encoded + EdDSA signature over a TALER_SIGNATURE_RESERVE_HISTORY_REQUEST made with + the respective ``$RESERVE_PRIV``, affirming desire to download the current + reserve transaction history. - .. ts:def:: WithdrawResponse + :query start=OFFSET: *Optional.* Only return reserve history entries with + offsets above the given OFFSET. Allows clients to not + retrieve history entries they already have. - interface WithdrawResponse { - // The blinded signature over the 'coin_ev', affirms the coin's - // validity after unblinding. - ev_sig: BlindedDenominationSignature; + **Response:** - } + :http:statuscode:`200 OK`: + The exchange responds with a `ReserveHistory` object; the reserve was known to the exchange. + :http:statuscode:`204 No content`: + The reserve history is known, but at this point from the given starting point it is empty. Can only happen if OFFSET was positive. + :http:statuscode:`304 Not modified`: + The reserve history matches the one identified by the "If-none-match" HTTP header of the request. + :http:statuscode:`403 Forbidden`: + The *TALER_SIGNATURE_RESERVE_HISTORY_REQUEST* is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The reserve key does not belong to a reserve known to the exchange. - .. ts:def:: BlindedDenominationSignature + **Details:** - type BlindedDenominationSignature = - | RsaBlindedDenominationSignature - | CSBlindedDenominationSignature; + .. ts:def:: ReserveHistory - .. ts:def:: RsaBlindedDenominationSignature + interface ReserveHistory { + // Balance left in the reserve. + balance: Amount; - interface RsaBlindedDenominationSignature { - cipher: "RSA"; + // If set, gives the maximum age group that the client is required to set + // during withdrawal. + maximum_age_group: number; - // (blinded) RSA signature - blinded_rsa_signature: BlindedRsaSignature; + // Transaction history for this reserve. + // May be partial (!). + history: TransactionHistoryItem[]; } - .. ts:def:: CSBlindedDenominationSignature - - interface CSBlindedDenominationSignature { - type: "CS"; + Objects in the transaction history have the following format: - // Signer chosen bit value, 0 or 1, used - // in Clause Blind Schnorr to make the - // ROS problem harder. - b: Integer; + .. ts:def:: TransactionHistoryItem - // Blinded scalar calculated from c_b. - s: Cs25519Scalar; + // Union discriminated by the "type" field. + type TransactionHistoryItem = + | AccountSetupTransaction + | ReserveWithdrawTransaction + | ReserveAgeWithdrawTransaction + | ReserveCreditTransaction + | ReserveClosingTransaction + | ReserveOpenRequestTransaction + | ReserveCloseRequestTransaction + | PurseMergeTransaction; - } + .. ts:def:: AccountSetupTransaction - .. ts:def:: KycNeededRedirect + interface AccountSetupTransaction { + type: "SETUP"; - interface KycNeededRedirect { + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - // Numeric `error code <error-codes>` unique to the condition. - // Should always be ``TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED``. - code: number; + // KYC fee agreed to by the reserve owner. + kyc_fee: Amount; - // Human-readable description of the error, i.e. "missing parameter", "commitment violation", ... - // Should give a human-readable hint about the error's nature. Optional, may change without notice! - hint?: string; + // Time when the KYC was triggered. + kyc_timestamp: Timestamp; - // Hash of the payto:// account URI that identifies - // the account which is being KYCed. - h_payto: PaytoHash; + // Hash of the wire details of the account. + // Note that this hash is unsalted and potentially + // private (as it could be inverted), hence access + // to this endpoint must be authorized using the + // private key of the reserve. + h_wire: HashCode; - // Legitimization target that the merchant should - // use to check for its KYC status using - // the ``/kyc-check/$REQUIREMENT_ROW/...`` endpoint. - requirement_row: Integer; + // Signature created with the reserve's private key. + // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_SETUP_REQUEST`` over + // a ``TALER_AccountSetupRequestSignaturePS``. + reserve_sig: EddsaSignature; } - .. ts:def:: WithdrawError + .. ts:def:: ReserveWithdrawTransaction - interface WithdrawError { - // Text describing the error. - hint: string; + interface ReserveWithdrawTransaction { + type: "WITHDRAW"; - // Detailed error code. - code: Integer; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - // Amount left in the reserve. - balance: Amount; + // Amount withdrawn. + amount: Amount; - // History of the reserve's activity, in the same format - // as returned by ``/reserve/$RID/history``. - history: TransactionHistoryItem[] - } + // Hash of the denomination public key of the coin. + h_denom_pub: HashCode; + // Hash of the blinded coin to be signed. + h_coin_envelope: HashCode; + // Signature over a `TALER_WithdrawRequestPS` + // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW`` + // created with the reserve's private key. + reserve_sig: EddsaSignature; + // Fee that is charged for withdraw. + withdraw_fee: Amount; + } -Batch Withdraw -~~~~~~~~~~~~~~ + .. ts:def:: ReserveAgeWithdrawTransaction + interface ReserveAgeWithdrawTransaction { + type: "AGEWITHDRAW"; -.. http:post:: /reserves/$RESERVE_PUB/batch-withdraw + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - Withdraw multiple coins from the same reserve. Note that the client should - commit all of the request details, including the private key of the coins and - the blinding factors, to disk *before* issuing this request, so that it can - recover the information if necessary in case of transient failures, like - power outage, network outage, etc. + // Total Amount withdrawn. + amount: Amount; - **Request:** The request body must be a `BatchWithdrawRequest` object. + // Commitment of all ``n*kappa`` blinded coins. + h_commitment: HashCode; - **Response:** + // Signature over a `TALER_AgeWithdrawRequestPS` + // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW`` + // created with the reserve's private key. + reserve_sig: EddsaSignature; - :http:statuscode:`200 OK`: - The request was successful, and the response is a `BatchWithdrawResponse`. - Note that repeating exactly the same request will again yield the same - response, so if the network goes down during the transaction or before the - client can commit the coin signature to disk, the coin is not lost. - :http:statuscode:`403 Forbidden`: - A signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`404 Not found`: - A denomination key or the reserve are not known to the exchange. If the - denomination key is unknown, this suggests a bug in the wallet as the - wallet should have used current denomination keys from ``/keys``. - In this case, the response will be a `DenominationUnknownMessage`. - If the reserve is unknown, the wallet should not report a hard error yet, but - instead simply wait for up to a day, as the wire transaction might simply - not yet have completed and might be known to the exchange in the near future. - In this case, the wallet should repeat the exact same request later again - using exactly the same blinded coin. - :http:statuscode:`409 Conflict`: - One of the following reasons occured: + // Fee that is charged for withdraw. + withdraw_fee: Amount; + } - 1. The balance of the reserve is not sufficient to withdraw the coins of the - indicated denominations. The response is `WithdrawError` object. - 2. The reserve has a birthday set and requires a request to ``/age-withdraw`` instead. - The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in the call to ``/age-withdraw`` - :http:statuscode:`410 Gone`: - A requested denomination key is not yet or no longer valid. - It either before the validity start, past the expiration or was revoked. - The response is a `DenominationExpiredMessage`. Clients must evaluate the - error code provided to understand which of the cases this is and handle it - accordingly. - :http:statuscode:`451 Unavailable for Legal Reasons`: - This reserve has received funds from a purse or the amount withdrawn - exceeds another legal threshold and thus the reserve must - be upgraded to an account (with KYC) before the withdraw can - complete. Note that this response does NOT affirm that the - withdraw will ultimately complete with the requested amount. - The user should be redirected to the provided location to perform - the required KYC checks to open the account before withdrawing. - Afterwards, the request should be repeated. - The response will be an `KycNeededRedirect` object. + .. ts:def:: ReserveCreditTransaction - Implementation note: internally, we need to - distinguish between upgrading the reserve to an - account (due to P2P payment) and identifying the - owner of the origin bank account (due to exceeding - the withdraw amount threshold), as we need to create - a different payto://-URI for the KYC check depending - on the case. + interface ReserveCreditTransaction { + type: "CREDIT"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - **Details:** + // Amount deposited. + amount: Amount; - .. ts:def:: BatchWithdrawRequest + // Sender account ``payto://`` URL. + sender_account_url: string; - interface BatchWithdrawRequest { - // Array of requests for the individual coins to withdraw. - planchets: WithdrawRequest[]; + // Opaque identifier internal to the exchange that + // uniquely identifies the wire transfer that credited the reserve. + wire_reference: Integer; + // Timestamp of the incoming wire transfer. + timestamp: Timestamp; } - .. ts:def:: BatchWithdrawResponse + .. ts:def:: ReserveClosingTransaction - interface BatchWithdrawResponse { - // Array of blinded signatures, in the same order as was - // given in the request. - ev_sigs: WithdrawResponse[]; + interface ReserveClosingTransaction { + type: "CLOSING"; - } + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; -Withdraw with Age Restriction -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Closing balance. + amount: Amount; -If the reserve was marked with a maximum age group, the client has to perform a -cut&choose protocol with the exchange. It first calls -``/reserves/$RESERVE_PUB/age-withdraw`` and commits to ``n*kappa`` coins. On -success, the exchange answers this request with an noreveal-index. The client -then has to call ``/age-withdraw/$ACH/reveal`` to reveal all ``n*(kappa - 1)`` -coins along with their age commitments to proof that they were appropriate. -If so, the exchange will blindly sign ``n`` undisclosed coins from the request. + // Closing fee charged by the exchange. + closing_fee: Amount; + // Wire transfer subject. + wtid: Base32; -.. http:POST:: /reserves/$RESERVE_PUB/age-withdraw + // ``payto://`` URI of the wire account into which the funds were returned to. + receiver_account_details: string; - Withdraw multiple coins *with age restriction* from the same reserve. - Note that the client should commit all of the request details, including the - private key of the coins and the blinding factors, to disk *before* issuing - this request, so that it can recover the information if necessary in case of - transient failures, like power outage, network outage, etc. + // This is a signature over a + // struct `TALER_ReserveCloseConfirmationPS` with purpose + // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``. + exchange_sig: EddsaSignature; - **Request:** The request body must be a `AgeWithdrawRequest` object. + // Public key used to create 'exchange_sig'. + exchange_pub: EddsaPublicKey; - **Response:** + // Time when the reserve was closed. + timestamp: Timestamp; + } - :http:statuscode:`200 OK`: - The request was successful, and the response is a `AgeWithdrawResponse`. - Note that repeating exactly the same request will again yield the same - response, so if the network goes down during the transaction or before the - client can commit the coin signature to disk, the coin is not lost. - :http:statuscode:`403 Forbidden`: - A signature is invalid. - This response comes with a standard `ErrorDetail` response. - :http:statuscode:`409 Conflict`: - One of two reasons occured: - 1. The balance of the reserve is not sufficient to withdraw the coins of the - given amount. The response is a `WithdrawError` object. + .. ts:def:: ReserveOpenRequestTransaction - 2. The provided value for ``max_age`` is higher than the allowed value according to the reserve's birthday. - The response comes with a standard `ErrorDetail` response with error-code ``TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE`` and an additional field ``maximum_allowed_age`` for the maximum age (in years) that the client can commit to in a call to ``/age-withdraw`` - :http:statuscode:`410 Gone`: - A requested denomination key is not yet or no longer valid. - It either before the validity start, past the expiration or was revoked. - The response is a `DenominationExpiredMessage`. Clients must evaluate the - error code provided to understand which of the cases this is and handle it - accordingly. - :http:statuscode:`451 Unavailable for Legal Reasons`: - This reserve has received funds from a purse or the amount withdrawn - exceeds another legal threshold and thus the reserve must - be upgraded to an account (with KYC) before the withdraw can - complete. Note that this response does NOT affirm that the - withdraw will ultimately complete with the requested amount. - The user should be redirected to the provided location to perform - the required KYC checks to open the account before withdrawing. - Afterwards, the request should be repeated. - The response will be an `KycNeededRedirect` object. + interface ReserveOpenRequestTransaction { + type: "OPEN"; + + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - .. ts:def:: AgeWithdrawRequest + // Open fee paid from the reserve. + open_fee: Amount; - interface AgeWithdrawRequest { - // Array of ``n`` hash codes of denomination public keys to order. - // These denominations MUST support age restriction as defined in the - // output to /keys. - // The sum of all denomination's values and fees MUST be at most the - // balance of the reserve. The balance of the reserve will be - // immediatley reduced by that amount. - denoms_h: HashCode[]; + // This is a signature over + // a struct `TALER_ReserveOpenPS` with purpose + // ``TALER_SIGNATURE_WALLET_RESERVE_OPEN``. + reserve_sig: EddsaSignature; - // ``n`` arrays of ``kappa`` entries with blinded coin envelopes. Each - // (toplevel) entry represents ``kappa`` canditates for a particular - // coin. The exchange will respond with an index ``gamma``, which is - // the index that shall remain undisclosed during the reveal phase. - // The SHA512 hash $ACH over the blinded coin envelopes is the commitment - // that is later used as the key to the reveal-URL. - blinded_coins_evs: CoinEnvelope[][]; + // Timestamp of the open request. + request_timestamp: Timestamp; - // The maximum age to commit to. MUST be the same as the maximum - // age in the reserve. - max_age: number; + // Requested expiration. + requested_expiration: Timestamp; + + // Requested number of free open purses. + requested_min_purses: Integer; - // Signature of `TALER_AgeWithdrawRequestPS` created with - // the `reserves's private key <reserve-priv>` - // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``. - reserve_sig: EddsaSignature; } - .. ts:def:: AgeWithdrawResponse + .. ts:def:: ReserveCloseRequestTransaction - interface AgeWithdrawResponse { - // index of the commitments that the client doesn't - // have to disclose - noreveal_index: Integer; + interface ReserveCloseRequestTransaction { + type: "CLOSE"; - // Signature of `TALER_AgeWithdrawConfirmationPS` whereby - // the exchange confirms the ``noreveal_index``. - exchange_sig: EddsaSignature; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - // `Public EdDSA key <sign-key-pub>` of the exchange that was used to - // generate the signature. Should match one of the exchange's signing - // keys from ``/keys``. Again given explicitly as the client might - // otherwise be confused by clock skew as to which signing key was used. - exchange_pub: EddsaPublicKey; + // This is a signature over + // a struct `TALER_ReserveClosePS` with purpose + // ``TALER_SIGNATURE_WALLET_RESERVE_CLOSE``. + reserve_sig: EddsaSignature; + + // Target account ``payto://``, optional. + h_payto?: PaytoHash; + + // Timestamp of the close request. + request_timestamp: Timestamp; } + .. ts:def:: ReserveCreditTransaction + interface ReserveCreditTransaction { + type: "CREDIT"; -.. http:POST:: /age-withdraw/$ACH/reveal + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - The client has previously committed to multiple coins with age restriction - in a call to ``/reserve/$RESERVE_PUB/age-withdraw`` and got a - `AgeWithdrawResponse` from the exchange. By calling this - endpoint, the client has to reveal each coin and their ``kappa - 1`` - age commitments, except for the age commitments with index - ``noreveal_index``. The hash of all commitments from the former withdraw - request is given as the ``$ACH`` value in the URL to this endpoint. + // Amount deposited. + amount: Amount; + // Sender account ``payto://`` URL. + sender_account_url: string; - **Request:** The request body must be a `AgeWithdrawRevealRequest` object. + // Opaque identifier internal to the exchange that + // uniquely identifies the wire transfer that credited the reserve. + wire_reference: Integer; - **Response:** + // Timestamp of the incoming wire transfer. + timestamp: Timestamp; + } - :http:statuscode:`200 OK`: - The request was successful, and the response is a `AgeWithdrawRevealResponse`. - Note that repeating exactly the same request will again yield the same - response, so if the network goes down during the transaction or before the - client can commit the coin signature to disk, the coin is not lost. - :http:statuscode:`404 Not found`: - The provided commitment $ACH is unknown. - :http:statuscode:`409 Conflict`: - The reveal operation failed and the response is an `WithdrawError` object. - The error codes indicate one of two cases: + .. ts:def:: PurseMergeTransaction - 1. An age commitment for at least one of the coins did not fulfill the - required maximum age requirement of the corresponding reserve. - Error code: - ``TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE``. - 2. The computation of the hash of the commitment with provided input does - result in the value $ACH. - Error code: - ``TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH`` + interface PurseMergeTransaction { + type: "MERGE"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; - .. ts:def:: AgeWithdrawRevealRequest + // SHA-512 hash of the contact of the purse. + h_contract_terms: HashCode; - interface AgeWithdrawRevealRequest { - // Array of ``n`` of ``(kappa - 1)`` disclosed coin master secrets, from - // which the coins' private key, blinding, nonce (for Clause-Schnorr) and - // age-restriction is calculated. - // - // Given each coin's private key and age commitment, the exchange will - // calculate each coin's blinded hash value und use all those (disclosed) - // blinded hashes together with the non-disclosed envelopes ``coin_evs`` - // during the verification of the original age-withdraw-commitment. - disclosed_coin_secrets: AgeRestrictedCoinSecret[][]; - } + // EdDSA public key used to approve merges of this purse. + merge_pub: EddsaPublicKey; - .. ts:def:: AgeRestrictedCoinSecret + // Minimum age required for all coins deposited into the purse. + min_age: Integer; - // The Master key material from which the coins' private key ``coin_priv``, - // blinding ``beta`` and nonce ``nonce`` (for Clause-Schnorr) itself are - // derived as usually in wallet-core. Given a coin's master key material, - // the age commitment for the coin MUST be derived from this private key as - // follows: - // - // Let m ∈ {1,...,M} be the maximum age group as defined in the reserve - // that the wallet can commit to. - // - // For age group $AG ∈ {1,...m}, set - // seed = HDKF(coin_secret, "age-commitment", $AG) - // p[$AG] = Edx25519_generate_private(seed) - // and calculate the corresponding Edx25519PublicKey as - // q[$AG] = Edx25519_public_from_private(p[$AG]) - // - // For age groups $AG ∈ {m,...,M}, set - // f[$AG] = HDKF(coin_secret, "age-factor", $AG) - // and calculate the corresponding Edx25519PublicKey as - // q[$AG] = Edx25519_derive_public(`PublishedAgeRestrictionBaseKey`, f[$AG]) - // - type AgeRestrictedCoinSecret = string; + // Number that identifies who created the purse + // and how it was paid for. + flags: Integer; - .. ts:def:: PublishedAgeRestrictionBaseKey + // Purse public key. + purse_pub: EddsaPublicKey; - // The value for ``PublishedAgeRestrictionBaseKey`` is a randomly chosen - // `Edx25519PublicKey` for which the private key is not known to the clients. It is - // used during the age-withdraw protocol so that clients can proof that they - // derived all public keys to age groups higher than their allowed maximum - // from this particular value. - const PublishedAgeRestrictionBaseKey = - new Edx25519PublicKey("CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG"); + // EdDSA signature of the account/reserve affirming the merge + // over a `TALER_AccountMergeSignaturePS`. + // Must be of purpose ``TALER_SIGNATURE_ACCOUNT_MERGE`` + reserve_sig: EddsaSignature; - .. ts:def:: AgeWithdrawRevealResponse + // Client-side timestamp of when the merge request was made. + merge_timestamp: Timestamp; - interface AgeWithdrawRevealResponse { - // List of the exchange's blinded RSA signatures on the new coins. - ev_sigs : BlindedDenominationSignature[]; + // Indicative time by which the purse should expire + // if it has not been merged into an account. At this + // point, all of the deposits made should be + // auto-refunded. + purse_expiration: Timestamp; + + // Purse fee the reserve owner paid for the purse creation. + purse_fee: Amount; + + // Total amount merged into the reserve. + // (excludes fees). + amount: Amount; + + // True if the purse was actually merged. + // If false, only the purse_fee has an impact + // on the reserve balance! + merged: boolean; } @@ -2640,7 +2576,7 @@ If so, the exchange will blindly sign ``n`` undisclosed coins from the request. Coin History ------------ -.. http:GET:: /coins/$COIN_PUB +.. http:GET:: /coins/$COIN_PUB/history Obtain the transaction history of a coin. Used only in special cases, like when the exchange claims a double-spending error and the wallet does not @@ -2649,17 +2585,35 @@ Coin History **Request:** - If possible, clients should set an "If-none-match" HTTP header based on a - previous "Etag" returned by the exchange. + The GET request should come with the following HTTP headers: + + *If-None-Match*: The client MAY provide an ``If-None-Match`` header with an + Etag. In that case, the server MUST additionally respond with an ``304`` + status code in case the coin history matches the provided Etag. + + *Taler-Coin-History-Signature*: The client MUST provide Base-32 encoded + EdDSA signature over a TALER_SIGNATURE_COIN_HISTORY_REQUEST made with + the respective ``$RESERVE_PRIV``, affirming desire to download the current + coin transaction history. + + :query start=OFFSET: *Optional.* Only return coin history entries with + offsets above the given OFFSET. Allows clients to not + retrieve history entries they already have. + **Response:** :http:statuscode:`200 OK`: The operation succeeded, the exchange confirms that no double-spending took place. The response will include a `CoinHistoryResponse` object. + :http:statuscode:`204 No content`: + The reserve history is known, but at this point from the given starting point it is empty. Can only happen if OFFSET was positive. :http:statuscode:`304 Not modified`: The coin history has not changed since the previous query (detected via Etag in "If-none-match" header). + :http:statuscode:`403 Forbidden`: + The *TALER_SIGNATURE_COIN_HISTORY_REQUEST* is invalid. + This response comes with a standard `ErrorDetail` response. :http:statuscode:`404 Not found`: The coin public key is not (yet) known to the exchange. @@ -2689,6 +2643,11 @@ Coin History interface CoinDepositTransaction { type: "DEPOSIT"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value absorbed (or restored in the // case of a refund) by this transaction. // The amount given includes @@ -2732,6 +2691,11 @@ Coin History interface CoinMeltTransaction { type: "MELT"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value absorbed by this transaction. // Note that for melt this means the amount given includes // the melt fee. The current coin value can thus be computed by @@ -2761,6 +2725,11 @@ Coin History interface CoinRefundTransaction { type: "REFUND"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value restored // by this transaction. // The amount given excludes the transaction fee. @@ -2791,6 +2760,11 @@ Coin History interface CoinRecoupTransaction { type: "RECOUP"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value absorbed // by this transaction. // The current coin value can thus be computed by @@ -2832,6 +2806,11 @@ Coin History interface CoinOldCoinRecoupTransaction { type: "OLD-COIN-RECOUP"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value restored // by this transaction. // The current coin value can thus be computed by @@ -2856,6 +2835,11 @@ Coin History interface CoinRecoupRefreshTransaction { type: "RECOUP-REFRESH"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value absorbed // by this transaction. // The current coin value can thus be computed by @@ -2898,6 +2882,11 @@ Coin History interface CoinPurseDepositTransaction { type: "PURSE-DEPOSIT"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value absorbed // by this transaction. // Note that this means the amount given includes @@ -2932,6 +2921,11 @@ Coin History interface CoinPurseRefundTransaction { type: "PURSE-REFUND"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value restored // by this transaction. // The amount given excludes the refund fee. @@ -2961,6 +2955,11 @@ Coin History interface CoinReserveOpenDepositTransaction { type: "RESERVE-OPEN-DEPOSIT"; + // Offset of this entry in the reserve history. + // Useful to request incremental histories via + // the "start" query parameter. + history_offset: Integer; + // The total amount of the coin's value absorbed // by this transaction. // Note that this means the amount given includes @@ -5490,3 +5489,61 @@ naturally expire and possibly (5) wire the funds to a designated account. wire_amount: Amount; } + + +.. _delete-reserve: + +.. http:DELETE:: /reserves/$RESERVE_PUB + + Forcefully closes a reserve. + The request header must contain an *Account-Request-Signature*. + Note: this endpoint is not currently implemented! + + **Request:** + + *Account-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$ACCOUNT_PRIV``, affirming its authorization to delete the account. The purpose used MUST be ``TALER_SIGNATURE_RESERVE_CLOSE``. + + :query force=BOOLEAN: *Optional.* If set to 'true' specified, the exchange + will delete the account even if there is a balance remaining. + + **Response:** + + :http:statuscode:`200 OK`: + The operation succeeded, the exchange provides details + about the account deletion. + The response will include a `ReserveDeletedResponse` object. + :http:statuscode:`403 Forbidden`: + The *Account-Request-Signature* is invalid. + This response comes with a standard `ErrorDetail` response. + :http:statuscode:`404 Not found`: + The account is unknown to the exchange. + :http:statuscode:`409 Conflict`: + The account is still has digital cash in it, the associated + wire method is ``void`` and the *force* option was not provided. + This response comes with a standard `ErrorDetail` response. + + **Details:** + + .. ts:def:: ReserveDeletedResponse + + interface ReserveDeletedResponse { + + // Final balance of the account. + closing_amount: Amount; + + // Current time of the exchange, used as part of + // what the exchange signs over. + close_time: Timestamp; + + // Hash of the wire account into which the remaining + // balance will be transferred. Note: may be the + // hash over ``payto://void/`, in which case the + // balance is forfeit to the profit of the exchange. + h_wire: HashCode; + + // This is a signature over a + // struct ``TALER_AccountDeleteConfirmationPS`` with purpose + // ``TALER_SIGNATURE_EXCHANGE_RESERVE_DELETED``. + exchange_sig: EddsaSignature; + + }