summaryrefslogtreecommitdiff
path: root/core/api-exchange.rst
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-05-03 21:47:14 +0200
committerChristian Grothoff <christian@grothoff.org>2021-05-03 21:47:14 +0200
commit446fec10a4e1fb7923f6732f80290fcb6c7bc1af (patch)
treee417b8efb69b3dd957b353b1231044f1ecb46bc0 /core/api-exchange.rst
parenta48b3b90b7b0a36d3fd93a58b24125989a6442a3 (diff)
downloaddocs-446fec10a4e1fb7923f6732f80290fcb6c7bc1af.tar.gz
docs-446fec10a4e1fb7923f6732f80290fcb6c7bc1af.tar.bz2
docs-446fec10a4e1fb7923f6732f80290fcb6c7bc1af.zip
first cut at DD13 spec writing
Diffstat (limited to 'core/api-exchange.rst')
-rw-r--r--core/api-exchange.rst713
1 files changed, 712 insertions, 1 deletions
diff --git a/core/api-exchange.rst b/core/api-exchange.rst
index 1e9db1d..12cb4e1 100644
--- a/core/api-exchange.rst
+++ b/core/api-exchange.rst
@@ -123,6 +123,15 @@ possibly by using HTTPS.
// The exchange's currency.
currency: string;
+ // Purse fee, charged only if a purse is abandoned
+ // and was not covered by the account limit.
+ purse_fee: Amount;
+
+ // Non-negative number of concurrent purses that any
+ // account holder is allowed to create without having
+ // to pay the purse_fee.
+ purse_account_limit: integer;
+
// EdDSA master public key of the exchange, used to sign entries
// in ``denoms`` and ``signkeys``.
master_public_key: EddsaPublicKey;
@@ -918,7 +927,7 @@ exchange.
.. ts:def:: TransactionHistoryItem
// Union discriminated by the "type" field.
- type ReserveTransaction =
+ type TransactionHistoryItem =
| ReserveWithdrawTransaction
| ReserveCreditTransaction
| ReserveClosingTransaction
@@ -1971,3 +1980,705 @@ Refunds
// the relevant subset of the transactions.
history: CoinSpendHistoryItem[];
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+.. _exchange_w2w:
+
+--------------------------
+Wallet-to-wallet transfers
+--------------------------
+
+TODO for the spec:
+
+ * endpoint to create purse with KYC'ed account OR payment,
+ but without deposit
+ * endpoint to DELETE account is missing (note: may need
+ two variants: bank-administrative (offline key?)
+ and user-driven)
+ * specify new database schema at exchange
+ * specify what each signature is made over precisely
+ * add KYC fee to /keys
+ * add extended account history fee to /keys
+ * add purse 'GET' expiration (purse_expiration is when
+ deposits are no longer allowed, but we may still want
+ to return the purse status for a bit longer, right?
+ Or only as part of the coins/accounts status? What about
+ long-pollers that are active at this expiration time?
+ [should not DELETE the purse data while the long pollers
+ are still potentially waking up, so needs a grace period, at least])
+ * update wire transfer API to enable WAD IDs (and while we are
+ at it, should probably also write extended version to allow
+ _merchants_ to query for their inbound transfers, so spec
+ for both WADs and regular WTID!)
+ * specify WAD ID format (keep separte from
+ WTIDs and reserve public keys!)
+ * Update coin history replies to include purse actions.
+ * Merging a purse into an account that is never KYC'ed means
+ the funds are lost. We need to specify an account expiration
+ time (after funds are merged, but KYC never happens).
+
+Discussion:
+
+ * Should the account-withdraw be a separate endpoint from
+ the reserve withdraw, or should we *extend* the reserve-withdraw
+ with the "missing KYC" response?
+ * Should the account-history be a separate endpoint from
+ the reserve history, or should we *extend* the reserve-history
+ with the new history entries and the payment option?
+ * Should we return somewhere a list of the desired
+ KYC attributes that the wallet should (optionally)
+ POST to the exchange when initiating KYC? (see: "attributes").
+
+Notes:
+
+ * Current API does not allow 'merging' a purse directly into
+ a regular bank account. The merge MUST go into an account.
+ * What do we do with KYC'ed accounts where the user fails to
+ drain the funds? Just keep them? Or do we require getting an
+ IBAN as part of KYC and 'close' the account that way?
+
+
+Purses
+^^^^^^
+
+
+.. http:GET:: /purses/$PURSE_PUB
+
+ Obtain information about a purse. The request header must
+ contain a *Purse-Request-Signature*.
+
+ **Request:**
+
+ *Purse-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$PURSE_PRIV``, affirming its authorization to download the purse status. The purpose used MUST be ``TALER_SIGNATURE_PURSE_STATUS`` (NUMBER: TBD).
+
+ :query merge_timeout_ms=NUMBER: *Optional.* If specified,
+ the exchange
+ will wait up to ``timeout_ms`` milliseconds for completion
+ of a merge operation before sending the HTTP response.
+ :query deposit_timeout_ms=NUMBER: *Optional.* If specified,
+ the exchange
+ will wait up to ``timeout_ms`` milliseconds for completion
+ of a deposit operation before sending the HTTP response.
+ :query contract=BOOLEAN: *Optional.* If 'false' is specified,
+ the exchange will not return the encrypted contract, saving
+ bandwidth for clients that already know it.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange provides details
+ about the purse.
+ The response will include a `PurseStatus` object.
+ :http:statuscode:`401 Unauthorized`:
+ The *Purse-Request-Signature* is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The purse is unknown to the exchange.
+
+ **Details:**
+
+ .. ts:def:: PurseStatus
+
+ interface PurseStatus {
+
+ // Total amount that must be paid into the purse.
+ total_purse_amount: Amount;
+
+ // Total amount deposited into the purse so far.
+ total_deposit_amount: Amount;
+
+ // Indicative time by which the purse expires
+ // if it has not been merged into an account. At this
+ // point, all of the deposits made will be auto-refunded.
+ purse_expiration: Timestamp;
+
+ // Indicative time at which the exchange is answering the
+ // status request. Used as part of `exchange_sig`.
+ status_timestamp: Timestamp;
+
+ // Maximum deposit fees that can be charged under the contract.
+ max_deposit_fees: Amount;
+
+ // SHA-512 hash of the contact of the purse.
+ h_contract_terms: HashCode;
+
+ // EdDSA signature of the exchange affirming the purse status.
+ exchange_sig: EddsaSignature;
+
+ // AES-GCM Encrypted contract terms using encryption
+ // key derived from DH of 'contract_pub' and the 'purse_pub'.
+ // Optional, may be omitted if not desired by the client.
+ e_contract_terms?: byte[];
+
+ // If a merge request was received, information about the
+ // merge request. Omitted if the purse has not yet received
+ // a merge request.
+ merge_request?: MergeRequest;
+
+ }
+
+
+.. http:POST:: /purses/$PURSE_PUB/deposit
+
+ Create a purse without an account, but with associated payment.
+
+ **Request:** The request body must be a `PurseRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange confirms that all
+ coins were deposited into the purse.
+ The response will include a `PursePaymentSuccess` object.
+ :http:statuscode:`202 Accepted`:
+ The payment was accepted, but insufficient to reach the
+ specified purse balance. The client should make further
+ purse deposits before the expiration deadline.
+ The response will include a `PursePaymentAccepted` object.
+ :http:statuscode:`401 Unauthorized`:
+ A coin signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`403 Forbidden`:
+ The server is denying the operation as a purse with a
+ different contract or total amount already exists.
+ This response comes with a standard `PurseConflict` response.
+ :http:statuscode:`404 Not found`:
+ Either the denomination key is not recognized (expired or invalid) or
+ the wire type is not recognized.
+ :http:statuscode:`409 Conflict`:
+ The deposit operation has either failed because a coin has insufficient
+ residual value, or because the same public key of the coin has been
+ previously used with a different denomination. Which case it is
+ can be decided by looking at the error code
+ (``TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS`` or ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
+ The fields of the response are the same in both cases.
+ The request should not be repeated again with this coin.
+ In this case, the response is a `PurseDepositDoubleSpendError`.
+ If the value of all successful coins is below the purse fee,
+ the exchange may not setup the purse at all.
+
+
+ **Details:**
+
+ .. ts:def:: PursePaymentSuccess
+
+ interface PursePaymentSuccess {
+
+ // Total amount paid into the purse.
+ total_purse_amount: Amount;
+
+ // Total deposit fees charged.
+ total_deposit_fees: Amount;
+
+ // EdDSA signature of the exchange affirming the payment.
+ // Signs over the above and the purse public key and
+ // the hash of the contract terms.
+ exchange_sig: EddsaSignature;
+
+ }
+
+ .. ts:def:: PursePaymentAccepted
+
+ interface PursePaymentAccepted {
+
+ // Total amount paid so far into the purse, in this
+ // and previous requests.
+ total_amount_deposited: Amount;
+
+ // Total amount contributed by the current request.
+ total_amount_contributed: Amount;
+
+ }
+
+ .. ts:def:: PurseConflict
+
+ interface PurseConflict {
+
+ // Total amount to be paid into the purse as per
+ // the previous request.
+ total_purse_amount: Amount;
+
+ // SHA-512 hash of the contact of the purse.
+ h_contract_terms: HashCode;
+
+ // 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;
+
+ // EdDSA signature of the purse confirming that the
+ // above details hold for this purse.
+ purse_sig: EddsaSignature;
+
+ }
+
+ .. ts:def:: PurseRequest
+
+ interface PurseRequest {
+
+ // EdDSA signature of the purse confirming the key
+ // invariants associated with the purse.
+ // (amount, h_contract_terms, expiration).
+ purse_sig: EddsaSignature;
+
+ // Total amount to be paid into the purse.
+ // Clients may make several requests, i.e. if a
+ // first request failed with a double-spending error.
+ // The exchange will confirm the creation of the
+ // purse once the amount given here is reached.
+ total_purse_amount: Amount;
+
+ // SHA-512 hash of the contact of the purse.
+ h_contract_terms: HashCode;
+
+ // ECDH contract_public key used to encrypt the contract.
+ // Optional as the contract terms may already be known
+ // to the exchange or the other wallet from a different
+ // interaction.
+ contract_pub?: EcdhPublicKey;
+
+ // AES-GCM Encrypted contract terms using encryption
+ // key derived from DH of 'contract_pub' and the 'purse_pub'.
+ // Optional as the contract terms may already be known
+ // to the exchange or the other wallet from a different
+ // interaction.
+ e_contract_terms?: byte[];
+
+ // Client-side timestamp of when the payment was made.
+ timestamp: Timestamp;
+
+ // 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 will be auto-refunded.
+ purse_expiration: Timestamp;
+
+ // Array of coins being deposited into the purse.
+ deposits: PurseDeposit[];
+ }
+
+ .. ts:def:: PurseDeposit {
+
+ // Public key of the coin being deposited into the purse.
+ coin_pub: EddsaPublicKey;
+
+ // Amount to be deposited, can be a fraction of the
+ // coin's total value.
+ contribution: Amount;
+
+ // Hash of denomination RSA key with which the coin is signed.
+ denom_pub_hash: HashCode;
+
+ // Exchange's unblinded RSA signature of the coin.
+ ub_sig: RsaSignature;
+
+ // Signature of `TALER_PurseDepositRequestPS`,
+ // made by the customer with the
+ // `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
+
+ }
+
+ .. ts:def:: PurseDepositDoubleSpendError
+
+ interface DepositDoubleSpendError {
+ // The string constant "insufficient funds".
+ hint: string;
+
+ // Total amount contributed by the current request.
+ // Note that some coins may have been successfully
+ // deposited into the purse, so the total amount
+ // from these coins is listed here.
+ total_amount_contributed: Amount;
+
+ // Public keys of coins that could not be deposited
+ // into the purse, mapped to the coin's histories.
+ coin_map: EddsaPublicKey -> CoinSpendHistoryItem[];
+ }
+
+
+
+.. http:POST:: /purses/$PURSE_PUB/merge
+
+ Merge purse with account, adding the value of the purse into
+ the account.
+
+ **Request:** The request body must be a `MergeRequest` object.
+
+ :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will
+ wait up to ``timeout_ms`` milliseconds to receive payment before
+ reporting on the completion of merge operation. Basically
+ forstalls returning a 202 response for up to timeout milliseconds
+ to possibly return a 200 response instead.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange confirms that the
+ funds were merged into the account.
+ The response will include a `MergeSuccess` object.
+ :http:statuscode:`202 Accepted`:
+ The operation succeeded, the exchange confirms that the
+ merge request is valid. Alas, the purse was still not
+ funded and thus the actual merge is delayed.
+ The response will include a `MergeAccepted` object.
+ :http:statuscode:`401 Unauthorized`:
+ Account signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The refund operation failed as we could not find the purse.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`410 Gone`:
+ The purse has already expired and thus can no longer be merged.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`429 Too Many Requests`:
+ This account is not at this exchange, has not yet passed the
+ KYC checks, or it has exceeded the number of open purses.
+ The client must include payment to create another purse or
+ wait until existing purses have expired.
+
+ **Details:**
+
+ .. ts:def:: MergeSuccess
+
+ interface MergeSuccess {
+
+ // Amount merged (excluding deposit fees).
+ merge_amount: Amount;
+
+ // SHA-512 hash of the contact of the purse.
+ h_contract_terms: HashCode;
+
+ // Time at which the merge came into effect.
+ merge_time: Timestamp;
+
+ // EdDSA signature of the exchange affirming the merge.
+ // Signs over the above and the account public key.
+ exchange_sig: EddsaSignature;
+
+ }
+
+ .. ts:def:: MergeAccepted
+
+ interface MergeAccepted {
+
+ // The number of open purses under the given account.
+ // Useful to calculate how many purses still can be created.
+ open_purses: integer;
+
+ }
+
+ .. ts:def:: MergeRequest
+
+ interface MergeRequest {
+
+ // payto://-URI of the account the purse is to be merged into.
+ // Must be of the form: "payto://taler/EXCHANGE_URL/ACCOUNT_PUB".
+ payto_uri: string;
+
+ // EdDSA signature of the exchange affirming the merge.
+ account_sig: EddsaSignature;
+
+ // EdDSA signature of the purse private key affirming the merge.
+ purse_sig: EddsaSignature;
+
+ // Array of payments made to pay for the creation of the
+ // purse. Can be empty, say if no payment is needed.
+ payments: CreatePurseDeposit[];
+
+ }
+
+ .. ts:def:: CreatePurseDeposit {
+
+ // Public key of the coin being used to pay for creating a purse.
+ coin_pub: EddsaPublicKey;
+
+ // Amount to be deposited, can be a fraction of the
+ // coin's total value.
+ contribution: Amount;
+
+ // Hash of denomination RSA key with which the coin is signed.
+ denom_pub_hash: HashCode;
+
+ // Exchange's unblinded RSA signature of the coin.
+ ub_sig: RsaSignature;
+
+ // Signature of `TALER_PurseCreateRequestPS`,
+ // made by the customer with the
+ // `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
+
+ }
+
+
+
+
+.. _exchange_accounts:
+
+Accounts
+^^^^^^^^
+
+.. http:GET:: /accounts/$ACCOUNT_PUB
+
+ Obtain information about an account. The request header must
+ contain an *Account-Request-Signature*.
+
+ **Request:**
+
+ *Account-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$ACCOUNT_PRIV``, affirming its authorization to access the account status. The purpose used MUST be ``TALER_SIGNATURE_ACCOUNT_STATUS`` (NUMBER: TBD).
+
+ :query history=BOOLEAN: *Optional.* If specified, the exchange
+ will return the recent account history.
+ This is still free of charge.
+ :query full_history=BOOLEAN: *Optional.* If 'true' is specified,
+ the exchange will return the full account history. This
+ may incur a fee that will be charged to the account.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange provides details
+ about the account.
+ The response will include a `AccountHistory` object.
+ :http:statuscode:`401 Unauthorized`:
+ 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.
+
+ **Details:**
+
+ .. ts:def:: AccountHistory
+
+ interface AccountHistory {
+
+ // Current balance of the account.
+ balance: Amount;
+
+ // True if the owner of the account currently satisfies
+ // the required KYC checks.
+ kyc: boolean;
+
+ // Transaction history for this account.
+ history: AccountHistoryItem[];
+ }
+
+ Objects in the transaction history have the following format:
+
+ .. ts:def:: AccountHistoryItem
+
+ // Union discriminated by the "type" field.
+ type AccountHistoryItem =
+ | AccountMergeTransaction
+ | ReserveWithdrawTransaction
+ | ReserveCreditTransaction
+ | ReserveClosingTransaction
+ | ReserveRecoupTransaction;
+
+ .. ts:def:: AccountMergeTransaction
+
+ interface AccountMergeTransaction {
+ type: "MERGE";
+
+ // Amount merged (what was left after fees).
+ amount: Amount;
+
+ // Purse that was merged.
+ purse_pub: EddsaPublicKey;
+
+ // Hash of the contract.
+ h_contract: HashCode;
+
+ // Signature created with the account's private key.
+ account_sig: EddsaSignature;
+
+ // Signature created with the purse's private key.
+ purse_sig: EddsaSignature;
+
+ // Deposit fees that were charged to the purse.
+ deposit_fees: Amount;
+ }
+
+
+.. http:POST:: /accounts
+
+ Create a new account.
+
+ **Request:** The request body must be a `AccountSetupRequest` object.
+
+ :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will
+ wait up to ``timeout_ms`` milliseconds for the KYC gateway to
+ confirm completion of the KYC process.
+
+ **Response:**
+
+ :http:statuscode:`200 Ok`:
+ The operation succeeded, the exchange confirms that the account
+ can now be used.
+ The response will be an `AccountStatus` object.
+ :http:statuscode:`303 See Other`:
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account. Afterwards, the
+ request should be repeated.
+ :http:statuscode:`504 Gateway Timeout`:
+ The exchange did not receive a confirmation from the KYC service
+ within the specified time period. Used when long-polling for the
+ result.
+
+ **Details:**
+
+ .. ts:def:: AccountSetupRequest
+
+ interface AccountSetupRequest {
+
+ // EdDSA public key for the account.
+ account_pub: EddsaPublicKey;
+
+ // EdDSA signature of the account affirming the request
+ // to create the account.
+ account_sig: EddsaPublicKey;
+
+ // Array of payments made to pay for the creation of the
+ // account. Can be empty, say if no payment is needed.
+ payments: CreateAccountDeposit[];
+
+ // Generic key-value map of attributes about the
+ // account owner. Useful to pre-fill KYC forms.
+ // A list of well-known keys is defined in FIXME.
+ attributes: Object;
+
+ }
+
+ .. ts:def:: CreateAccountDeposit {
+
+ // Public key of the coin being used to pay for creating an
+ // account.
+ coin_pub: EddsaPublicKey;
+
+ // Amount to be deposited, can be a fraction of the
+ // coin's total value.
+ contribution: Amount;
+
+ // Hash of denomination RSA key with which the coin is signed.
+ denom_pub_hash: HashCode;
+
+ // Exchange's unblinded RSA signature of the coin.
+ ub_sig: RsaSignature;
+
+ // Signature of `TALER_AccountCreateRequestPS`,
+ // made by the customer with the
+ // `coin's private key <coin-priv>`.
+ coin_sig: EddsaSignature;
+ }
+
+ .. ts:def:: AccountKycStatus
+
+ interface AccountKycStatus {
+
+ // Current time of the exchange, used as part of
+ // what the exchange signs over.
+ now: Timestamp;
+
+ // EdDSA signature of the exchange affirming the account
+ // is KYC'ed.
+ exchange_sig: EddsaSignature;
+
+ }
+
+
+.. http:post:: /accounts/$ACCOUNT_PUB/withdraw
+
+ This endpoint is virtually identical to the withdraw endpoint
+ for reserves, with the only difference being a new response
+ code `303 See Other` which is returned for accounts that
+ have not yet completed the required KYC check.
+
+ **Response**:
+
+ :http:statuscode:`303 See Other`:
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account. Afterwards, the
+ request should be repeated.
+
+
+.. _exchange_wads:
+
+
+Wads
+^^^^
+
+These endpoints are used to manage exchange-to-exchange payments in support of
+wallet-to-wallet payments.
+
+
+.. http:GET:: /wads/$WAD_ID
+
+ Obtain information about a wad.
+
+ **Request:**
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange provides details
+ about the wad.
+ The response will include a `WadDetails` object.
+ :http:statuscode:`404 Not found`:
+ The wad is unknown to the exchange.
+
+ **Details:**
+
+ .. ts:def:: WadDetails
+
+ interface WadDetails {
+
+ // Total transfer amount claimed by the exchange.
+ total: Amount;
+
+ // Transfers aggregated in the wad.
+ items: WadItem[];
+ }
+
+ Objects in the wad item list have the following format:
+
+ .. ts:def:: WadItem
+
+ interface WadItem {
+
+ // Amount in the purse.
+ amount: Amount;
+
+ // Purse public key.
+ purse_pub: EddsaPublicKey;
+
+ // Account public key.
+ account_pub: EddsaPublicKey;
+
+ // Hash of the contract.
+ h_contract: HashCode;
+
+ // Signature created with the account's private key.
+ account_sig: EddsaSignature;
+
+ // Signature created with the purse's private key.
+ purse_sig: EddsaSignature;
+
+ // Deposit fees that were charged to the purse.
+ deposit_fees: Amount;
+
+ // Wad fees that was charged to the purse.
+ wad_fees: Amount;
+ }